move integration tests to test-containers (#984)

This commit is contained in:
erabii
2022-06-08 22:29:00 +03:00
committed by GitHub
parent 0e1d03dfcb
commit 78c507ca33
138 changed files with 2635 additions and 2173 deletions

View File

@@ -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

View File

@@ -1,6 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: istio-test
labels:
istio-injection: enabled

42
.github/workflows/maven.yaml vendored Normal file
View 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/*'

Binary file not shown.

18
pom.xml
View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -3,9 +3,3 @@ management:
web:
exposure:
include: "*"
#logging:
# level:
# org:
# springframework:
# cloud:
# kubernetes: DEBUG

View File

@@ -1,7 +1,6 @@
spring:
cloud:
kubernetes:
enabled: true
secrets:
enable-api: true
reload:

View File

@@ -1,7 +1,4 @@
spring:
application:
name: spring-cloud-kubernetes-client-config-it
cloud:
kubernetes:
enabled: false

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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:

View File

@@ -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>

View File

@@ -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")

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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");
}
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -0,0 +1,3 @@
spring:
application:
name: spring-cloud-kubernetes-configuration-watcher-it

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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:

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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:

View File

@@ -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>

View File

@@ -1,13 +0,0 @@
spring:
cloud:
kubernetes:
discovery:
discoveryServerUrl: http://spring-cloud-kubernetes-discoveryserver
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: "*"

View File

@@ -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");
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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:

View File

@@ -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>

View File

@@ -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:

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
spring:
application:
name: my-configmap
config:
import: "kubernetes:"

View File

@@ -1,7 +0,0 @@
spring:
cloud:
kubernetes:
config:
sources:
- name: my-configmap
namespace: default

View File

@@ -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);
}
}

View File

@@ -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:

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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:

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,3 @@
spring:
webflux:
base-path: /loadbalancer-it

View File

@@ -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