move integration tests to test-containers (#984)
This commit is contained in:
@@ -22,9 +22,12 @@ workflows:
|
||||
build-then-test:
|
||||
jobs:
|
||||
- build
|
||||
- test:
|
||||
- fabric8_istio:
|
||||
requires:
|
||||
- build
|
||||
- test:
|
||||
requires:
|
||||
- fabric8_istio
|
||||
jobs:
|
||||
test:
|
||||
parallelism: 5 # parallel containers to split the tests among
|
||||
@@ -34,11 +37,35 @@ jobs:
|
||||
_JAVA_OPTIONS: "-Xms1024m -Xmx2048m"
|
||||
_SERVICE_OCCURENCE: 5
|
||||
steps:
|
||||
- run:
|
||||
name: testcontainers reuse support
|
||||
command: |
|
||||
# needed for .withReuse(true) to work
|
||||
echo "testcontainers.reuse.enable=true" > ~/.testcontainers.properties
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: /tmp/docker
|
||||
- run:
|
||||
name: Load Controller Images From Workspace
|
||||
command: |
|
||||
VIEW=$(ls -l /tmp/docker/images)
|
||||
echo "${VIEW}"
|
||||
docker load -i /tmp/docker/images/spring-cloud-kubernetes-configuration-watcher.tar
|
||||
docker load -i /tmp/docker/images/spring-cloud-kubernetes-discoveryserver.tar
|
||||
docker load -i /tmp/docker/images/spring-cloud-kubernetes-configserver.tar
|
||||
- run:
|
||||
name: Run regular tests
|
||||
command: |
|
||||
CLASSNAMES=$(circleci tests glob "**/src/test/**/**.java" | grep -v 'spring-cloud-kubernetes-integration-tests' \
|
||||
|
||||
###########################################################################################################################
|
||||
################################################# Build test support dependency ###########################################
|
||||
cd spring-cloud-kubernetes-test-support
|
||||
.././mvnw clean install
|
||||
cd ..
|
||||
|
||||
###########################################################################################################################
|
||||
##################################################### Split and run tests #################################################
|
||||
CLASSNAMES=$(circleci tests glob "**/src/test/**/**.java" | grep -v 'Fabric8IstioIT' \
|
||||
| xargs grep -l '@Test' \
|
||||
| sed 's/.*src.test.java.//g' | sed 's@/@.@g' \
|
||||
| sed 's/.\{5\}$//' \
|
||||
@@ -46,25 +73,14 @@ jobs:
|
||||
echo $CLASSNAMES
|
||||
TEST_ARG=$(echo $CLASSNAMES | sed 's/ /,/g')
|
||||
echo $TEST_ARG
|
||||
./mvnw -s .settings.xml -DfailIfNoTests=false -DtestsToRun=$TEST_ARG -e clean org.jacoco:jacoco-maven-plugin:prepare-agent test -U -P sonar -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
|
||||
./mvnw -s .settings.xml -DfailIfNoTests=false -DtestsToRun=$TEST_ARG -e clean 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/ \;
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
- kube-orb/install-kubectl
|
||||
- attach_workspace:
|
||||
at: ./
|
||||
- run:
|
||||
name: Load Controller Images From Workspace
|
||||
command: |
|
||||
docker load -i ./docker-images/spring-cloud-kubernetes-configuration-watcher.tar
|
||||
docker load -i ./docker-images/spring-cloud-kubernetes-discoveryserver.tar
|
||||
docker load -i ./docker-images/spring-cloud-kubernetes-configserver.tar
|
||||
- run:
|
||||
name: Run Kind Integration Tests
|
||||
command: |
|
||||
cd spring-cloud-kubernetes-integration-tests
|
||||
./run.sh
|
||||
- run:
|
||||
name: "Aggregate test results"
|
||||
when: always
|
||||
@@ -104,17 +120,56 @@ jobs:
|
||||
command: |
|
||||
TAG=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)
|
||||
echo $TAG
|
||||
mkdir docker-images
|
||||
docker save -o docker-images/spring-cloud-kubernetes-configuration-watcher.tar docker.io/springcloud/spring-cloud-kubernetes-configuration-watcher:${TAG}
|
||||
docker save -o docker-images/spring-cloud-kubernetes-discoveryserver.tar docker.io/springcloud/spring-cloud-kubernetes-discoveryserver:${TAG}
|
||||
docker save -o docker-images/spring-cloud-kubernetes-configserver.tar docker.io/springcloud/spring-cloud-kubernetes-configserver:${TAG}
|
||||
mkdir -p /tmp/docker/images/
|
||||
docker save -o /tmp/docker/images/spring-cloud-kubernetes-configuration-watcher.tar docker.io/springcloud/spring-cloud-kubernetes-configuration-watcher:${TAG}
|
||||
docker save -o /tmp/docker/images/spring-cloud-kubernetes-discoveryserver.tar docker.io/springcloud/spring-cloud-kubernetes-discoveryserver:${TAG}
|
||||
docker save -o /tmp/docker/images/spring-cloud-kubernetes-configserver.tar docker.io/springcloud/spring-cloud-kubernetes-configserver:${TAG}
|
||||
VIEW=$(ls -l /tmp/docker/images)
|
||||
echo "${VIEW}"
|
||||
- persist_to_workspace:
|
||||
root: ./
|
||||
paths: docker-images
|
||||
root: /tmp/docker/
|
||||
paths:
|
||||
- images
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
key: spring-cloud-kubernetes-{{ .Branch }}-{{ checksum "pom.xml" }}
|
||||
fabric8_istio:
|
||||
machine:
|
||||
image: ubuntu-2004:202201-02
|
||||
steps:
|
||||
- run:
|
||||
name: Install OpenJDK 17
|
||||
command: |
|
||||
wget -qO - https://adoptium.jfrog.io/adoptium/api/gpg/key/public | sudo apt-key add -
|
||||
sudo add-apt-repository --yes https://adoptium.jfrog.io/adoptium/deb/
|
||||
sudo apt-get update && sudo apt-get install temurin-17-jdk
|
||||
sudo update-alternatives --set java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java
|
||||
sudo update-alternatives --set javac /usr/lib/jvm/temurin-17-jdk-amd64/bin/javac
|
||||
java -version
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- spring-cloud-kubernetes-{{ .Branch }}-{{ checksum "pom.xml" }}
|
||||
- spring-cloud-kubernetes-{{ .Branch }}
|
||||
- spring-cloud-kubernetes
|
||||
- run:
|
||||
name: Run fabric8 istio test
|
||||
command: |
|
||||
|
||||
# we need to run some test, so that K3s container is started and then all other instances will re-use this one.
|
||||
# otherwise (since we use static ports) there might be two instances starting at the same time, and ports might conflict
|
||||
# this also deals with a slightly more involved istio set-up that is needed for this test
|
||||
|
||||
###########################################################################################################################
|
||||
######################################## Build test support dependency and Run test #######################################
|
||||
cd spring-cloud-kubernetes-test-support
|
||||
.././mvnw clean install
|
||||
cd ..
|
||||
|
||||
cd spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/
|
||||
../.././mvnw clean install
|
||||
cd ../..
|
||||
notify:
|
||||
webhooks:
|
||||
- url: https://webhooks.gitter.im/e/22e6bb4eb945dd61ba54
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: istio-test
|
||||
labels:
|
||||
istio-injection: enabled
|
||||
42
.github/workflows/maven.yaml
vendored
Normal file
42
.github/workflows/maven.yaml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# This workflow will build a Java project with Maven
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
|
||||
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
- name: Build with Maven
|
||||
run: ./mvnw -s .settings.xml clean 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
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@v2
|
||||
if: always() # always run even if the previous step fails
|
||||
with:
|
||||
report_paths: '**/surefire-reports/TEST-*.xml'
|
||||
- name: Archive code coverage results
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: surefire-reports
|
||||
path: '**/surefire-reports/*'
|
||||
BIN
istio-cli/istio-1.13.3/bin/istioctl
Executable file
BIN
istio-cli/istio-1.13.3/bin/istioctl
Executable file
Binary file not shown.
18
pom.xml
18
pom.xml
@@ -76,14 +76,11 @@
|
||||
<groovy.version>2.4.12</groovy.version>
|
||||
<restassured.version>3.0.2</restassured.version>
|
||||
|
||||
<excludeITTests>**/*IT.java</excludeITTests>
|
||||
|
||||
<maven-checkstyle-plugin.failsOnError>true</maven-checkstyle-plugin.failsOnError>
|
||||
<maven-checkstyle-plugin.failsOnViolation>true
|
||||
</maven-checkstyle-plugin.failsOnViolation>
|
||||
<maven-checkstyle-plugin.includeTestSourceDirectory>true
|
||||
</maven-checkstyle-plugin.includeTestSourceDirectory>
|
||||
<excludeITTests>**/*IT.java</excludeITTests>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
@@ -199,14 +196,9 @@
|
||||
<configuration>
|
||||
<parallel>all</parallel>
|
||||
<reuseForks>false</reuseForks>
|
||||
<!-- workaround for https://issues.apache.org/jira/projects/SUREFIRE/issues/SUREFIRE-1633?filter=allopenissues -->
|
||||
<!-- we run tests with mvn -DtestsToRun=.... in the pipeline-->
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
<excludes>
|
||||
<exclude>${excludeITTests}</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
@@ -359,16 +351,6 @@
|
||||
<configuration>
|
||||
<parallel>all</parallel>
|
||||
<reuseForks>false</reuseForks>
|
||||
<!-- workaround for https://issues.apache.org/jira/projects/SUREFIRE/issues/SUREFIRE-1633?filter=allopenissues -->
|
||||
<!-- we run tests with mvn -DtestsToRun=.... in the pipeline-->
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
<!-- Sets the VM argument line used when unit tests are run. -->
|
||||
<argLine>${surefireArgLine}</argLine>
|
||||
<excludes>
|
||||
<exclude>${excludeITTests}</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
@@ -8,3 +8,4 @@ management:
|
||||
health:
|
||||
rabbit:
|
||||
enabled: true
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# 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.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6
|
||||
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.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: integration-test
|
||||
name: spring-cloud-kubernetes-serviceaccount
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-core-k8s-client-it
|
||||
name: spring-cloud-kubernetes-core-k8s-client-it:view
|
||||
roleRef:
|
||||
kind: Role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
name: namespace-reader
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: spring-cloud-kubernetes-serviceaccount
|
||||
namespace: default
|
||||
- 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"]
|
||||
|
||||
# needed for istio
|
||||
|
||||
- apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: istio-integration-test
|
||||
name: spring-cloud-kubernetes-istio-serviceaccount
|
||||
namespace: istio-test
|
||||
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
namespace: istio-test
|
||||
name: istio-test
|
||||
rules:
|
||||
- apiGroups: [ "", "extensions", "apps" ]
|
||||
resources: [ "configmaps", "pods", "services", "endpoints", "secrets" ]
|
||||
verbs: [ "get", "list", "watch" ]
|
||||
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-core-k8s-client-it
|
||||
name: istio-test-rb
|
||||
roleRef:
|
||||
kind: Role
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
name: istio-test
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: spring-cloud-kubernetes-istio-serviceaccount
|
||||
namespace: istio-test
|
||||
@@ -13,31 +13,16 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Spring Cloud Kubernetes :: Integration Tests</name>
|
||||
<description>Integration tests where SCK applications are run inside a Kubernetes
|
||||
cluster
|
||||
</description>
|
||||
<description>Integration tests where SCK applications are run inside a Kubernetes cluster</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<arquillian-cube.version>1.18.2</arquillian-cube.version>
|
||||
<arquillian.version>1.4.0.Final</arquillian.version>
|
||||
<okhttptests.version>3.12.12</okhttptests.version>
|
||||
<docker-java.version>3.2.2</docker-java.version>
|
||||
|
||||
<!--
|
||||
The port on localhost where the application will listen to from outside the cluster
|
||||
Will be used to construct a NodePort
|
||||
-->
|
||||
<nodeport.value>32222</nodeport.value>
|
||||
<docker-java.version>3.2.13</docker-java.version>
|
||||
<testcontainers.version>1.16.3</testcontainers.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
@@ -46,7 +31,6 @@
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -79,34 +63,6 @@
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>it</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<service.host>localhost</service.host>
|
||||
<service.port>${nodeport.value}</service.port>
|
||||
<service.secure>false</service.secure>
|
||||
</systemPropertyVariables>
|
||||
<classesDirectory>${project.build.outputDirectory}
|
||||
</classesDirectory>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -123,6 +79,24 @@
|
||||
<version>${docker-java.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>k3s</artifactId>
|
||||
<version>${testcontainers.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</dependencyManagement>
|
||||
@@ -134,11 +108,10 @@
|
||||
<module>spring-cloud-kubernetes-fabric8-client-discovery</module>
|
||||
<module>spring-cloud-kubernetes-fabric8-client-loadbalancer</module>
|
||||
|
||||
<module>spring-cloud-kubernetes-discovery-client-it</module>
|
||||
<module>spring-cloud-kubernetes-reactive-discovery-client-it</module>
|
||||
<module>spring-cloud-kubernetes-discoveryclient-it</module>
|
||||
<module>spring-cloud-kubernetes-client-config-it</module>
|
||||
<module>spring-cloud-kubernetes-client-loadbalancer-it</module>
|
||||
<module>spring-cloud-kubernetes-client-reactive-discovery-client-it</module>
|
||||
<module>spring-cloud-kubernetes-client-reactive-discoveryclient-it</module>
|
||||
<module>spring-cloud-kubernetes-configuration-watcher-it</module>
|
||||
<module>spring-cloud-kubernetes-core-k8s-client-it</module>
|
||||
</modules>
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
#!/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"
|
||||
|
||||
ISTIO="${BIN_DIR}/istio"
|
||||
|
||||
CURRENT_DIR="$(pwd)"
|
||||
|
||||
MVN="${CURRENT_DIR}/../mvnw"
|
||||
|
||||
PROJECT_VERSION=$($MVN help:evaluate -Dexpression=project.version -q -DforceStdout)
|
||||
|
||||
ISTIO_VERSION="1.12.0"
|
||||
|
||||
ALL_INTEGRATION_PROJECTS=(
|
||||
"spring-cloud-kubernetes-core-k8s-client-it"
|
||||
"spring-cloud-kubernetes-client-config-it"
|
||||
"spring-cloud-kubernetes-configuration-watcher-it"
|
||||
"spring-cloud-kubernetes-client-loadbalancer-it"
|
||||
"spring-cloud-kubernetes-client-reactive-discovery-client-it"
|
||||
"spring-cloud-kubernetes-discovery-client-it"
|
||||
"spring-cloud-kubernetes-reactive-discovery-client-it"
|
||||
"spring-cloud-kubernetes-fabric8-client-simple-core"
|
||||
"spring-cloud-kubernetes-fabric8-client-configmap"
|
||||
"spring-cloud-kubernetes-fabric8-istio-it"
|
||||
"spring-cloud-kubernetes-fabric8-client-discovery"
|
||||
"spring-cloud-kubernetes-fabric8-client-loadbalancer"
|
||||
)
|
||||
INTEGRATION_PROJECTS=(${INTEGRATION_PROJECTS:-${ALL_INTEGRATION_PROJECTS[@]}})
|
||||
|
||||
DEFAULT_PULLING_IMAGES=(
|
||||
"jettech/kube-webhook-certgen:v1.2.2"
|
||||
"rabbitmq:3-management"
|
||||
"zookeeper:3.6.2"
|
||||
"rodolpheche/wiremock:2.27.2"
|
||||
"wurstmeister/kafka:2.13-2.6.0"
|
||||
"istio/proxyv2:${ISTIO_VERSION}"
|
||||
"istio/pilot:${ISTIO_VERSION}"
|
||||
)
|
||||
PULLING_IMAGES=(${PULLING_IMAGES:-${DEFAULT_PULLING_IMAGES[@]}})
|
||||
|
||||
LOADING_IMAGES=(${LOADING_IMAGES:-${DEFAULT_PULLING_IMAGES[@]}} "docker.io/springcloud/spring-cloud-kubernetes-configuration-watcher:${PROJECT_VERSION}"
|
||||
"docker.io/springcloud/spring-cloud-kubernetes-discoveryserver:${PROJECT_VERSION}")
|
||||
# cleanup on exit (useful for running locally)
|
||||
|
||||
cleanup() {
|
||||
"${KIND}" delete cluster || true
|
||||
rm -rf "${BIN_DIR}"
|
||||
}
|
||||
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.11.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 "Unknown OS, using linux binary"
|
||||
fi
|
||||
wget -O "${KIND}" "${KIND_BINARY_URL}"
|
||||
chmod +x "${KIND}"
|
||||
}
|
||||
|
||||
# util to install a released istio version into ${BIN_DIR}
|
||||
install_istio_release() {
|
||||
ISTIO_BINARY_URL="https://github.com/istio/istio/releases/download/$ISTIO_VERSION/istio-$ISTIO_VERSION-linux-amd64.tar.gz"
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
ISTIO_BINARY_URL="https://github.com/istio/istio/releases/download/$ISTIO_VERSION/istio-$ISTIO_VERSION-osx-arm64.tar.gz"
|
||||
else
|
||||
echo "Unknown OS, using linux binary"
|
||||
fi
|
||||
# seems like wget can't do both --output-document and --directory-prefix? At least on my Mac
|
||||
# this is the case. To be on the safe side, download, then rename
|
||||
wget --directory-prefix "${ISTIO}" "${ISTIO_BINARY_URL}"
|
||||
find "${ISTIO}" -type f -name "istio-*.tar.gz" -exec mv "{}" "${ISTIO}/istio.tar.gz" \;
|
||||
tar -xf "$BIN_DIR/istio/istio.tar.gz" -C "$BIN_DIR/istio"
|
||||
chmod +x "${ISTIO}/istio-$ISTIO_VERSION/bin/istioctl"
|
||||
export PATH=$PATH:"$ISTIO/istio-$ISTIO_VERSION/bin"
|
||||
|
||||
if ! [ -x "$(command -v istioctl)" ]; then
|
||||
echo 'Problem installing istioctl, check the script'
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
enable_istio() {
|
||||
kubectl create namespace istio-test
|
||||
kubectl label namespace istio-test istio-injection=enabled
|
||||
install_istio_release
|
||||
# remove taint, otherwise istio will not start
|
||||
kubectl taint node kind-control-plane node-role.kubernetes.io/master:NoSchedule-
|
||||
|
||||
# for Mac M1 : https://github.com/istio/istio/issues/21094#issuecomment-956117650
|
||||
istioctl install --set profile=demo -y
|
||||
}
|
||||
|
||||
main() {
|
||||
# get kind
|
||||
install_kind_release
|
||||
|
||||
# create a cluster
|
||||
cd $CURRENT_DIR
|
||||
|
||||
#TODO what happens if cluster is already there????
|
||||
"${KIND}" create cluster --config=kind-config.yaml -v 2147483647
|
||||
|
||||
# set KUBECONFIG to point to the cluster
|
||||
kubectl cluster-info --context kind-kind
|
||||
|
||||
# pulling necessary images for setting up the integration test environment
|
||||
for i in "${PULLING_IMAGES[@]}"; do
|
||||
echo "Pull images for prepping testing environment: $i"
|
||||
docker pull $i
|
||||
done
|
||||
for i in "${LOADING_IMAGES[@]}"; do
|
||||
echo "Loading images into Kind: $i"
|
||||
"${KIND}" load docker-image $i
|
||||
done
|
||||
|
||||
# istio
|
||||
install_istio_release
|
||||
enable_istio
|
||||
|
||||
#setup nginx ingress
|
||||
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
|
||||
sleep 5 # hold 5 sec so that the pods can be created
|
||||
kubectl wait --namespace ingress-nginx --for=condition=ready pod --selector=app.kubernetes.io/component=controller --timeout=420s
|
||||
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
# running tests..
|
||||
if [[ -z ${CIRCLECI+x} ]]; then
|
||||
run_tests "${INTEGRATION_PROJECTS[@]}"
|
||||
else
|
||||
#This splits projects across all circleci instances, it returns a list of projects separated by a space
|
||||
SPLIT_PROJECTS=$(printf "%s\n" "${INTEGRATION_PROJECTS[@]}" | circleci tests split)
|
||||
SPLIT_PROJECTS=$(echo $SPLIT_PROJECTS | sed 's/ /,/g')
|
||||
echo "split tests $SPLIT_PROJECTS"
|
||||
#This splits the projects back into an array so we can iterate over them
|
||||
IFS=',' read -ra PROJECTS <<< "$SPLIT_PROJECTS"
|
||||
echo "${PROJECTS[@]}"
|
||||
run_tests "${PROJECTS[@]}"
|
||||
fi
|
||||
|
||||
# teardown will happen automatically on exit
|
||||
}
|
||||
|
||||
run_tests() {
|
||||
arr=("$@")
|
||||
cd ../spring-cloud-kubernetes-test-support
|
||||
${MVN} clean install
|
||||
cd ../spring-cloud-kubernetes-integration-tests
|
||||
for p in "${arr[@]}"; do
|
||||
echo "Running test: $p"
|
||||
cd $p
|
||||
${MVN} spring-boot:build-image \
|
||||
-Dspring-boot.build-image.imageName=docker.io/springcloud/$p:${PROJECT_VERSION} -Dspring-boot.build-image.builder=paketobuildpacks/builder
|
||||
"${KIND}" load docker-image docker.io/springcloud/$p:${PROJECT_VERSION}
|
||||
# empty excludeITTests, so that integration tests will run
|
||||
${MVN} clean install -DexcludeITTests=
|
||||
cd ..
|
||||
done
|
||||
}
|
||||
|
||||
main
|
||||
@@ -5,8 +5,9 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-client-config-it</artifactId>
|
||||
@@ -51,6 +52,22 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>k3s</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -64,6 +81,56 @@
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<!-- build image in the 'package' phase, and ignore plain tests -->
|
||||
<!-- via maven-surefire-plugin::skipTests -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- run tests in the 'integration-tests' phase, one that is after 'package' (where we build the image) -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
@@ -103,6 +170,7 @@
|
||||
<env.IMAGE>springcloud/${project.artifactId}:${project.version}</env.IMAGE>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -3,9 +3,3 @@ management:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
#logging:
|
||||
# level:
|
||||
# org:
|
||||
# springframework:
|
||||
# cloud:
|
||||
# kubernetes: DEBUG
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
spring:
|
||||
cloud:
|
||||
kubernetes:
|
||||
enabled: true
|
||||
secrets:
|
||||
enable-api: true
|
||||
reload:
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
spring:
|
||||
application:
|
||||
name: spring-cloud-kubernetes-client-config-it
|
||||
cloud:
|
||||
kubernetes:
|
||||
enabled: false
|
||||
|
||||
|
||||
@@ -16,11 +16,10 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.config.it;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.apis.AppsV1Api;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
|
||||
@@ -29,29 +28,34 @@ import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1Secret;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import org.testcontainers.shaded.org.awaitility.Awaitility;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class ConfigMapAndSecretIT {
|
||||
class ConfigMapAndSecretIT {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(ConfigMapAndSecretIT.class);
|
||||
private static final String PROPERTY_URL = "localhost:80/myProperty";
|
||||
|
||||
private static final String SECRET_URL = "localhost:80/mySecret";
|
||||
|
||||
private static final String SPRING_CLOUD_CLIENT_CONFIG_IT_DEPLOYMENT_NAME = "spring-cloud-kubernetes-client-config-it-deployment";
|
||||
|
||||
@@ -61,15 +65,8 @@ public class ConfigMapAndSecretIT {
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private static final String MYPROPERTY_URL = "http://localhost:80/client-config-it/myProperty";
|
||||
|
||||
private static final String MYSECRET_URL = "http://localhost:80/client-config-it/mySecret";
|
||||
|
||||
private static final String APP_NAME = "spring-cloud-kubernetes-client-config-it";
|
||||
|
||||
// though not obvious, we need this, even if it is "unused"
|
||||
private static ApiClient client;
|
||||
|
||||
private static CoreV1Api api;
|
||||
|
||||
private static AppsV1Api appsApi;
|
||||
@@ -78,17 +75,28 @@ public class ConfigMapAndSecretIT {
|
||||
|
||||
private static K8SUtils k8SUtils;
|
||||
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws Exception {
|
||||
client = createApiClient();
|
||||
static void setup() throws Exception {
|
||||
K3S.start();
|
||||
Commons.validateImage(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S);
|
||||
Commons.loadImage(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S);
|
||||
createApiClient(K3S.getKubeConfigYaml());
|
||||
api = new CoreV1Api();
|
||||
appsApi = new AppsV1Api();
|
||||
networkingApi = new NetworkingV1Api();
|
||||
k8SUtils = new K8SUtils(api, appsApi);
|
||||
k8SUtils.setUp(NAMESPACE);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() throws Exception {
|
||||
Commons.cleanUp(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception {
|
||||
void after() throws Exception {
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + K8S_CONFIG_CLIENT_IT_NAME, null, null, null, null, null, null, null, null, null);
|
||||
api.deleteNamespacedService(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, NAMESPACE, null, null, null, null, null, null);
|
||||
@@ -97,56 +105,8 @@ public class ConfigMapAndSecretIT {
|
||||
api.deleteNamespacedSecret(APP_NAME, NAMESPACE, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public void testConfigMapAndSecretRefresh() throws Exception {
|
||||
|
||||
RestTemplate rest = new RestTemplateBuilder().build();
|
||||
rest.setErrorHandler(new ResponseErrorHandler() {
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
|
||||
LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode());
|
||||
return clientHttpResponse.getRawStatusCode() != 503;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse clientHttpResponse) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
|
||||
// available and we get a 503, we just need to wait a bit
|
||||
await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(2))
|
||||
.until(() -> rest.getForEntity(MYPROPERTY_URL, String.class).getStatusCode().is2xxSuccessful());
|
||||
|
||||
String myProperty = rest.getForObject(MYPROPERTY_URL, String.class);
|
||||
assertThat(myProperty).isEqualTo("from-config-map");
|
||||
String mySecret = rest.getForObject(MYSECRET_URL, String.class);
|
||||
assertThat(mySecret).isEqualTo("p455w0rd");
|
||||
|
||||
V1ConfigMap configMap = getConfigK8sClientItConfigMap();
|
||||
Map<String, String> data = configMap.getData();
|
||||
data.replace("application.yaml", data.get("application.yaml").replace("from-config-map", "from-unit-test"));
|
||||
configMap.data(data);
|
||||
api.replaceNamespacedConfigMap(APP_NAME, NAMESPACE, configMap, null, null, null);
|
||||
await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(2))
|
||||
.until(() -> rest.getForObject(MYPROPERTY_URL, String.class).equals("from-unit-test"));
|
||||
myProperty = rest.getForObject(MYPROPERTY_URL, String.class);
|
||||
assertThat(myProperty).isEqualTo("from-unit-test");
|
||||
|
||||
V1Secret secret = getConfigK8sClientItCSecret();
|
||||
Map<String, byte[]> secretData = secret.getData();
|
||||
secretData.replace("my.config.mySecret", "p455w1rd".getBytes());
|
||||
secret.setData(secretData);
|
||||
api.replaceNamespacedSecret(APP_NAME, NAMESPACE, secret, null, null, null);
|
||||
await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(2))
|
||||
.until(() -> rest.getForObject(MYSECRET_URL, String.class).equals("p455w1rd"));
|
||||
mySecret = rest.getForObject(MYSECRET_URL, String.class);
|
||||
assertThat(mySecret).isEqualTo("p455w1rd");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigMapAndSecretWatchRefresh() throws Exception {
|
||||
void testConfigMapAndSecretWatchRefresh() throws Exception {
|
||||
deployConfigK8sClientIt();
|
||||
|
||||
// Check to make sure the controller deployment is ready
|
||||
@@ -155,7 +115,7 @@ public class ConfigMapAndSecretIT {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigMapAndSecretPollingRefresh() throws Exception {
|
||||
void testConfigMapAndSecretPollingRefresh() throws Exception {
|
||||
deployConfigK8sClientPollingIt();
|
||||
|
||||
// Check to make sure the controller deployment is ready
|
||||
@@ -163,13 +123,47 @@ public class ConfigMapAndSecretIT {
|
||||
testConfigMapAndSecretRefresh();
|
||||
}
|
||||
|
||||
void testConfigMapAndSecretRefresh() throws Exception {
|
||||
|
||||
WebClient.Builder builder = builder();
|
||||
WebClient propertyClient = builder.baseUrl(PROPERTY_URL).build();
|
||||
|
||||
String property = propertyClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
|
||||
.retryWhen(retrySpec()).block();
|
||||
assertThat(property).isEqualTo("from-config-map");
|
||||
|
||||
WebClient secretClient = builder.baseUrl(SECRET_URL).build();
|
||||
String secret = secretClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec())
|
||||
.block();
|
||||
assertThat(secret).isEqualTo("p455w0rd");
|
||||
|
||||
V1ConfigMap configMap = getConfigK8sClientItConfigMap();
|
||||
Map<String, String> data = configMap.getData();
|
||||
data.replace("application.yaml", data.get("application.yaml").replace("from-config-map", "from-unit-test"));
|
||||
configMap.data(data);
|
||||
api.replaceNamespacedConfigMap(APP_NAME, NAMESPACE, configMap, null, null, null);
|
||||
Awaitility.await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(2))
|
||||
.until(() -> propertyClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class).block()
|
||||
.equals("from-unit-test"));
|
||||
V1Secret v1Secret = getConfigK8sClientItCSecret();
|
||||
Map<String, byte[]> secretData = v1Secret.getData();
|
||||
secretData.replace("my.config.mySecret", "p455w1rd".getBytes());
|
||||
v1Secret.setData(secretData);
|
||||
api.replaceNamespacedSecret(APP_NAME, NAMESPACE, v1Secret, null, null, null);
|
||||
Awaitility.await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(2)).until(() -> secretClient
|
||||
.method(HttpMethod.GET).retrieve().bodyToMono(String.class).block().equals("p455w1rd"));
|
||||
}
|
||||
|
||||
private static void deployConfigK8sClientIt() throws Exception {
|
||||
k8SUtils.waitForDeploymentToBeDeleted(K8S_CONFIG_CLIENT_IT_NAME, NAMESPACE);
|
||||
api.createNamespacedSecret(NAMESPACE, getConfigK8sClientItCSecret(), null, null, null);
|
||||
api.createNamespacedConfigMap(NAMESPACE, getConfigK8sClientItConfigMap(), null, null, null);
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getConfigK8sClientItDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getConfigK8sClientItService(), null, null, null);
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, getConfigK8sClientItIngress(), null, null, null);
|
||||
|
||||
V1Ingress ingress = getConfigK8sClientItIngress();
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null);
|
||||
k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE);
|
||||
}
|
||||
|
||||
private static void deployConfigK8sClientPollingIt() throws Exception {
|
||||
@@ -215,4 +209,12 @@ public class ConfigMapAndSecretIT {
|
||||
return (V1Secret) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-client-config-it-secret.yaml");
|
||||
}
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -3,13 +3,11 @@ kind: Ingress
|
||||
metadata:
|
||||
name: it-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /client-config-it(/|$)(.*)
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
|
||||
@@ -48,6 +48,27 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>k3s</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -61,6 +82,55 @@
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<!-- build image in the 'package' phase, and ignore plain tests -->
|
||||
<!-- via maven-surefire-plugin::skipTests -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- run tests in the 'integration-tests' phase, one that is after 'package' (where we build the image) -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -19,15 +19,18 @@ package org.springframework.cloud.kubernetes.client.loadbalancer.it;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
@@ -37,6 +40,8 @@ import org.springframework.web.client.RestTemplate;
|
||||
@RestController
|
||||
public class KubernetesClientLoadBalancerApplicationIt {
|
||||
|
||||
private static final String URL = "http://servicea-wiremock/__admin/mappings";
|
||||
|
||||
private final DiscoveryClient discoveryClient;
|
||||
|
||||
public KubernetesClientLoadBalancerApplicationIt(DiscoveryClient discoveryClien) {
|
||||
@@ -49,13 +54,15 @@ public class KubernetesClientLoadBalancerApplicationIt {
|
||||
|
||||
@Bean
|
||||
@LoadBalanced
|
||||
RestTemplate restTemplate() {
|
||||
return new RestTemplateBuilder().build();
|
||||
WebClient.Builder client() {
|
||||
return WebClient.builder();
|
||||
}
|
||||
|
||||
@GetMapping("/servicea")
|
||||
@GetMapping("/loadbalancer-it/servicea")
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> greeting() {
|
||||
return restTemplate().getForObject("http://servicea-wiremock/__admin/mappings", Map.class);
|
||||
return (Map<String, Object>) client().clientConnector(new ReactorClientHttpConnector(HttpClient.create()))
|
||||
.baseUrl(URL).build().method(HttpMethod.GET).retrieve().bodyToMono(Map.class).block();
|
||||
}
|
||||
|
||||
@GetMapping("/services")
|
||||
|
||||
@@ -16,12 +16,10 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.loadbalancer.it;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import io.kubernetes.client.openapi.apis.AppsV1Api;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
@@ -29,33 +27,34 @@ import io.kubernetes.client.openapi.apis.NetworkingV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class LoadBalancerIT {
|
||||
class LoadBalancerIT {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(LoadBalancerIT.class);
|
||||
|
||||
private static final String WIREMOCK_DEPLOYMENT_NAME = "servicea-wiremock-deployment";
|
||||
|
||||
private static final String WIREMOCK_APP_NAME = "servicea-wiremock";
|
||||
private static final String SERVICE_URL = "localhost:80/loadbalancer-it/servicea";
|
||||
|
||||
private static final String SPRING_CLOUD_K8S_LOADBALANCER_DEPLOYMENT_NAME = "spring-cloud-kubernetes-client-loadbalancer-it-deployment";
|
||||
|
||||
@@ -63,54 +62,56 @@ public class LoadBalancerIT {
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private ApiClient client;
|
||||
private static CoreV1Api api;
|
||||
|
||||
private CoreV1Api api;
|
||||
private static AppsV1Api appsApi;
|
||||
|
||||
private AppsV1Api appsApi;
|
||||
private static NetworkingV1Api networkingApi;
|
||||
|
||||
private NetworkingV1Api networkingApi;
|
||||
private static K8SUtils k8SUtils;
|
||||
|
||||
private K8SUtils k8SUtils;
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
Commons.validateImage(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S);
|
||||
Commons.loadImage(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S);
|
||||
createApiClient(K3S.getKubeConfigYaml());
|
||||
api = new CoreV1Api();
|
||||
appsApi = new AppsV1Api();
|
||||
networkingApi = new NetworkingV1Api();
|
||||
k8SUtils = new K8SUtils(api, appsApi);
|
||||
k8SUtils.setUp(NAMESPACE);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() throws Exception {
|
||||
Commons.cleanUp(SPRING_CLOUD_K8S_LOADBALANCER_APP_NAME, K3S);
|
||||
k8SUtils.removeWiremockImage();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
this.client = createApiClient();
|
||||
this.api = new CoreV1Api();
|
||||
this.appsApi = new AppsV1Api();
|
||||
this.networkingApi = new NetworkingV1Api();
|
||||
this.k8SUtils = new K8SUtils(api, appsApi);
|
||||
|
||||
deployWiremock();
|
||||
|
||||
// Check to make sure the wiremock deployment is ready
|
||||
k8SUtils.waitForDeployment(WIREMOCK_DEPLOYMENT_NAME, NAMESPACE);
|
||||
|
||||
// Check to see if endpoint is ready
|
||||
k8SUtils.waitForEndpointReady(WIREMOCK_APP_NAME, NAMESPACE);
|
||||
void setup() throws Exception {
|
||||
k8SUtils.deployWiremock(NAMESPACE, false);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach() throws Exception {
|
||||
cleanup();
|
||||
k8SUtils.cleanUpWiremock(NAMESPACE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadBalancerServiceMode() throws Exception {
|
||||
try {
|
||||
deployLoadbalancerServiceIt();
|
||||
testLoadBalancer();
|
||||
}
|
||||
finally {
|
||||
cleanup();
|
||||
}
|
||||
void testLoadBalancerServiceMode() throws Exception {
|
||||
deployLoadbalancerServiceIt();
|
||||
testLoadBalancer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadBalancerPodMode() throws Exception {
|
||||
try {
|
||||
deployLoadbalancerPodIt();
|
||||
testLoadBalancer();
|
||||
}
|
||||
finally {
|
||||
cleanup();
|
||||
}
|
||||
void testLoadBalancerPodMode() throws Exception {
|
||||
deployLoadbalancerPodIt();
|
||||
testLoadBalancer();
|
||||
}
|
||||
|
||||
private void cleanup() throws ApiException {
|
||||
@@ -125,50 +126,37 @@ public class LoadBalancerIT {
|
||||
private void testLoadBalancer() {
|
||||
// Check to make sure the controller deployment is ready
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_LOADBALANCER_DEPLOYMENT_NAME, NAMESPACE);
|
||||
RestTemplate rest = new RestTemplateBuilder().build();
|
||||
rest.setErrorHandler(new ResponseErrorHandler() {
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
|
||||
LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode());
|
||||
return clientHttpResponse.getRawStatusCode() != 503;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse clientHttpResponse) {
|
||||
WebClient.Builder builder = builder();
|
||||
WebClient serviceClient = builder.baseUrl(SERVICE_URL).build();
|
||||
|
||||
}
|
||||
});
|
||||
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
|
||||
// available and we get a 503, we just need to wait a bit
|
||||
await().pollInterval(Duration.ofSeconds(1)).atMost(600, TimeUnit.SECONDS).ignoreExceptions()
|
||||
.until(() -> rest.getForEntity("http://localhost:80/loadbalancer-it/servicea", String.class)
|
||||
.getStatusCode().is2xxSuccessful());
|
||||
Map<String, Object> result = rest.getForObject("http://localhost:80/loadbalancer-it/servicea", Map.class);
|
||||
assertThat(result.containsKey("mappings")).isTrue();
|
||||
assertThat(result.containsKey("meta")).isTrue();
|
||||
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> result = (Map<String, Object>) serviceClient.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec())
|
||||
.block();
|
||||
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception {
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + WIREMOCK_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null, null);
|
||||
|
||||
api.deleteNamespacedService(WIREMOCK_APP_NAME, NAMESPACE, null, null, null, null, null, null);
|
||||
networkingApi.deleteNamespacedIngress("wiremock-ingress", NAMESPACE, null, null, null, null, null, null);
|
||||
Assertions.assertThat(result.containsKey("mappings")).isTrue();
|
||||
Assertions.assertThat(result.containsKey("meta")).isTrue();
|
||||
|
||||
}
|
||||
|
||||
private void deployLoadbalancerServiceIt() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getLoadbalancerServiceItDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getLoadbalancerItService(), null, null, null);
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, getLoadbalancerItIngress(), null, null, null);
|
||||
deployIngress();
|
||||
}
|
||||
|
||||
private void deployLoadbalancerPodIt() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getLoadbalancerPodItDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getLoadbalancerItService(), null, null, null);
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, getLoadbalancerItIngress(), null, null, null);
|
||||
deployIngress();
|
||||
}
|
||||
|
||||
private void deployIngress() throws Exception {
|
||||
V1Ingress ingress = getLoadbalancerItIngress();
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null);
|
||||
k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE);
|
||||
}
|
||||
|
||||
private V1Deployment getLoadbalancerServiceItDeployment() throws Exception {
|
||||
@@ -189,12 +177,6 @@ public class LoadBalancerIT {
|
||||
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 V1Ingress getLoadbalancerItIngress() throws Exception {
|
||||
return (V1Ingress) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-client-loadbalancer-it-ingress.yaml");
|
||||
@@ -205,16 +187,12 @@ public class LoadBalancerIT {
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-client-loadbalancer-it-service.yaml");
|
||||
}
|
||||
|
||||
private V1Ingress getWiremockIngress() throws Exception {
|
||||
return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock-ingress.yaml");
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private V1Service getWiremockAppService() throws Exception {
|
||||
return (V1Service) K8SUtils.readYamlFromClasspath("wiremock-service.yaml");
|
||||
}
|
||||
|
||||
private V1Deployment getWireockDeployment() throws Exception {
|
||||
return (V1Deployment) K8SUtils.readYamlFromClasspath("wiremock-deployment.yaml");
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -3,13 +3,11 @@ kind: Ingress
|
||||
metadata:
|
||||
name: it-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /loadbalancer-it(/|$)(.*)
|
||||
- path: /loadbalancer-it/
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
|
||||
@@ -13,7 +13,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: servicea-wiremock
|
||||
image: rodolpheche/wiremock:2.27.2
|
||||
image: wiremock/wiremock:2.32.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
@@ -3,13 +3,11 @@ kind: Ingress
|
||||
metadata:
|
||||
name: wiremock-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /wiremock(/|$)(.*)
|
||||
- path: /wiremock/
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* 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.client.reactive.discovery.it;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import io.kubernetes.client.openapi.apis.AppsV1Api;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class ReactiveDiscoveryClientIT {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(ReactiveDiscoveryClientIT.class);
|
||||
|
||||
private static final String WIREMOCK_DEPLOYMENT_NAME = "servicea-wiremock-deployment";
|
||||
|
||||
private static final String WIREMOCK_APP_NAME = "servicea-wiremock";
|
||||
|
||||
private static final String SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME = "spring-cloud-kubernetes-client-reactive-discovery-it-deployment";
|
||||
|
||||
private static final String SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME = "spring-cloud-kubernetes-client-reactive-discovery-it";
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private ApiClient client;
|
||||
|
||||
private CoreV1Api api;
|
||||
|
||||
private AppsV1Api appsApi;
|
||||
|
||||
private NetworkingV1Api networkingApi;
|
||||
|
||||
private K8SUtils k8SUtils;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
this.client = createApiClient();
|
||||
this.api = new CoreV1Api();
|
||||
this.appsApi = new AppsV1Api();
|
||||
this.networkingApi = new NetworkingV1Api();
|
||||
this.k8SUtils = new K8SUtils(api, appsApi);
|
||||
|
||||
deployWiremock();
|
||||
|
||||
// Check to make sure the wiremock deployment is ready
|
||||
k8SUtils.waitForDeployment(WIREMOCK_DEPLOYMENT_NAME, NAMESPACE);
|
||||
|
||||
// Check to see if endpoint is ready
|
||||
k8SUtils.waitForEndpointReady(WIREMOCK_APP_NAME, NAMESPACE);
|
||||
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception {
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + WIREMOCK_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null, null);
|
||||
|
||||
api.deleteNamespacedService(WIREMOCK_APP_NAME, NAMESPACE, null, null, null, null, null, null);
|
||||
networkingApi.deleteNamespacedIngress("wiremock-ingress", NAMESPACE, null, null, null, null, null, null);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReactiveDiscoveryClient() throws Exception {
|
||||
try {
|
||||
deployReactiveDiscoveryIt();
|
||||
testLoadBalancer();
|
||||
testHealth();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
private void testHealth() {
|
||||
RestTemplate rest = createRestTemplate();
|
||||
|
||||
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
|
||||
// available and we get a 503, we just need to wait a bit
|
||||
await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(1))
|
||||
.until(() -> rest
|
||||
.getForEntity("http://localhost:80/reactive-discovery-it/actuator/health", String.class)
|
||||
.getStatusCode().is2xxSuccessful());
|
||||
|
||||
Map<String, Object> health = rest.getForObject("http://localhost:80/reactive-discovery-it/actuator/health",
|
||||
Map.class);
|
||||
Map<String, Object> components = (Map<String, Object>) health.get("components");
|
||||
|
||||
assertThat(components.containsKey("reactiveDiscoveryClients")).isTrue();
|
||||
Map<String, Object> discoveryComposite = (Map<String, Object>) components.get("discoveryComposite");
|
||||
assertThat(discoveryComposite.get("status")).isEqualTo("UP");
|
||||
}
|
||||
|
||||
private void cleanup() throws ApiException {
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME, null, null, null, null, null,
|
||||
null, null, null, null);
|
||||
api.deleteNamespacedService(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME, NAMESPACE, null, null, null, null,
|
||||
null, null);
|
||||
networkingApi.deleteNamespacedIngress("it-ingress", NAMESPACE, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
private void testLoadBalancer() throws Exception {
|
||||
// Check to make sure the controller deployment is ready
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME, NAMESPACE);
|
||||
RestTemplate rest = createRestTemplate();
|
||||
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
|
||||
// available and we get a 503, we just need to wait a bit
|
||||
await().timeout(Duration.ofSeconds(60)).pollInterval(Duration.ofSeconds(1))
|
||||
.until(() -> rest.getForEntity("http://localhost:80/reactive-discovery-it/services", String.class)
|
||||
.getStatusCode().is2xxSuccessful());
|
||||
String result = rest.getForObject("http://localhost:80/reactive-discovery-it/services", String.class);
|
||||
assertThat(Arrays.stream(result.split(",")).anyMatch(s -> "servicea-wiremock".equalsIgnoreCase(s))).isTrue();
|
||||
|
||||
}
|
||||
|
||||
private RestTemplate createRestTemplate() {
|
||||
RestTemplate rest = new RestTemplateBuilder().build();
|
||||
|
||||
rest.setErrorHandler(new ResponseErrorHandler() {
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
|
||||
LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode());
|
||||
return clientHttpResponse.getRawStatusCode() != 503;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse clientHttpResponse) {
|
||||
|
||||
}
|
||||
});
|
||||
return rest;
|
||||
}
|
||||
|
||||
private void deployReactiveDiscoveryIt() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getReactiveDiscoveryItDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getReactiveDiscoveryService(), null, null, null);
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, getReactiveDiscoveryItIngress(), null, null, null);
|
||||
}
|
||||
|
||||
private V1Deployment getReactiveDiscoveryItDeployment() throws Exception {
|
||||
V1Deployment deployment = (V1Deployment) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-client-reactive-discovery-it-deployment.yaml");
|
||||
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
|
||||
+ getPomVersion();
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image);
|
||||
return deployment;
|
||||
}
|
||||
|
||||
private V1Service getReactiveDiscoveryService() throws Exception {
|
||||
return (V1Service) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-client-reactive-discovery-it-service.yaml");
|
||||
}
|
||||
|
||||
private V1Ingress getReactiveDiscoveryItIngress() throws Exception {
|
||||
return (V1Ingress) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-client-reactive-discovery-it-ingress.yaml");
|
||||
}
|
||||
|
||||
private void deployWiremock() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getWiremockDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getWiremockAppService(), null, null, null);
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, getWiremockIngress(), null, null, null);
|
||||
}
|
||||
|
||||
private V1Ingress getWiremockIngress() throws Exception {
|
||||
return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock-ingress.yaml");
|
||||
}
|
||||
|
||||
private V1Service getWiremockAppService() throws Exception {
|
||||
return (V1Service) K8SUtils.readYamlFromClasspath("wiremock-service.yaml");
|
||||
}
|
||||
|
||||
private V1Deployment getWiremockDeployment() throws Exception {
|
||||
return (V1Deployment) K8SUtils.readYamlFromClasspath("wiremock-deployment.yaml");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-client-reactive-discovery-it
|
||||
name: spring-cloud-kubernetes-client-reactive-discovery-it
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: spring-cloud-kubernetes-client-reactive-discovery-it
|
||||
type: ClusterIP
|
||||
@@ -9,7 +9,7 @@
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-client-reactive-discovery-client-it</artifactId>
|
||||
<artifactId>spring-cloud-kubernetes-client-reactive-discoveryclient-it</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -47,6 +47,22 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>k3s</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -60,6 +76,55 @@
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<!-- build image in the 'package' phase, and ignore plain tests -->
|
||||
<!-- via maven-surefire-plugin::skipTests -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- run tests in the 'integration-tests' phase, one that is after 'package' (where we build the image) -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
@@ -1,3 +1,7 @@
|
||||
spring:
|
||||
webflux:
|
||||
base-path: /reactive-discovery-it
|
||||
|
||||
management:
|
||||
endpoint:
|
||||
health:
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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.client.reactive.discovery.it;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import io.kubernetes.client.openapi.apis.AppsV1Api;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
class ReactiveDiscoveryClientIT {
|
||||
|
||||
private static final String HEALTH_URL = "localhost:80/reactive-discovery-it/actuator/health";
|
||||
|
||||
private static final String SERVICES_URL = "localhost:80/reactive-discovery-it/services";
|
||||
|
||||
private static final String SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME = "spring-cloud-kubernetes-client-reactive-discoveryclient-it-deployment";
|
||||
|
||||
private static final String SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME = "spring-cloud-kubernetes-client-reactive-discoveryclient-it";
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private static CoreV1Api api;
|
||||
|
||||
private static AppsV1Api appsApi;
|
||||
|
||||
private static NetworkingV1Api networkingApi;
|
||||
|
||||
private static K8SUtils k8SUtils;
|
||||
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
Commons.validateImage(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME, K3S);
|
||||
Commons.loadImage(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME, K3S);
|
||||
createApiClient(K3S.getKubeConfigYaml());
|
||||
api = new CoreV1Api();
|
||||
appsApi = new AppsV1Api();
|
||||
networkingApi = new NetworkingV1Api();
|
||||
k8SUtils = new K8SUtils(api, appsApi);
|
||||
k8SUtils.setUp(NAMESPACE);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() throws Exception {
|
||||
Commons.cleanUp(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME, K3S);
|
||||
k8SUtils.removeWiremockImage();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
k8SUtils.deployWiremock(NAMESPACE, false);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void after() throws Exception {
|
||||
k8SUtils.cleanUpWiremock(NAMESPACE);
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReactiveDiscoveryClient() throws Exception {
|
||||
deployReactiveDiscoveryIt();
|
||||
testLoadBalancer();
|
||||
testHealth();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void testHealth() {
|
||||
|
||||
WebClient.Builder builder = builder();
|
||||
WebClient serviceClient = builder.baseUrl(HEALTH_URL).build();
|
||||
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> health = (Map<String, Object>) serviceClient.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec())
|
||||
.block();
|
||||
|
||||
Map<String, Object> components = (Map<String, Object>) health.get("components");
|
||||
|
||||
assertThat(components.containsKey("reactiveDiscoveryClients")).isTrue();
|
||||
Map<String, Object> discoveryComposite = (Map<String, Object>) components.get("discoveryComposite");
|
||||
assertThat(discoveryComposite.get("status")).isEqualTo("UP");
|
||||
}
|
||||
|
||||
private void cleanup() throws ApiException {
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME, null, null, null, null, null,
|
||||
null, null, null, null);
|
||||
api.deleteNamespacedService(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_APP_NAME, NAMESPACE, null, null, null, null,
|
||||
null, null);
|
||||
networkingApi.deleteNamespacedIngress("it-ingress", NAMESPACE, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
private void testLoadBalancer() {
|
||||
|
||||
// Check to make sure the controller deployment is ready
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_REACTIVE_DISCOVERY_DEPLOYMENT_NAME, NAMESPACE);
|
||||
|
||||
WebClient.Builder builder = builder();
|
||||
WebClient serviceClient = builder.baseUrl(SERVICES_URL).build();
|
||||
String servicesResponse = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
|
||||
.retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions
|
||||
.assertThat(Arrays.stream(servicesResponse.split(",")).anyMatch("servicea-wiremock"::equalsIgnoreCase))
|
||||
.isTrue();
|
||||
|
||||
}
|
||||
|
||||
private void deployIngress(V1Ingress ingress) throws Exception {
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null);
|
||||
k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE);
|
||||
}
|
||||
|
||||
private void deployReactiveDiscoveryIt() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getReactiveDiscoveryItDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getReactiveDiscoveryService(), null, null, null);
|
||||
deployIngress(getReactiveDiscoveryItIngress());
|
||||
}
|
||||
|
||||
private V1Deployment getReactiveDiscoveryItDeployment() throws Exception {
|
||||
V1Deployment deployment = (V1Deployment) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-client-reactive-discoveryclient-it-deployment.yaml");
|
||||
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
|
||||
+ getPomVersion();
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image);
|
||||
return deployment;
|
||||
}
|
||||
|
||||
private V1Service getReactiveDiscoveryService() throws Exception {
|
||||
return (V1Service) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-client-reactive-discoveryclient-it-service.yaml");
|
||||
}
|
||||
|
||||
private V1Ingress getReactiveDiscoveryItIngress() throws Exception {
|
||||
return (V1Ingress) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-client-reactive-discoveryclient-it-ingress.yaml");
|
||||
}
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -1,28 +1,28 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-client-reactive-discovery-it-deployment
|
||||
name: spring-cloud-kubernetes-client-reactive-discoveryclient-it-deployment
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: spring-cloud-kubernetes-client-reactive-discovery-it
|
||||
app: spring-cloud-kubernetes-client-reactive-discoveryclient-it
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-client-reactive-discovery-it
|
||||
app: spring-cloud-kubernetes-client-reactive-discoveryclient-it
|
||||
spec:
|
||||
serviceAccountName: spring-cloud-kubernetes-serviceaccount
|
||||
containers:
|
||||
- name: spring-cloud-kubernetes-client-reactive-discovery-it
|
||||
image: docker.io/springcloud/spring-cloud-kubernetes-client-reactive-discovery-client-it
|
||||
- name: spring-cloud-kubernetes-client-reactive-discoveryclient-it
|
||||
image: docker.io/springcloud/spring-cloud-kubernetes-client-reactive-discoveryclient-it
|
||||
imagePullPolicy: IfNotPresent
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
path: /actuator/health/readiness
|
||||
path: /reactive-discovery-it/actuator/health/readiness
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
path: /actuator/health/liveness
|
||||
path: /reactive-discovery-it/actuator/health/liveness
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
@@ -3,16 +3,14 @@ kind: Ingress
|
||||
metadata:
|
||||
name: it-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /reactive-discovery-it(/|$)(.*)
|
||||
pathType: Prefix
|
||||
- path: /reactive-discovery-it
|
||||
pathType: ImplementationSpecific
|
||||
backend:
|
||||
service:
|
||||
name: spring-cloud-kubernetes-client-reactive-discovery-it
|
||||
name: spring-cloud-kubernetes-client-reactive-discoveryclient-it
|
||||
port:
|
||||
number: 8080
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: spring-cloud-kubernetes-client-reactive-discoveryclient-it
|
||||
name: spring-cloud-kubernetes-client-reactive-discoveryclient-it
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: spring-cloud-kubernetes-client-reactive-discoveryclient-it
|
||||
type: ClusterIP
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -74,6 +74,29 @@
|
||||
<artifactId>docker-java-transport-httpclient5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>k3s</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -87,6 +110,55 @@
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<!-- build image in the 'package' phase, and ignore plain tests -->
|
||||
<!-- via maven-surefire-plugin::skipTests -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- run tests in the 'integration-tests' phase, one that is after 'package' (where we build the image) -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -38,15 +38,15 @@ public class ConfigWatcherTestApplication implements ApplicationListener<Refresh
|
||||
SpringApplication.run(ConfigWatcherTestApplication.class, args);
|
||||
}
|
||||
|
||||
@GetMapping("/")
|
||||
@GetMapping("/it")
|
||||
public boolean index() {
|
||||
log.warn("Current value: " + value);
|
||||
log.info("Current value: " + value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(RefreshRemoteApplicationEvent refreshRemoteApplicationEvent) {
|
||||
log.warn("Received remote refresh event");
|
||||
log.info("Received remote refresh event");
|
||||
this.value = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
spring:
|
||||
application:
|
||||
name: spring-cloud-kubernetes-configuration-watcher-it
|
||||
@@ -20,19 +20,20 @@ import java.time.Duration;
|
||||
|
||||
import com.github.tomakehurst.wiremock.client.VerificationException;
|
||||
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.apis.AppsV1Api;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1ConfigMap;
|
||||
import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
|
||||
@@ -49,66 +50,84 @@ import static org.springframework.cloud.kubernetes.integration.tests.commons.K8S
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class ActuatorRefreshIT {
|
||||
|
||||
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";
|
||||
class ActuatorRefreshIT {
|
||||
|
||||
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 String configWatcherConfigMapName;
|
||||
|
||||
private static final String WIREMOCK_HOST = "localhost";
|
||||
|
||||
private static final String WIREMOCK_PATH = "/wiremock";
|
||||
private static final String WIREMOCK_PATH = "/";
|
||||
|
||||
private static final int WIREMOCK_PORT = 80;
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private ApiClient client;
|
||||
private static CoreV1Api api;
|
||||
|
||||
private CoreV1Api api;
|
||||
private static AppsV1Api appsApi;
|
||||
|
||||
private AppsV1Api appsApi;
|
||||
private static K8SUtils k8SUtils;
|
||||
|
||||
private NetworkingV1Api networkingApi;
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
private K8SUtils k8SUtils;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
this.client = createApiClient();
|
||||
this.api = new CoreV1Api();
|
||||
this.appsApi = new AppsV1Api();
|
||||
this.networkingApi = new NetworkingV1Api();
|
||||
this.k8SUtils = new K8SUtils(api, appsApi);
|
||||
|
||||
deployWiremock();
|
||||
deployConfigWatcher();
|
||||
|
||||
// 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);
|
||||
// Check to make sure the controller deployment is ready
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE);
|
||||
@BeforeAll
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
Commons.validateImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S);
|
||||
Commons.loadImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S);
|
||||
createApiClient(K3S.getKubeConfigYaml());
|
||||
api = new CoreV1Api();
|
||||
appsApi = new AppsV1Api();
|
||||
k8SUtils = new K8SUtils(api, appsApi);
|
||||
k8SUtils.setUp(NAMESPACE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActuatorRefresh() throws Exception {
|
||||
// Configure wiremock to point at the server
|
||||
WireMock.configureFor(WIREMOCK_HOST, WIREMOCK_PORT, WIREMOCK_PATH);
|
||||
@AfterAll
|
||||
static void afterAll() throws Exception {
|
||||
Commons.cleanUp(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S);
|
||||
k8SUtils.removeWiremockImage();
|
||||
}
|
||||
|
||||
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
|
||||
// available and we get a 503, we just need to wait a bit
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
deployConfigWatcher();
|
||||
k8SUtils.deployWiremock(NAMESPACE, true);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void after() throws Exception {
|
||||
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, null, null, null, null, null, null,
|
||||
null, null, null);
|
||||
api.deleteNamespacedService(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, NAMESPACE, null, null, null, null, null,
|
||||
null);
|
||||
|
||||
api.deleteNamespacedConfigMap(configWatcherConfigMapName, NAMESPACE, null, null, null, null, null, null);
|
||||
api.deleteNamespacedConfigMap("servicea-wiremock", NAMESPACE, null, null, null, null, null, null);
|
||||
k8SUtils.cleanUpWiremock(NAMESPACE);
|
||||
}
|
||||
|
||||
/*
|
||||
* this test loads uses two services: wiremock on port 8080 and configuration-watcher
|
||||
* on port 8888. we deploy configuration-watcher first and configure it via a
|
||||
* configmap with the same name. then, we mock the call to actuator/refresh endpoint
|
||||
* and deploy a new configmap: "servicea-wiremock", this in turn will trigger that
|
||||
* refresh that we capture and assert for.
|
||||
*/
|
||||
@Test
|
||||
void testActuatorRefresh() throws Exception {
|
||||
WireMock.configureFor(WIREMOCK_HOST, WIREMOCK_PORT, WIREMOCK_PATH);
|
||||
await().timeout(Duration.ofSeconds(60)).ignoreException(VerificationException.class)
|
||||
.until(() -> stubFor(post(urlEqualTo("/actuator/refresh")).willReturn(aResponse().withStatus(200)))
|
||||
.getResponse().wasConfigured());
|
||||
|
||||
// Create new configmap to trigger controller to signal app to refresh
|
||||
V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata().withName(CONFIG_WATCHER_WIREMOCK_APP_NAME)
|
||||
V1ConfigMap configMap = new V1ConfigMapBuilder().editOrNewMetadata().withName("servicea-wiremock")
|
||||
.addToLabels("spring.cloud.kubernetes.config", "true").endMetadata().addToData("foo", "bar").build();
|
||||
api.createNamespacedConfigMap(NAMESPACE, configMap, null, null, null);
|
||||
|
||||
@@ -119,67 +138,34 @@ public class ActuatorRefreshIT {
|
||||
verify(postRequestedFor(urlEqualTo("/actuator/refresh")));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception {
|
||||
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, null, null, null, null, null, null,
|
||||
null, null, null);
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + CONFIG_WATCHER_WIREMOCK_DEPLOYMENT_NAME, null, null, null, null, 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);
|
||||
// Check to make sure the controller deployment is deleted
|
||||
k8SUtils.waitForDeploymentToBeDeleted(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE);
|
||||
k8SUtils.waitForDeploymentToBeDeleted(CONFIG_WATCHER_WIREMOCK_DEPLOYMENT_NAME, NAMESPACE);
|
||||
}
|
||||
|
||||
private void deployConfigWatcher() throws Exception {
|
||||
api.createNamespacedConfigMap(NAMESPACE, getConfigWatcherConfigMap(), null, null, null);
|
||||
V1ConfigMap configMap = getConfigWatcherConfigMap();
|
||||
configWatcherConfigMapName = configMap.getMetadata().getName();
|
||||
api.createNamespacedConfigMap(NAMESPACE, configMap, null, null, null);
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getConfigWatcherDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getConfigWatcherService(), null, null, null);
|
||||
|
||||
// Check to make sure the controller deployment is ready
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE);
|
||||
}
|
||||
|
||||
private V1Deployment getConfigWatcherDeployment() throws Exception {
|
||||
V1Deployment deployment = (V1Deployment) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml");
|
||||
V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath(
|
||||
"config-watcher/spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml");
|
||||
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
|
||||
+ getPomVersion();
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image);
|
||||
return deployment;
|
||||
}
|
||||
|
||||
private void deployWiremock() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getWiremockDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getWiremockAppService(), null, null, null);
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, getWiremockIngress(), null, null, null);
|
||||
}
|
||||
|
||||
private V1Service getConfigWatcherService() throws Exception {
|
||||
return (V1Service) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-service.yaml");
|
||||
return (V1Service) K8SUtils
|
||||
.readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml");
|
||||
}
|
||||
|
||||
private V1ConfigMap getConfigWatcherConfigMap() throws Exception {
|
||||
return (V1ConfigMap) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-configmap.yaml");
|
||||
}
|
||||
|
||||
private V1Ingress getWiremockIngress() throws Exception {
|
||||
return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock-ingress.yaml");
|
||||
}
|
||||
|
||||
private V1Service getWiremockAppService() throws Exception {
|
||||
return (V1Service) K8SUtils.readYamlFromClasspath("wiremock-service.yaml");
|
||||
}
|
||||
|
||||
private V1Deployment getWiremockDeployment() throws Exception {
|
||||
return (V1Deployment) K8SUtils.readYamlFromClasspath("wiremock-deployment.yaml");
|
||||
.readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.configuration.watcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.apis.AppsV1Api;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
|
||||
@@ -28,19 +28,23 @@ import io.kubernetes.client.openapi.models.V1ConfigMapBuilder;
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion;
|
||||
@@ -48,9 +52,7 @@ import static org.springframework.cloud.kubernetes.integration.tests.commons.K8S
|
||||
/**
|
||||
* @author Kris Iyer
|
||||
*/
|
||||
public class ActuatorRefreshKafkaIT {
|
||||
|
||||
private final Log log = LogFactory.getLog(getClass());
|
||||
class ActuatorRefreshKafkaIT {
|
||||
|
||||
private static final String CONFIG_WATCHER_IT_IMAGE = "spring-cloud-kubernetes-configuration-watcher-it";
|
||||
|
||||
@@ -70,23 +72,42 @@ public class ActuatorRefreshKafkaIT {
|
||||
|
||||
private static final String ZOOKEEPER_DEPLOYMENT = "zookeeper";
|
||||
|
||||
private ApiClient client;
|
||||
private static CoreV1Api api;
|
||||
|
||||
private CoreV1Api api;
|
||||
private static AppsV1Api appsApi;
|
||||
|
||||
private AppsV1Api appsApi;
|
||||
private static NetworkingV1Api networkingApi;
|
||||
|
||||
private NetworkingV1Api networkingApi;
|
||||
private static K8SUtils k8SUtils;
|
||||
|
||||
private K8SUtils k8SUtils;
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
|
||||
Commons.validateImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S);
|
||||
Commons.loadImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S);
|
||||
|
||||
Commons.validateImage(CONFIG_WATCHER_IT_IMAGE, K3S);
|
||||
Commons.loadImage(CONFIG_WATCHER_IT_IMAGE, K3S);
|
||||
|
||||
createApiClient(K3S.getKubeConfigYaml());
|
||||
api = new CoreV1Api();
|
||||
appsApi = new AppsV1Api();
|
||||
k8SUtils = new K8SUtils(api, appsApi);
|
||||
networkingApi = new NetworkingV1Api();
|
||||
k8SUtils.setUp(NAMESPACE);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() throws Exception {
|
||||
Commons.cleanUp(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S);
|
||||
Commons.cleanUp(CONFIG_WATCHER_IT_IMAGE, K3S);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
this.client = createApiClient();
|
||||
this.api = new CoreV1Api();
|
||||
this.appsApi = new AppsV1Api();
|
||||
this.networkingApi = new NetworkingV1Api();
|
||||
this.k8SUtils = new K8SUtils(api, appsApi);
|
||||
void setup() throws Exception {
|
||||
|
||||
deployZookeeper();
|
||||
deployKafka();
|
||||
@@ -94,68 +115,23 @@ public class ActuatorRefreshKafkaIT {
|
||||
deployConfigWatcher();
|
||||
|
||||
// Check to make sure the controller deployment is ready
|
||||
k8SUtils.waitForDeployment(ZOOKEEPER_DEPLOYMENT, NAMESPACE);
|
||||
k8SUtils.waitForDeployment(KAFKA_BROKER, NAMESPACE);
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME, NAMESPACE);
|
||||
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();
|
||||
rest.setErrorHandler(new ResponseErrorHandler() {
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
|
||||
log.warn("Received response status code: " + clientHttpResponse.getRawStatusCode());
|
||||
return clientHttpResponse.getRawStatusCode() != 503;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse clientHttpResponse) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
|
||||
// available and we get a 503, we just need to wait a bit
|
||||
await().timeout(Duration.ofSeconds(60)).until(
|
||||
() -> rest.getForEntity("http://localhost:80/it", String.class).getStatusCode().is2xxSuccessful());
|
||||
// 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();
|
||||
waitForDeployment(ZOOKEEPER_DEPLOYMENT);
|
||||
waitForDeployment(KAFKA_BROKER);
|
||||
waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME);
|
||||
waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception {
|
||||
appsApi.deleteNamespacedDeployment(KAFKA_BROKER, NAMESPACE, null, null, null, null, null, null);
|
||||
api.deleteNamespacedService(KAFKA_SERVICE, NAMESPACE, null, null, null, null, null, null);
|
||||
appsApi.deleteNamespacedDeployment(ZOOKEEPER_DEPLOYMENT, NAMESPACE, null, null, null, null, null, null);
|
||||
api.deleteNamespacedService(ZOOKEEPER_SERVICE, NAMESPACE, null, null, null, null, null, null);
|
||||
void after() throws Exception {
|
||||
|
||||
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);
|
||||
cleanUpKafka();
|
||||
cleanUpZookeeper();
|
||||
cleanUpServices();
|
||||
cleanUpDeployments();
|
||||
|
||||
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);
|
||||
cleanUpConfigMaps();
|
||||
|
||||
// Check to make sure the controller deployment is deleted
|
||||
k8SUtils.waitForDeploymentToBeDeleted(KAFKA_BROKER, NAMESPACE);
|
||||
@@ -164,10 +140,39 @@ public class ActuatorRefreshKafkaIT {
|
||||
k8SUtils.waitForDeploymentToBeDeleted(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME, NAMESPACE);
|
||||
}
|
||||
|
||||
@Test
|
||||
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);
|
||||
|
||||
WebClient.Builder builder = builder();
|
||||
WebClient serviceClient = builder.baseUrl("http://localhost:80/it").build();
|
||||
|
||||
Boolean[] value = new Boolean[1];
|
||||
await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> {
|
||||
value[0] = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(Boolean.class).retryWhen(retrySpec())
|
||||
.block();
|
||||
return value[0];
|
||||
});
|
||||
|
||||
Assertions.assertThat(value[0]).isTrue();
|
||||
}
|
||||
|
||||
private void waitForDeployment(String deploymentName) {
|
||||
await().pollInterval(Duration.ofSeconds(3)).atMost(600, TimeUnit.SECONDS)
|
||||
.until(() -> k8SUtils.isDeploymentReady(deploymentName, NAMESPACE));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
V1Ingress ingress = getItIngress();
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null);
|
||||
k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE);
|
||||
}
|
||||
|
||||
private void deployConfigWatcher() throws Exception {
|
||||
@@ -177,24 +182,28 @@ public class ActuatorRefreshKafkaIT {
|
||||
}
|
||||
|
||||
private void deployZookeeper() throws Exception {
|
||||
System.out.println("deploy deployZookeeper");
|
||||
api.createNamespacedService(NAMESPACE, getZookeeperService(), null, null, null);
|
||||
System.out.println("created getZookeeperService");
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getZookeeperDeployment(), null, null, null);
|
||||
System.out.println("created getZookeeperDeployment");
|
||||
}
|
||||
|
||||
private void deployKafka() throws Exception {
|
||||
System.out.println("deploy kafka");
|
||||
api.createNamespacedService(NAMESPACE, getKafkaService(), null, null, null);
|
||||
System.out.println("created getKafkaService");
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getKafkaDeployment(), null, null, null);
|
||||
System.out.println("created getKafkaDeployment");
|
||||
}
|
||||
|
||||
private void cleanUpKafka() throws Exception {
|
||||
appsApi.deleteNamespacedDeployment(KAFKA_BROKER, NAMESPACE, null, null, null, null, null, null);
|
||||
api.deleteNamespacedService(KAFKA_SERVICE, NAMESPACE, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
private void cleanUpZookeeper() throws Exception {
|
||||
appsApi.deleteNamespacedDeployment(ZOOKEEPER_DEPLOYMENT, NAMESPACE, null, null, null, null, null, null);
|
||||
api.deleteNamespacedService(ZOOKEEPER_SERVICE, NAMESPACE, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
private V1Deployment getConfigWatcherDeployment() throws Exception {
|
||||
V1Deployment deployment = (V1Deployment) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml");
|
||||
V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath(
|
||||
"app-watcher/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml");
|
||||
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
|
||||
+ getPomVersion();
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image);
|
||||
@@ -202,7 +211,7 @@ public class ActuatorRefreshKafkaIT {
|
||||
}
|
||||
|
||||
private V1Deployment getItDeployment() throws Exception {
|
||||
String urlString = "spring-cloud-kubernetes-configuration-watcher-it-bus-kafka-deployment.yaml";
|
||||
String urlString = "app/spring-cloud-kubernetes-configuration-watcher-it-bus-kafka-deployment.yaml";
|
||||
V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath(urlString);
|
||||
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
|
||||
+ getPomVersion();
|
||||
@@ -211,38 +220,66 @@ public class ActuatorRefreshKafkaIT {
|
||||
}
|
||||
|
||||
private V1Service getConfigWatcherService() throws Exception {
|
||||
return (V1Service) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-service.yaml");
|
||||
return (V1Service) K8SUtils
|
||||
.readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml");
|
||||
}
|
||||
|
||||
private V1ConfigMap getConfigWatcherConfigMap() throws Exception {
|
||||
return (V1ConfigMap) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-configmap.yaml");
|
||||
.readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml");
|
||||
}
|
||||
|
||||
private V1Service getItAppService() throws Exception {
|
||||
return (V1Service) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-it-service.yaml");
|
||||
.readYamlFromClasspath("app/spring-cloud-kubernetes-configuration-watcher-it-service.yaml");
|
||||
}
|
||||
|
||||
private V1Ingress getItIngress() throws Exception {
|
||||
return (V1Ingress) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml");
|
||||
.readYamlFromClasspath("app/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml");
|
||||
}
|
||||
|
||||
private V1Deployment getKafkaDeployment() throws Exception {
|
||||
return (V1Deployment) K8SUtils.readYamlFromClasspath("kafka-deployment.yaml");
|
||||
return (V1Deployment) K8SUtils.readYamlFromClasspath("kafka/kafka-deployment.yaml");
|
||||
}
|
||||
|
||||
private V1Service getKafkaService() throws Exception {
|
||||
return (V1Service) K8SUtils.readYamlFromClasspath("kafka-service.yaml");
|
||||
return (V1Service) K8SUtils.readYamlFromClasspath("kafka/kafka-service.yaml");
|
||||
}
|
||||
|
||||
private V1Deployment getZookeeperDeployment() throws Exception {
|
||||
return (V1Deployment) K8SUtils.readYamlFromClasspath("zookeeper-deployment.yaml");
|
||||
return (V1Deployment) K8SUtils.readYamlFromClasspath("zookeeper/zookeeper-deployment.yaml");
|
||||
}
|
||||
|
||||
private V1Service getZookeeperService() throws Exception {
|
||||
return (V1Service) K8SUtils.readYamlFromClasspath("zookeeper-service.yaml");
|
||||
return (V1Service) K8SUtils.readYamlFromClasspath("zookeeper/zookeeper-service.yaml");
|
||||
}
|
||||
|
||||
private void cleanUpConfigMaps() throws Exception {
|
||||
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 cleanUpDeployments() throws Exception {
|
||||
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);
|
||||
}
|
||||
|
||||
private void cleanUpServices() throws Exception {
|
||||
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);
|
||||
}
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.configuration.watcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.apis.AppsV1Api;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
|
||||
@@ -29,19 +29,23 @@ import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1ReplicationController;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion;
|
||||
@@ -49,11 +53,7 @@ import static org.springframework.cloud.kubernetes.integration.tests.commons.K8S
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class ActuatorRefreshRabbitMQIT {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(ActuatorRefreshRabbitMQIT.class);
|
||||
|
||||
private Log log = LogFactory.getLog(getClass());
|
||||
class ActuatorRefreshRabbitMQIT {
|
||||
|
||||
private static final String CONFIG_WATCHER_IT_IMAGE = "spring-cloud-kubernetes-configuration-watcher-it";
|
||||
|
||||
@@ -67,23 +67,42 @@ public class ActuatorRefreshRabbitMQIT {
|
||||
|
||||
private static final String RABBIT_MQ_CONTROLLER_NAME = "rabbitmq-controller";
|
||||
|
||||
private ApiClient client;
|
||||
private static CoreV1Api api;
|
||||
|
||||
private CoreV1Api api;
|
||||
private static AppsV1Api appsApi;
|
||||
|
||||
private AppsV1Api appsApi;
|
||||
private static NetworkingV1Api networkingApi;
|
||||
|
||||
private NetworkingV1Api networkingApi;
|
||||
private static K8SUtils k8SUtils;
|
||||
|
||||
private K8SUtils k8SUtils;
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
|
||||
Commons.validateImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S);
|
||||
Commons.loadImage(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S);
|
||||
|
||||
Commons.validateImage(CONFIG_WATCHER_IT_IMAGE, K3S);
|
||||
Commons.loadImage(CONFIG_WATCHER_IT_IMAGE, K3S);
|
||||
|
||||
createApiClient(K3S.getKubeConfigYaml());
|
||||
api = new CoreV1Api();
|
||||
appsApi = new AppsV1Api();
|
||||
k8SUtils = new K8SUtils(api, appsApi);
|
||||
networkingApi = new NetworkingV1Api();
|
||||
k8SUtils.setUp(NAMESPACE);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() throws Exception {
|
||||
Commons.cleanUp(SPRING_CLOUD_K8S_CONFIG_WATCHER_APP_NAME, K3S);
|
||||
Commons.cleanUp(CONFIG_WATCHER_IT_IMAGE, K3S);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
this.client = createApiClient();
|
||||
this.api = new CoreV1Api();
|
||||
this.appsApi = new AppsV1Api();
|
||||
this.networkingApi = new NetworkingV1Api();
|
||||
this.k8SUtils = new K8SUtils(api, appsApi);
|
||||
void setup() throws Exception {
|
||||
|
||||
deployRabbitMQ();
|
||||
deployTestApp();
|
||||
@@ -91,48 +110,33 @@ public class ActuatorRefreshRabbitMQIT {
|
||||
|
||||
// Check to make sure the controller deployment is ready
|
||||
k8SUtils.waitForReplicationController(RABBIT_MQ_CONTROLLER_NAME, NAMESPACE);
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME, NAMESPACE);
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME, NAMESPACE);
|
||||
waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_IT_DEPLOYMENT_NAME);
|
||||
waitForDeployment(SPRING_CLOUD_K8S_CONFIG_WATCHER_DEPLOYMENT_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefresh() throws Exception {
|
||||
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();
|
||||
rest.setErrorHandler(new ResponseErrorHandler() {
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
|
||||
LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode());
|
||||
return clientHttpResponse.getRawStatusCode() != 503;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse clientHttpResponse) {
|
||||
WebClient.Builder builder = builder();
|
||||
WebClient serviceClient = builder.baseUrl("http://localhost:80/it").build();
|
||||
|
||||
}
|
||||
Boolean[] value = new Boolean[1];
|
||||
await().pollInterval(Duration.ofSeconds(3)).atMost(Duration.ofSeconds(90)).until(() -> {
|
||||
value[0] = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(Boolean.class).retryWhen(retrySpec())
|
||||
.block();
|
||||
return value[0];
|
||||
});
|
||||
|
||||
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
|
||||
// available and we get a 503, we just need to wait a bit
|
||||
await().timeout(Duration.ofSeconds(60)).until(
|
||||
() -> rest.getForEntity("http://localhost:80/it", String.class).getStatusCode().is2xxSuccessful());
|
||||
|
||||
// Wait a bit before we verify
|
||||
await().pollInterval(Duration.ofSeconds(1)).atMost(Duration.ofSeconds(120)).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();
|
||||
Assertions.assertThat(value[0]).isTrue();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception {
|
||||
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,
|
||||
@@ -148,10 +152,8 @@ public class ActuatorRefreshRabbitMQIT {
|
||||
null, null);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// swallowing this exception, the delete does actually happen, its a problem
|
||||
// downstream from the k8s
|
||||
// client
|
||||
// see
|
||||
// swallowing this exception, delete does actually happen, it's a problem
|
||||
// downstream from the k8s client; see:
|
||||
// https://github.com/kubernetes-client/java/issues/86#issuecomment-411234259
|
||||
}
|
||||
|
||||
@@ -170,7 +172,10 @@ public class ActuatorRefreshRabbitMQIT {
|
||||
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);
|
||||
|
||||
V1Ingress ingress = getItIngress();
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null);
|
||||
k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE);
|
||||
}
|
||||
|
||||
private void deployConfigWatcher() throws Exception {
|
||||
@@ -185,8 +190,8 @@ public class ActuatorRefreshRabbitMQIT {
|
||||
}
|
||||
|
||||
private V1Deployment getConfigWatcherDeployment() throws Exception {
|
||||
V1Deployment deployment = (V1Deployment) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-bus-amqp-deployment.yaml");
|
||||
V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath(
|
||||
"app-watcher/spring-cloud-kubernetes-configuration-watcher-bus-amqp-deployment.yaml");
|
||||
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
|
||||
+ getPomVersion();
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image);
|
||||
@@ -194,7 +199,7 @@ public class ActuatorRefreshRabbitMQIT {
|
||||
}
|
||||
|
||||
private V1Deployment getItDeployment() throws Exception {
|
||||
String urlString = "spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml";
|
||||
String urlString = "app-watcher/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml";
|
||||
V1Deployment deployment = (V1Deployment) K8SUtils.readYamlFromClasspath(urlString);
|
||||
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
|
||||
+ getPomVersion();
|
||||
@@ -204,29 +209,43 @@ public class ActuatorRefreshRabbitMQIT {
|
||||
|
||||
private V1Service getItAppService() throws Exception {
|
||||
return (V1Service) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-it-service.yaml");
|
||||
.readYamlFromClasspath("app/spring-cloud-kubernetes-configuration-watcher-it-service.yaml");
|
||||
}
|
||||
|
||||
private V1Service getConfigWatcherService() throws Exception {
|
||||
return (V1Service) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-service.yaml");
|
||||
return (V1Service) K8SUtils
|
||||
.readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml");
|
||||
}
|
||||
|
||||
private V1ConfigMap getConfigWatcherConfigMap() throws Exception {
|
||||
return (V1ConfigMap) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-configmap.yaml");
|
||||
.readYamlFromClasspath("config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml");
|
||||
}
|
||||
|
||||
private V1Ingress getItIngress() throws Exception {
|
||||
return (V1Ingress) K8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml");
|
||||
.readYamlFromClasspath("app/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml");
|
||||
}
|
||||
|
||||
private V1ReplicationController getRabbitMQReplicationController() throws Exception {
|
||||
return (V1ReplicationController) K8SUtils.readYamlFromClasspath("rabbitmq-controller.yaml");
|
||||
return (V1ReplicationController) K8SUtils.readYamlFromClasspath("rabbitmq/rabbitmq-controller.yaml");
|
||||
}
|
||||
|
||||
private V1Service getRabbitMQService() throws Exception {
|
||||
return (V1Service) K8SUtils.readYamlFromClasspath("rabbitmq-service.yaml");
|
||||
return (V1Service) K8SUtils.readYamlFromClasspath("rabbitmq/rabbitmq-service.yaml");
|
||||
}
|
||||
|
||||
private void waitForDeployment(String deploymentName) {
|
||||
await().pollInterval(Duration.ofSeconds(3)).atMost(600, TimeUnit.SECONDS)
|
||||
.until(() -> k8SUtils.isDeploymentReady(deploymentName, NAMESPACE));
|
||||
}
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,13 +3,11 @@ kind: Ingress
|
||||
metadata:
|
||||
name: it-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /it(/|$)(.*)
|
||||
- path: /it
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -1,27 +0,0 @@
|
||||
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:2.27.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
path: /__admin/mappings
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
path: /__admin/mappings
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
@@ -1,14 +0,0 @@
|
||||
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
|
||||
@@ -59,6 +59,55 @@
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<!-- build image in the 'package' phase, and ignore plain tests -->
|
||||
<!-- via maven-surefire-plugin::skipTests -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- run tests in the 'integration-tests' phase, one that is after 'package' (where we build the image) -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -16,40 +16,40 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.core.k8s.it;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.apis.AppsV1Api;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class ActuatorEndpointIT {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(ActuatorEndpointIT.class);
|
||||
class ActuatorEndpointIT {
|
||||
|
||||
private static final String SPRING_CLOUD_K8S_CLIENT_IT_DEPLOYMENT_NAME = "spring-cloud-kubernetes-core-k8s-client-it-deployment";
|
||||
|
||||
@@ -59,23 +59,27 @@ public class ActuatorEndpointIT {
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private static ApiClient client;
|
||||
|
||||
private static CoreV1Api api;
|
||||
|
||||
private static AppsV1Api appsApi;
|
||||
|
||||
private static NetworkingV1Api networkingApi;
|
||||
|
||||
private static K8SUtils k8SUtils;
|
||||
|
||||
private static NetworkingV1Api networkingApi;
|
||||
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws Exception {
|
||||
client = createApiClient();
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
Commons.validateImage(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S);
|
||||
Commons.loadImage(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S);
|
||||
createApiClient(K3S.getKubeConfigYaml());
|
||||
api = new CoreV1Api();
|
||||
appsApi = new AppsV1Api();
|
||||
networkingApi = new NetworkingV1Api();
|
||||
k8SUtils = new K8SUtils(api, appsApi);
|
||||
k8SUtils.setUp(NAMESPACE);
|
||||
|
||||
deployCoreK8sClientIt();
|
||||
|
||||
@@ -84,7 +88,8 @@ public class ActuatorEndpointIT {
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void after() throws Exception {
|
||||
static void after() throws Exception {
|
||||
Commons.cleanUp(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, K3S);
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + K8S_CONFIG_CLIENT_IT_NAME, null, null, null, null, null, null, null, null, null);
|
||||
api.deleteNamespacedService(K8S_CONFIG_CLIENT_IT_SERVICE_NAME, NAMESPACE, null, null, null, null, null, null);
|
||||
@@ -92,31 +97,18 @@ public class ActuatorEndpointIT {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHealth() {
|
||||
RestTemplate rest = new RestTemplateBuilder().build();
|
||||
@SuppressWarnings("unchecked")
|
||||
void testHealth() {
|
||||
|
||||
rest.setErrorHandler(new ResponseErrorHandler() {
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
|
||||
LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode());
|
||||
return clientHttpResponse.getRawStatusCode() != 503;
|
||||
}
|
||||
WebClient.Builder builder = builder();
|
||||
WebClient serviceClient = builder.baseUrl("http://localhost:80/actuator/health").build();
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
|
||||
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> health = (Map<String, Object>) serviceClient.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec())
|
||||
.block();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
|
||||
// available and we get a 503, we just need to wait a bit
|
||||
await().timeout(Duration.ofSeconds(60))
|
||||
.until(() -> rest.getForEntity("http://localhost:80/core-k8s-client-it/actuator/health", String.class)
|
||||
.getStatusCode().is2xxSuccessful());
|
||||
LOG.debug("Response from /health endpoint: "
|
||||
+ rest.getForEntity("http://localhost:80/core-k8s-client-it/actuator/health", String.class));
|
||||
Map<String, Object> health = rest.getForObject("http://localhost:80/core-k8s-client-it/actuator/health",
|
||||
Map.class);
|
||||
Map<String, Object> components = (Map<String, Object>) health.get("components");
|
||||
assertThat(components.containsKey("kubernetes")).isTrue();
|
||||
Map<String, Object> kubernetes = (Map<String, Object>) components.get("kubernetes");
|
||||
@@ -132,35 +124,24 @@ public class ActuatorEndpointIT {
|
||||
assertThat(details.containsKey("serviceAccount")).isTrue();
|
||||
|
||||
assertThat(components.containsKey("discoveryComposite")).isTrue();
|
||||
Map<String, Object> discoveryComposite = (Map) components.get("discoveryComposite");
|
||||
Map<String, Object> discoveryComposite = (Map<String, Object>) components.get("discoveryComposite");
|
||||
assertThat(discoveryComposite.get("status")).isEqualTo("UP");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInfo() {
|
||||
RestTemplate rest = new RestTemplateBuilder().build();
|
||||
@SuppressWarnings("unchecked")
|
||||
void testInfo() {
|
||||
|
||||
rest.setErrorHandler(new ResponseErrorHandler() {
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
|
||||
LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode());
|
||||
return clientHttpResponse.getRawStatusCode() != 503;
|
||||
}
|
||||
WebClient.Builder builder = builder();
|
||||
WebClient serviceClient = builder.baseUrl("http://localhost:80/actuator/info").build();
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse clientHttpResponse) {
|
||||
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> info = (Map<String, Object>) serviceClient.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec())
|
||||
.block();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
|
||||
// available and we get a 503, we just need to wait a bit
|
||||
await().timeout(Duration.ofSeconds(60))
|
||||
.until(() -> rest.getForEntity("http://localhost:80/core-k8s-client-it/actuator/info", String.class)
|
||||
.getStatusCode().is2xxSuccessful());
|
||||
LOG.debug("Response from /info endpoint: "
|
||||
+ rest.getForEntity("http://localhost:80/core-k8s-client-it/actuator/info", String.class));
|
||||
Map<String, Object> info = rest.getForObject("http://localhost:80/core-k8s-client-it/actuator/info", Map.class);
|
||||
Map<String, Object> kubernetes = (Map<String, Object>) info.get("kubernetes");
|
||||
assertThat(kubernetes.containsKey("hostIp")).isTrue();
|
||||
assertThat(kubernetes.containsKey("inside")).isTrue();
|
||||
@@ -174,7 +155,10 @@ public class ActuatorEndpointIT {
|
||||
private static void deployCoreK8sClientIt() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getCoreK8sClientItDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getCoreK8sClientItService(), null, null, null);
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, getCoreK8sClientItIngress(), null, null, null);
|
||||
|
||||
V1Ingress ingress = getCoreK8sClientItIngress();
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null);
|
||||
k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE);
|
||||
}
|
||||
|
||||
private static V1Deployment getCoreK8sClientItDeployment() throws Exception {
|
||||
@@ -194,4 +178,12 @@ public class ActuatorEndpointIT {
|
||||
return (V1Ingress) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-core-k8s-client-it-ingress.yaml");
|
||||
}
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -3,13 +3,11 @@ kind: Ingress
|
||||
metadata:
|
||||
name: it-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /core-k8s-client-it(/|$)(.*)
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-cloud-kubernetes-integration-tests</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-discoverclient-it</artifactId>
|
||||
|
||||
<properties>
|
||||
<jib.version>1.8.0</jib.version>
|
||||
<base.image>openjdk:8u222-slim</base.image>
|
||||
</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-kubernetes-discoveryclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.kubernetes</groupId>
|
||||
<artifactId>client-java-extended</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-transport-httpclient5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>../src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>skaffold</id>
|
||||
<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>
|
||||
</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>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
@@ -1,13 +0,0 @@
|
||||
spring:
|
||||
cloud:
|
||||
kubernetes:
|
||||
discovery:
|
||||
discoveryServerUrl: http://spring-cloud-kubernetes-discoveryserver
|
||||
management:
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "*"
|
||||
@@ -1,226 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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.discoveryclient.it;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiClient;
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import io.kubernetes.client.openapi.apis.AppsV1Api;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class DiscoveryClientIT {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(DiscoveryClientIT.class);
|
||||
|
||||
private static final String DISCOVERYSERVER_DEPLOYMENT_NAME = "spring-cloud-kubernetes-discoveryserver-deployment";
|
||||
|
||||
private static final String DISCOVERYSERVER_APP_NAME = "spring-cloud-kubernetes-discoveryserver";
|
||||
|
||||
private static final String SPRING_CLOUD_K8S_DISCOVERYCLIENT_DEPLOYMENT_NAME = "spring-cloud-kubernetes-discoveryclient-it-deployment";
|
||||
|
||||
private static final String SPRING_CLOUD_K8S_DISCOVERYCLIENT_APP_NAME = "spring-cloud-kubernetes-discoveryclient-it";
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private static ApiClient client;
|
||||
|
||||
private static CoreV1Api api;
|
||||
|
||||
private static AppsV1Api appsApi;
|
||||
|
||||
private static NetworkingV1Api networkingApi;
|
||||
|
||||
private static K8SUtils k8SUtils;
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws Exception {
|
||||
client = createApiClient();
|
||||
api = new CoreV1Api();
|
||||
appsApi = new AppsV1Api();
|
||||
networkingApi = new NetworkingV1Api();
|
||||
k8SUtils = new K8SUtils(api, appsApi);
|
||||
|
||||
deployDiscoveryServer();
|
||||
|
||||
// Check to make sure the discovery server deployment is ready
|
||||
k8SUtils.waitForDeployment(DISCOVERYSERVER_DEPLOYMENT_NAME, NAMESPACE);
|
||||
|
||||
// Check to see if endpoint is ready
|
||||
k8SUtils.waitForEndpointReady(DISCOVERYSERVER_APP_NAME, NAMESPACE);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiscoveryClient() throws Exception {
|
||||
try {
|
||||
deployDiscoveryIt();
|
||||
testLoadBalancer();
|
||||
testHealth();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanup() throws ApiException {
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + SPRING_CLOUD_K8S_DISCOVERYCLIENT_DEPLOYMENT_NAME, null, null, null, null, null, null,
|
||||
null, null, null);
|
||||
api.deleteNamespacedService(SPRING_CLOUD_K8S_DISCOVERYCLIENT_APP_NAME, NAMESPACE, null, null, null, null, null,
|
||||
null);
|
||||
networkingApi.deleteNamespacedIngress("it-ingress", NAMESPACE, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
private void testLoadBalancer() throws Exception {
|
||||
// Check to make sure the controller deployment is ready
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_DISCOVERYCLIENT_DEPLOYMENT_NAME, NAMESPACE);
|
||||
RestTemplate rest = createRestTemplate();
|
||||
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
|
||||
// available and we get a 503, we just need to wait a bit
|
||||
await().timeout(Duration.ofSeconds(60))
|
||||
.until(() -> rest.getForEntity("http://localhost:80/discoveryclient-it/services", String.class)
|
||||
.getStatusCode().is2xxSuccessful());
|
||||
String[] result = rest.getForObject("http://localhost:80/discoveryclient-it/services", String[].class);
|
||||
LOG.info("Services: " + Arrays.toString(result));
|
||||
assertThat(Arrays.stream(result).anyMatch(s -> "spring-cloud-kubernetes-discoveryserver".equalsIgnoreCase(s)))
|
||||
.isTrue();
|
||||
|
||||
}
|
||||
|
||||
private RestTemplate createRestTemplate() {
|
||||
RestTemplate rest = new RestTemplateBuilder().build();
|
||||
|
||||
rest.setErrorHandler(new ResponseErrorHandler() {
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
|
||||
LOG.warn("Received response status code: " + clientHttpResponse.getRawStatusCode());
|
||||
return clientHttpResponse.getRawStatusCode() != 503;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse clientHttpResponse) {
|
||||
|
||||
}
|
||||
});
|
||||
return rest;
|
||||
}
|
||||
|
||||
public void testHealth() {
|
||||
RestTemplate rest = createRestTemplate();
|
||||
|
||||
// Sometimes the NGINX ingress takes a bit to catch up and realize the service is
|
||||
// available and we get a 503, we just need to wait a bit
|
||||
await().timeout(Duration.ofSeconds(60))
|
||||
.until(() -> rest.getForEntity("http://localhost:80/discoveryclient-it/actuator/health", String.class)
|
||||
.getStatusCode().is2xxSuccessful());
|
||||
|
||||
Map<String, Object> health = rest.getForObject("http://localhost:80/discoveryclient-it/actuator/health",
|
||||
Map.class);
|
||||
Map<String, Object> components = (Map<String, Object>) health.get("components");
|
||||
|
||||
Map<String, Object> discoveryComposite = (Map<String, Object>) components.get("discoveryComposite");
|
||||
assertThat(discoveryComposite.get("status")).isEqualTo("UP");
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void after() throws Exception {
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + DISCOVERYSERVER_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null,
|
||||
null);
|
||||
|
||||
api.deleteNamespacedService(DISCOVERYSERVER_APP_NAME, NAMESPACE, null, null, null, null, null, null);
|
||||
networkingApi.deleteNamespacedIngress("discoveryserver-ingress", NAMESPACE, null, null, null, null, null, null);
|
||||
|
||||
}
|
||||
|
||||
private void deployDiscoveryIt() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getDiscoveryItDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getDiscoveryService(), null, null, null);
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, getDiscoveryItIngress(), null, null, null);
|
||||
}
|
||||
|
||||
private V1Deployment getDiscoveryItDeployment() throws Exception {
|
||||
V1Deployment deployment = (V1Deployment) k8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-discoveryclient-it-deployment.yaml");
|
||||
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
|
||||
+ getPomVersion();
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image);
|
||||
return deployment;
|
||||
}
|
||||
|
||||
private static void deployDiscoveryServer() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getDiscoveryServerDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getDiscoveryServerService(), null, null, null);
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, getDiscoveryServerIngress(), null, null, null);
|
||||
}
|
||||
|
||||
private static V1Deployment getDiscoveryServerDeployment() throws Exception {
|
||||
V1Deployment deployment = (V1Deployment) k8SUtils
|
||||
.readYamlFromClasspath("spring-cloud-kubernetes-discoveryserver-deployment.yaml");
|
||||
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
|
||||
+ getPomVersion();
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image);
|
||||
return deployment;
|
||||
}
|
||||
|
||||
private static V1Ingress getDiscoveryServerIngress() throws Exception {
|
||||
return (V1Ingress) k8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryserver-ingress.yaml");
|
||||
}
|
||||
|
||||
private static V1Service getDiscoveryServerService() throws Exception {
|
||||
return (V1Service) k8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryserver-service.yaml");
|
||||
}
|
||||
|
||||
private V1Ingress getDiscoveryItIngress() throws Exception {
|
||||
return (V1Ingress) k8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryclient-it-ingress.yaml");
|
||||
}
|
||||
|
||||
private V1Service getDiscoveryService() throws Exception {
|
||||
return (V1Service) k8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryclient-it-service.yaml");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: it-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /discoveryclient-it(/|$)(.*)
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: spring-cloud-kubernetes-discoveryclient-it
|
||||
port:
|
||||
number: 8080
|
||||
@@ -1,18 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: discoveryserver-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /discoveryserver(/|$)(.*)
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: spring-cloud-kubernetes-discoveryserver
|
||||
port:
|
||||
number: 80
|
||||
@@ -9,12 +9,12 @@
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-kubernetes-reactive-discoveryclient-it</artifactId>
|
||||
<artifactId>spring-cloud-kubernetes-discoveryclient-it</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
@@ -46,6 +46,12 @@
|
||||
<artifactId>docker-java-transport-httpclient5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -59,6 +65,55 @@
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<!-- build image in the 'package' phase, and ignore plain tests -->
|
||||
<!-- via maven-surefire-plugin::skipTests -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- run tests in the 'integration-tests' phase, one that is after 'package' (where we build the image) -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
@@ -1,3 +1,7 @@
|
||||
server:
|
||||
servlet:
|
||||
context-path: /discoveryclient-it
|
||||
|
||||
spring:
|
||||
cloud:
|
||||
kubernetes:
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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.discoveryclient.it;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.kubernetes.client.openapi.ApiException;
|
||||
import io.kubernetes.client.openapi.apis.AppsV1Api;
|
||||
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||
import io.kubernetes.client.openapi.apis.NetworkingV1Api;
|
||||
import io.kubernetes.client.openapi.models.V1Deployment;
|
||||
import io.kubernetes.client.openapi.models.V1Ingress;
|
||||
import io.kubernetes.client.openapi.models.V1Service;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.createApiClient;
|
||||
import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
class DiscoveryClientIT {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(DiscoveryClientIT.class);
|
||||
|
||||
private static final String DISCOVERY_SERVER_DEPLOYMENT_NAME = "spring-cloud-kubernetes-discoveryserver-deployment";
|
||||
|
||||
private static final String DISCOVERY_SERVER_APP_NAME = "spring-cloud-kubernetes-discoveryserver";
|
||||
|
||||
private static final String SPRING_CLOUD_K8S_DISCOVERY_CLIENT_DEPLOYMENT_NAME = "spring-cloud-kubernetes-discoveryclient-it-deployment";
|
||||
|
||||
private static final String SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME = "spring-cloud-kubernetes-discoveryclient-it";
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private static CoreV1Api api;
|
||||
|
||||
private static AppsV1Api appsApi;
|
||||
|
||||
private static NetworkingV1Api networkingApi;
|
||||
|
||||
private static K8SUtils k8SUtils;
|
||||
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
|
||||
Commons.validateImage(DISCOVERY_SERVER_APP_NAME, K3S);
|
||||
Commons.loadImage(DISCOVERY_SERVER_APP_NAME, K3S);
|
||||
|
||||
Commons.validateImage(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, K3S);
|
||||
Commons.loadImage(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, K3S);
|
||||
|
||||
createApiClient(K3S.getKubeConfigYaml());
|
||||
api = new CoreV1Api();
|
||||
appsApi = new AppsV1Api();
|
||||
networkingApi = new NetworkingV1Api();
|
||||
k8SUtils = new K8SUtils(api, appsApi);
|
||||
k8SUtils.setUp(NAMESPACE);
|
||||
|
||||
deployDiscoveryServer();
|
||||
|
||||
// Check to make sure the discovery server deployment is ready
|
||||
k8SUtils.waitForDeployment(DISCOVERY_SERVER_DEPLOYMENT_NAME, NAMESPACE);
|
||||
|
||||
// Check to see if endpoint is ready
|
||||
k8SUtils.waitForEndpointReady(DISCOVERY_SERVER_APP_NAME, NAMESPACE);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() throws Exception {
|
||||
Commons.cleanUp(DISCOVERY_SERVER_APP_NAME, K3S);
|
||||
Commons.cleanUp(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, K3S);
|
||||
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + DISCOVERY_SERVER_DEPLOYMENT_NAME, null, null, null, null, null, null, null, null,
|
||||
null);
|
||||
|
||||
api.deleteNamespacedService(DISCOVERY_SERVER_APP_NAME, NAMESPACE, null, null, null, null, null, null);
|
||||
networkingApi.deleteNamespacedIngress("discoveryserver-ingress", NAMESPACE, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach() throws ApiException {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDiscoveryClient() throws Exception {
|
||||
deployDiscoveryIt();
|
||||
testLoadBalancer();
|
||||
testHealth();
|
||||
}
|
||||
|
||||
private void cleanup() throws ApiException {
|
||||
appsApi.deleteCollectionNamespacedDeployment(NAMESPACE, null, null, null,
|
||||
"metadata.name=" + SPRING_CLOUD_K8S_DISCOVERY_CLIENT_DEPLOYMENT_NAME, null, null, null, null, null,
|
||||
null, null, null, null);
|
||||
api.deleteNamespacedService(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_APP_NAME, NAMESPACE, null, null, null, null, null,
|
||||
null);
|
||||
networkingApi.deleteNamespacedIngress("it-ingress", NAMESPACE, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
private void testLoadBalancer() {
|
||||
|
||||
// Check to make sure the controller deployment is ready
|
||||
k8SUtils.waitForDeployment(SPRING_CLOUD_K8S_DISCOVERY_CLIENT_DEPLOYMENT_NAME, NAMESPACE);
|
||||
WebClient.Builder builder = builder();
|
||||
WebClient serviceClient = builder.baseUrl("http://localhost:80/discoveryclient-it/services").build();
|
||||
|
||||
String[] result = serviceClient.method(HttpMethod.GET).retrieve().bodyToMono(String[].class)
|
||||
.retryWhen(retrySpec()).block();
|
||||
LOG.info("Services: " + Arrays.toString(result));
|
||||
assertThat(Arrays.stream(result).anyMatch("spring-cloud-kubernetes-discoveryserver"::equalsIgnoreCase))
|
||||
.isTrue();
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void testHealth() {
|
||||
WebClient.Builder builder = builder();
|
||||
WebClient serviceClient = builder.baseUrl("http://localhost:80/discoveryclient-it/actuator/health").build();
|
||||
|
||||
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(Map.class, String.class, Object.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> health = (Map<String, Object>) serviceClient.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec())
|
||||
.block();
|
||||
|
||||
Map<String, Object> components = (Map<String, Object>) health.get("components");
|
||||
|
||||
Map<String, Object> discoveryComposite = (Map<String, Object>) components.get("discoveryComposite");
|
||||
assertThat(discoveryComposite.get("status")).isEqualTo("UP");
|
||||
}
|
||||
|
||||
private void deployDiscoveryIt() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getDiscoveryItDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getDiscoveryService(), null, null, null);
|
||||
|
||||
V1Ingress ingress = getDiscoveryItIngress();
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null);
|
||||
k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE);
|
||||
}
|
||||
|
||||
private V1Deployment getDiscoveryItDeployment() throws Exception {
|
||||
V1Deployment deployment = (V1Deployment) k8SUtils
|
||||
.readYamlFromClasspath("client/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml");
|
||||
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
|
||||
+ getPomVersion();
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image);
|
||||
return deployment;
|
||||
}
|
||||
|
||||
private static void deployDiscoveryServer() throws Exception {
|
||||
appsApi.createNamespacedDeployment(NAMESPACE, getDiscoveryServerDeployment(), null, null, null);
|
||||
api.createNamespacedService(NAMESPACE, getDiscoveryServerService(), null, null, null);
|
||||
|
||||
V1Ingress ingress = getDiscoveryServerIngress();
|
||||
networkingApi.createNamespacedIngress(NAMESPACE, ingress, null, null, null);
|
||||
k8SUtils.waitForIngress(ingress.getMetadata().getName(), NAMESPACE);
|
||||
}
|
||||
|
||||
private static V1Deployment getDiscoveryServerDeployment() throws Exception {
|
||||
V1Deployment deployment = (V1Deployment) k8SUtils
|
||||
.readYamlFromClasspath("server/spring-cloud-kubernetes-discoveryserver-deployment.yaml");
|
||||
String image = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + ":"
|
||||
+ getPomVersion();
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setImage(image);
|
||||
return deployment;
|
||||
}
|
||||
|
||||
private static V1Ingress getDiscoveryServerIngress() throws Exception {
|
||||
return (V1Ingress) K8SUtils
|
||||
.readYamlFromClasspath("server/spring-cloud-kubernetes-discoveryserver-ingress.yaml");
|
||||
}
|
||||
|
||||
private static V1Service getDiscoveryServerService() throws Exception {
|
||||
return (V1Service) K8SUtils
|
||||
.readYamlFromClasspath("server/spring-cloud-kubernetes-discoveryserver-service.yaml");
|
||||
}
|
||||
|
||||
private V1Ingress getDiscoveryItIngress() throws Exception {
|
||||
return (V1Ingress) K8SUtils
|
||||
.readYamlFromClasspath("client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml");
|
||||
}
|
||||
|
||||
private V1Service getDiscoveryService() throws Exception {
|
||||
return (V1Service) K8SUtils
|
||||
.readYamlFromClasspath("client/spring-cloud-kubernetes-discoveryclient-it-service.yaml");
|
||||
}
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,10 +19,10 @@ spec:
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
path: /actuator/health/readiness
|
||||
path: /discoveryclient-it/actuator/health/readiness
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
port: 8080
|
||||
path: /actuator/health/liveness
|
||||
path: /discoveryclient-it/actuator/health/liveness
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
@@ -3,13 +3,11 @@ kind: Ingress
|
||||
metadata:
|
||||
name: it-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /discoveryclient-it(/|$)(.*)
|
||||
- path: /discoveryclient-it
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -3,13 +3,11 @@ kind: Ingress
|
||||
metadata:
|
||||
name: discoveryserver-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /discoveryserver(/|$)(.*)
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
@@ -16,10 +16,6 @@
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-kubernetes-fabric8-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-kubernetes-test-support</artifactId>
|
||||
@@ -55,6 +51,55 @@
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<!-- build image in the 'package' phase, and ignore plain tests -->
|
||||
<!-- via maven-surefire-plugin::skipTests -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- run tests in the 'integration-tests' phase, one that is after 'package' (where we build the image) -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
spring:
|
||||
application:
|
||||
name: my-configmap
|
||||
config:
|
||||
import: "kubernetes:"
|
||||
@@ -1,7 +0,0 @@
|
||||
spring:
|
||||
cloud:
|
||||
kubernetes:
|
||||
config:
|
||||
sources:
|
||||
- name: my-configmap
|
||||
namespace: default
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.configmap;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.ConfigMap;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
@@ -30,17 +31,21 @@ import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Fabric8Utils;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
|
||||
public class Fabric8ConfigMapIT {
|
||||
class Fabric8ConfigMapIT {
|
||||
|
||||
private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-configmap";
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
@@ -54,26 +59,30 @@ public class Fabric8ConfigMapIT {
|
||||
|
||||
private static String configMapName;
|
||||
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() {
|
||||
Config config = Config.autoConfigure(null);
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
Commons.validateImage(IMAGE_NAME, K3S);
|
||||
Commons.loadImage(IMAGE_NAME, K3S);
|
||||
Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml());
|
||||
client = new DefaultKubernetesClient(config);
|
||||
Fabric8Utils.setUp(client, NAMESPACE);
|
||||
deployManifests();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void after() {
|
||||
static void after() throws Exception {
|
||||
deleteManifests();
|
||||
Commons.cleanUp(IMAGE_NAME, K3S);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()))
|
||||
.baseUrl("localhost/fabric8-configmap/key1").build();
|
||||
void test() {
|
||||
WebClient client = builder().baseUrl("localhost/key1").build();
|
||||
|
||||
String result = client.method(HttpMethod.GET).retrieve().bodyToMono(String.class)
|
||||
.retryWhen(Retry.fixedDelay(15, Duration.ofSeconds(1))
|
||||
.filter(x -> ((WebClientResponseException) x).getStatusCode().value() == 503))
|
||||
String result = client.method(HttpMethod.GET).retrieve().bodyToMono(String.class).retryWhen(retrySpec())
|
||||
.block();
|
||||
|
||||
Assertions.assertEquals("value1", result);
|
||||
@@ -130,20 +139,28 @@ public class Fabric8ConfigMapIT {
|
||||
|
||||
}
|
||||
|
||||
private static FileInputStream getService() throws Exception {
|
||||
private static InputStream getService() {
|
||||
return Fabric8Utils.inputStream("fabric8-service.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getDeployment() throws Exception {
|
||||
private static InputStream getDeployment() {
|
||||
return Fabric8Utils.inputStream("fabric8-deployment.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getIngress() throws Exception {
|
||||
private static InputStream getIngress() {
|
||||
return Fabric8Utils.inputStream("fabric8-ingress.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getConfigMap() throws Exception {
|
||||
private static InputStream getConfigMap() {
|
||||
return Fabric8Utils.inputStream("fabric8-configmap.yaml");
|
||||
}
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,13 +3,11 @@ kind: Ingress
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-fabric8-client-configmap-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /fabric8-configmap(/|$)(.*)
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -51,6 +51,54 @@
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<!-- build image in the 'package' phase, and ignore plain tests -->
|
||||
<!-- via maven-surefire-plugin::skipTests -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- run tests in the 'integration-tests' phase, one that is after 'package' (where we build the image) -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.configmap;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.apps.Deployment;
|
||||
@@ -30,23 +31,27 @@ import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Fabric8Utils;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
public class Fabric8DiscoveryIT {
|
||||
class Fabric8DiscoveryIT {
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery";
|
||||
|
||||
private static KubernetesClient client;
|
||||
|
||||
private static String deploymentName;
|
||||
@@ -59,30 +64,38 @@ public class Fabric8DiscoveryIT {
|
||||
|
||||
private static String mockDeploymentName;
|
||||
|
||||
private static String mockDeploymentImage;
|
||||
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() {
|
||||
Config config = Config.autoConfigure(null);
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
Commons.validateImage(IMAGE_NAME, K3S);
|
||||
Commons.loadImage(IMAGE_NAME, K3S);
|
||||
|
||||
Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml());
|
||||
client = new DefaultKubernetesClient(config);
|
||||
Fabric8Utils.setUp(client, NAMESPACE);
|
||||
|
||||
deployManifests();
|
||||
deployMockManifests();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void after() {
|
||||
static void after() throws Exception {
|
||||
deleteManifests();
|
||||
Commons.cleanUp(IMAGE_NAME, K3S);
|
||||
Commons.cleanUpDownloadedImage(mockDeploymentImage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()))
|
||||
.baseUrl("localhost/fabric8-discovery/services").build();
|
||||
void test() {
|
||||
WebClient client = builder().baseUrl("localhost/services").build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> result = (List<String>) client.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
|
||||
.retryWhen(Retry.fixedDelay(15, Duration.ofSeconds(1))
|
||||
.filter(x -> ((WebClientResponseException) x).getStatusCode().value() == 503))
|
||||
.block();
|
||||
.retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertEquals(result.size(), 3);
|
||||
Assertions.assertTrue(result.contains("kubernetes"));
|
||||
@@ -146,6 +159,7 @@ public class Fabric8DiscoveryIT {
|
||||
Deployment deployment = client.apps().deployments().load(getMockDeployment()).get();
|
||||
client.apps().deployments().inNamespace(NAMESPACE).create(deployment);
|
||||
mockDeploymentName = deployment.getMetadata().getName();
|
||||
mockDeploymentImage = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage();
|
||||
|
||||
Service service = client.services().load(getMockService()).get();
|
||||
mockServiceName = service.getMetadata().getName();
|
||||
@@ -160,24 +174,32 @@ public class Fabric8DiscoveryIT {
|
||||
|
||||
}
|
||||
|
||||
private static FileInputStream getService() throws Exception {
|
||||
private static InputStream getService() {
|
||||
return Fabric8Utils.inputStream("fabric8-discovery-service.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getDeployment() throws Exception {
|
||||
private static InputStream getDeployment() {
|
||||
return Fabric8Utils.inputStream("fabric8-discovery-deployment.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getIngress() throws Exception {
|
||||
private static InputStream getIngress() {
|
||||
return Fabric8Utils.inputStream("fabric8-discovery-ingress.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getMockService() throws Exception {
|
||||
return Fabric8Utils.inputStream("fabric8-discovery-wiremock-service.yaml");
|
||||
private static InputStream getMockService() {
|
||||
return Fabric8Utils.inputStream("wiremock/fabric8-discovery-wiremock-service.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getMockDeployment() throws Exception {
|
||||
return Fabric8Utils.inputStream("fabric8-discovery-wiremock-deployment.yaml");
|
||||
private static InputStream getMockDeployment() {
|
||||
return Fabric8Utils.inputStream("wiremock/fabric8-discovery-wiremock-deployment.yaml");
|
||||
}
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,13 +3,11 @@ kind: Ingress
|
||||
metadata:
|
||||
name: spring-cloud-kubernetes-fabric8-client-discovery-ingress
|
||||
namespace: default
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$2
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /fabric8-discovery(/|$)(.*)
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.testcontainers" level="INFO"/>
|
||||
<logger name="com.github.dockerjava" level="WARN"/>
|
||||
</configuration>
|
||||
@@ -13,7 +13,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: servicea-wiremock
|
||||
image: rodolpheche/wiremock:2.27.2
|
||||
image: wiremock/wiremock:2.32.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
@@ -51,6 +51,55 @@
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<!-- build image in the 'package' phase, and ignore plain tests -->
|
||||
<!-- via maven-surefire-plugin::skipTests -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<imageName>docker.io/springcloud/${project.artifactId}:${project.version}</imageName>
|
||||
<imageBuilder>paketobuildpacks/builder</imageBuilder>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-image</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- ignore plain tests (in the 'test' phase), so that we could build the image first, see above -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- run tests in the 'integration-tests' phase, one that is after 'package' (where we build the image) -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${testsToRun}</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
spring:
|
||||
webflux:
|
||||
base-path: /loadbalancer-it
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
package org.springframework.cloud.kubernetes.client.loadbalancer.it;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.apps.Deployment;
|
||||
@@ -26,18 +27,22 @@ import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
|
||||
import io.fabric8.kubernetes.client.Config;
|
||||
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Fabric8Utils;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -48,6 +53,8 @@ public class Fabric8ClientLoadbalancerIT {
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-loadbalancer";
|
||||
|
||||
private static KubernetesClient client;
|
||||
|
||||
private static String deploymentName;
|
||||
@@ -62,33 +69,44 @@ public class Fabric8ClientLoadbalancerIT {
|
||||
|
||||
private static String mockIngressName;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
Config config = Config.autoConfigure(null);
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
Commons.validateImage(IMAGE_NAME, K3S);
|
||||
Commons.loadImage(IMAGE_NAME, K3S);
|
||||
|
||||
Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml());
|
||||
client = new DefaultKubernetesClient(config);
|
||||
Fabric8Utils.setUp(client, NAMESPACE);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() throws Exception {
|
||||
Commons.cleanUp(IMAGE_NAME, K3S);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
deployMockManifests();
|
||||
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() {
|
||||
void after() {
|
||||
deleteManifests();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadBalancerServiceMode() {
|
||||
void testLoadBalancerServiceMode() {
|
||||
|
||||
deployServiceManifests();
|
||||
|
||||
WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()))
|
||||
.baseUrl("localhost/loadbalancer-it/servicea").build();
|
||||
WebClient client = builder().baseUrl("localhost/loadbalancer-it/servicea").build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> mapResult = (Map<String, String>) client.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(Map.class).retryWhen(Retry.fixedDelay(15, Duration.ofSeconds(1))
|
||||
.filter(x -> ((WebClientResponseException) x).getStatusCode().value() == 503))
|
||||
.block();
|
||||
.bodyToMono(Map.class).retryWhen(retrySpec()).block();
|
||||
|
||||
assertThat(mapResult.containsKey("mappings")).isTrue();
|
||||
assertThat(mapResult.containsKey("meta")).isTrue();
|
||||
@@ -100,14 +118,11 @@ public class Fabric8ClientLoadbalancerIT {
|
||||
|
||||
deployPodManifests();
|
||||
|
||||
WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()))
|
||||
.baseUrl("localhost/loadbalancer-it/servicea").build();
|
||||
WebClient client = builder().baseUrl("localhost/loadbalancer-it/servicea").build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> mapResult = (Map<String, String>) client.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(Map.class).retryWhen(Retry.fixedDelay(15, Duration.ofSeconds(1))
|
||||
.filter(x -> ((WebClientResponseException) x).getStatusCode().value() == 503))
|
||||
.block();
|
||||
.bodyToMono(Map.class).retryWhen(retrySpec()).block();
|
||||
|
||||
assertThat(mapResult.containsKey("mappings")).isTrue();
|
||||
assertThat(mapResult.containsKey("meta")).isTrue();
|
||||
@@ -220,32 +235,40 @@ public class Fabric8ClientLoadbalancerIT {
|
||||
|
||||
}
|
||||
|
||||
private static FileInputStream getIngress() throws Exception {
|
||||
private static InputStream getIngress() {
|
||||
return Fabric8Utils.inputStream("spring-cloud-kubernetes-fabric8-client-loadbalancer-ingress.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getService() throws Exception {
|
||||
private static InputStream getService() {
|
||||
return Fabric8Utils.inputStream("spring-cloud-kubernetes-fabric8-client-loadbalancer-service.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getPodDeployment() throws Exception {
|
||||
private static InputStream getPodDeployment() {
|
||||
return Fabric8Utils.inputStream("spring-cloud-kubernetes-fabric8-client-loadbalancer-pod-deployment.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getServiceDeployment() throws Exception {
|
||||
private static InputStream getServiceDeployment() {
|
||||
return Fabric8Utils.inputStream("spring-cloud-kubernetes-fabric8-client-loadbalancer-service-deployment.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getMockIngress() throws Exception {
|
||||
return Fabric8Utils.inputStream("wiremock-ingress.yaml");
|
||||
private static InputStream getMockIngress() {
|
||||
return Fabric8Utils.inputStream("wiremock/wiremock-ingress.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getMockService() throws Exception {
|
||||
return Fabric8Utils.inputStream("wiremock-service.yaml");
|
||||
private static InputStream getMockService() {
|
||||
return Fabric8Utils.inputStream("wiremock/wiremock-service.yaml");
|
||||
}
|
||||
|
||||
private static FileInputStream getMockDeployment() throws Exception {
|
||||
return Fabric8Utils.inputStream("wiremock-deployment.yaml");
|
||||
private static InputStream getMockDeployment() {
|
||||
return Fabric8Utils.inputStream("wiremock/wiremock-deployment.yaml");
|
||||
}
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(1)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user