diff --git a/.circleci/config.yml b/.circleci/config.yml index 3a3b866c..5867c49e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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 diff --git a/.circleci/istio-test-namespace.yml b/.circleci/istio-test-namespace.yml deleted file mode 100644 index a9421811..00000000 --- a/.circleci/istio-test-namespace.yml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: istio-test - labels: - istio-injection: enabled \ No newline at end of file diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml new file mode 100644 index 00000000..f1fe0c26 --- /dev/null +++ b/.github/workflows/maven.yaml @@ -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/*' diff --git a/istio-cli/istio-1.13.3/bin/istioctl b/istio-cli/istio-1.13.3/bin/istioctl new file mode 100755 index 00000000..0319896a Binary files /dev/null and b/istio-cli/istio-1.13.3/bin/istioctl differ diff --git a/pom.xml b/pom.xml index 6b558734..ced5fbc1 100644 --- a/pom.xml +++ b/pom.xml @@ -76,14 +76,11 @@ 2.4.12 3.0.2 - **/*IT.java - true true true - **/*IT.java @@ -199,14 +196,9 @@ all false - - ${testsToRun} - - ${excludeITTests} - @@ -359,16 +351,6 @@ all false - - - - ${testsToRun} - - - ${surefireArgLine} - - ${excludeITTests} - diff --git a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application-bus-amqp.yaml b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application-bus-amqp.yaml index eed3c25d..f8da3850 100644 --- a/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application-bus-amqp.yaml +++ b/spring-cloud-kubernetes-controllers/spring-cloud-kubernetes-configuration-watcher/src/main/resources/application-bus-amqp.yaml @@ -8,3 +8,4 @@ management: health: rabbit: enabled: true + diff --git a/spring-cloud-kubernetes-integration-tests/kind-config.yaml b/spring-cloud-kubernetes-integration-tests/kind-config.yaml deleted file mode 100644 index 93efbb38..00000000 --- a/spring-cloud-kubernetes-integration-tests/kind-config.yaml +++ /dev/null @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/permissions.yaml b/spring-cloud-kubernetes-integration-tests/permissions.yaml deleted file mode 100644 index 89f99255..00000000 --- a/spring-cloud-kubernetes-integration-tests/permissions.yaml +++ /dev/null @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/pom.xml b/spring-cloud-kubernetes-integration-tests/pom.xml index be9bc23f..f2cc8862 100644 --- a/spring-cloud-kubernetes-integration-tests/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/pom.xml @@ -13,31 +13,16 @@ pom Spring Cloud Kubernetes :: Integration Tests - Integration tests where SCK applications are run inside a Kubernetes - cluster - + Integration tests where SCK applications are run inside a Kubernetes cluster 17 - 1.18.2 - 1.4.0.Final - 3.12.12 - 3.2.2 - - - 32222 + 3.2.13 + 1.16.3 - - org.springframework.boot - spring-boot-maven-plugin - - org.apache.maven.plugins maven-deploy-plugin @@ -46,7 +31,6 @@ true - @@ -79,34 +63,6 @@ - - it - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - localhost - ${nodeport.value} - false - - ${project.build.outputDirectory} - - - - - - integration-test - verify - - - - - - - @@ -123,6 +79,24 @@ ${docker-java.version} test + + org.testcontainers + testcontainers + ${testcontainers.version} + test + + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + org.testcontainers + k3s + ${testcontainers.version} + test + @@ -134,11 +108,10 @@ spring-cloud-kubernetes-fabric8-client-discovery spring-cloud-kubernetes-fabric8-client-loadbalancer - spring-cloud-kubernetes-discovery-client-it - spring-cloud-kubernetes-reactive-discovery-client-it + spring-cloud-kubernetes-discoveryclient-it spring-cloud-kubernetes-client-config-it spring-cloud-kubernetes-client-loadbalancer-it - spring-cloud-kubernetes-client-reactive-discovery-client-it + spring-cloud-kubernetes-client-reactive-discoveryclient-it spring-cloud-kubernetes-configuration-watcher-it spring-cloud-kubernetes-core-k8s-client-it diff --git a/spring-cloud-kubernetes-integration-tests/run.sh b/spring-cloud-kubernetes-integration-tests/run.sh deleted file mode 100755 index 7e4cf3e2..00000000 --- a/spring-cloud-kubernetes-integration-tests/run.sh +++ /dev/null @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/pom.xml index 3007530d..0d255972 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/pom.xml @@ -5,8 +5,9 @@ org.springframework.cloud spring-cloud-kubernetes-integration-tests -3.0.0-SNAPSHOT + 3.0.0-SNAPSHOT + 4.0.0 spring-cloud-kubernetes-client-config-it @@ -51,6 +52,22 @@ test + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + k3s + test + + @@ -64,6 +81,56 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + ${testsToRun} + + + + + + + + @@ -103,6 +170,7 @@ springcloud/${project.artifactId}:${project.version} + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/application.yaml index e81b66d5..7ea719a7 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/application.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/application.yaml @@ -3,9 +3,3 @@ management: web: exposure: include: "*" -#logging: -# level: -# org: -# springframework: -# cloud: -# kubernetes: DEBUG diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap-kubernetes.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap-kubernetes.yaml index 723f28c9..7df1beb5 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap-kubernetes.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap-kubernetes.yaml @@ -1,7 +1,6 @@ spring: cloud: kubernetes: - enabled: true secrets: enable-api: true reload: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap.yaml index d4075ec0..1a857f32 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/main/resources/bootstrap.yaml @@ -1,7 +1,4 @@ spring: application: name: spring-cloud-kubernetes-client-config-it - cloud: - kubernetes: - enabled: false diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/java/org/springframework/cloud/kubernetes/client/config/it/ConfigMapAndSecretIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/java/org/springframework/cloud/kubernetes/client/config/it/ConfigMapAndSecretIT.java index 19a3949f..bec7030e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/java/org/springframework/cloud/kubernetes/client/config/it/ConfigMapAndSecretIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/java/org/springframework/cloud/kubernetes/client/config/it/ConfigMapAndSecretIT.java @@ -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 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 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 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 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); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9e284876 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/spring-cloud-kubernetes-client-config-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/spring-cloud-kubernetes-client-config-it-ingress.yaml index d65600fc..083811f4 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/spring-cloud-kubernetes-client-config-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-config-it/src/test/resources/spring-cloud-kubernetes-client-config-it-ingress.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/pom.xml index de824a22..c779ad35 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/pom.xml @@ -48,6 +48,27 @@ test + + org.springframework.boot + spring-boot-starter-webflux + + + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + k3s + test + + @@ -61,6 +82,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/KubernetesClientLoadBalancerApplicationIt.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/KubernetesClientLoadBalancerApplicationIt.java index 0b75a1e4..728ee4a4 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/KubernetesClientLoadBalancerApplicationIt.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/main/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/KubernetesClientLoadBalancerApplicationIt.java @@ -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 greeting() { - return restTemplate().getForObject("http://servicea-wiremock/__admin/mappings", Map.class); + return (Map) client().clientConnector(new ReactorClientHttpConnector(HttpClient.create())) + .baseUrl(URL).build().method(HttpMethod.GET).retrieve().bodyToMono(Map.class).block(); } @GetMapping("/services") diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerIT.java index e15157bd..f58353ba 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/LoadBalancerIT.java @@ -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 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 result = (Map) 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); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9e284876 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/spring-cloud-kubernetes-client-loadbalancer-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/spring-cloud-kubernetes-client-loadbalancer-it-ingress.yaml index f63e9f03..8e44f4e6 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/spring-cloud-kubernetes-client-loadbalancer-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/spring-cloud-kubernetes-client-loadbalancer-it-ingress.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-deployment.yaml similarity index 92% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-deployment.yaml index 1dc89ac9..9d28eb62 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-deployment.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-ingress.yaml similarity index 75% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-ingress.yaml index 2d278b58..8786d59a 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-ingress.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-loadbalancer-it/src/test/resources/wiremock/wiremock-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/ReactiveDiscoveryClientIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/ReactiveDiscoveryClientIT.java deleted file mode 100644 index 9d7f339e..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/ReactiveDiscoveryClientIT.java +++ /dev/null @@ -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 health = rest.getForObject("http://localhost:80/reactive-discovery-it/actuator/health", - Map.class); - Map components = (Map) health.get("components"); - - assertThat(components.containsKey("reactiveDiscoveryClients")).isTrue(); - Map discoveryComposite = (Map) 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"); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-service.yaml deleted file mode 100644 index df3aad15..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-service.yaml +++ /dev/null @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/k8s/deployment-it.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/k8s/deployment-it.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/k8s/deployment-it.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/k8s/deployment-it.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/k8s/service-it.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/k8s/service-it.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/k8s/service-it.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/k8s/service-it.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/pom.xml similarity index 58% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/pom.xml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/pom.xml index 968ae912..b7c3fc58 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/pom.xml @@ -9,7 +9,7 @@ 4.0.0 - spring-cloud-kubernetes-client-reactive-discovery-client-it + spring-cloud-kubernetes-client-reactive-discoveryclient-it @@ -47,6 +47,22 @@ test + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + k3s + test + + @@ -60,6 +76,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/skaffold.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/skaffold.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/skaffold.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/skaffold.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/KubernetesClientReactiveDiscoveryClientApplicationIt.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/main/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/KubernetesClientReactiveDiscoveryClientApplicationIt.java similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/KubernetesClientReactiveDiscoveryClientApplicationIt.java rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/main/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/KubernetesClientReactiveDiscoveryClientApplicationIt.java diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/main/resources/application.yaml similarity index 67% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/main/resources/application.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/main/resources/application.yaml index cd402822..978e0cde 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/main/resources/application.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/main/resources/application.yaml @@ -1,3 +1,7 @@ +spring: + webflux: + base-path: /reactive-discovery-it + management: endpoint: health: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/ReactiveDiscoveryClientIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/ReactiveDiscoveryClientIT.java new file mode 100644 index 00000000..b1f3bf47 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/client/reactive/discovery/it/ReactiveDiscoveryClientIT.java @@ -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 health = (Map) serviceClient.method(HttpMethod.GET).retrieve() + .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec()) + .block(); + + Map components = (Map) health.get("components"); + + assertThat(components.containsKey("reactiveDiscoveryClients")).isTrue(); + Map discoveryComposite = (Map) 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); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9e284876 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-deployment.yaml similarity index 58% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-deployment.yaml index c6687cb3..4d1ce28e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-deployment.yaml @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-ingress.yaml similarity index 65% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-ingress.yaml index 305cc67b..6f361e0e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discovery-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-ingress.yaml @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-service.yaml new file mode 100644 index 00000000..8d2e27d5 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discoveryclient-it/src/test/resources/spring-cloud-kubernetes-client-reactive-discoveryclient-it-service.yaml @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/pom.xml index aecd8f8b..c5e73072 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/pom.xml @@ -5,7 +5,7 @@ org.springframework.cloud spring-cloud-kubernetes-integration-tests -3.0.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 @@ -74,6 +74,29 @@ docker-java-transport-httpclient5 test + + + org.testcontainers + testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + k3s + test + + + + org.springframework.boot + spring-boot-starter-webflux + test + + @@ -87,6 +110,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigWatcherTestApplication.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigWatcherTestApplication.java index 4033cee7..a3fb642f 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigWatcherTestApplication.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/main/java/org/springframework/cloud/kubernetes/configuration/watcher/ConfigWatcherTestApplication.java @@ -38,15 +38,15 @@ public class ConfigWatcherTestApplication implements ApplicationListener 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"); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshKafkaIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshKafkaIT.java index c928fbbd..ef5751c3 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshKafkaIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshKafkaIT.java @@ -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); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshRabbitMQIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshRabbitMQIT.java index dc1c3da1..e15a163f 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshRabbitMQIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/java/org/springframework/cloud/kubernetes/configuration/watcher/ActuatorRefreshRabbitMQIT.java @@ -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); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-bus-amqp-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app-watcher/spring-cloud-kubernetes-configuration-watcher-bus-amqp-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-bus-amqp-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app-watcher/spring-cloud-kubernetes-configuration-watcher-bus-amqp-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app-watcher/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app-watcher/spring-cloud-kubernetes-configuration-watcher-bus-kafka-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app-watcher/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app-watcher/spring-cloud-kubernetes-configuration-watcher-it-bus-amqp-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-bus-kafka-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-bus-kafka-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-bus-kafka-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-bus-kafka-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml similarity index 77% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml index 2ca14ad9..222b748a 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-ingress.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-it-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/app/spring-cloud-kubernetes-configuration-watcher-it-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-configmap.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-configmap.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-configmap.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-http-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/spring-cloud-kubernetes-configuration-watcher-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/config-watcher/spring-cloud-kubernetes-configuration-watcher-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka/kafka-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka/kafka-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka/kafka-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/kafka/kafka-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9e284876 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq-controller.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq/rabbitmq-controller.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq-controller.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq/rabbitmq-controller.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq/rabbitmq-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/rabbitmq/rabbitmq-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-deployment.yaml deleted file mode 100644 index fe39b31c..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-deployment.yaml +++ /dev/null @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-service.yaml deleted file mode 100644 index 559c7645..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-service.yaml +++ /dev/null @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper/zookeeper-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper/zookeeper-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper/zookeeper-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/zookeeper/zookeeper-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/pom.xml index b52d3a6f..e7ef75a3 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/pom.xml @@ -59,6 +59,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/java/org/springframework/cloud/kubernetes/core/k8s/it/ActuatorEndpointIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/java/org/springframework/cloud/kubernetes/core/k8s/it/ActuatorEndpointIT.java index 019f2f6f..df5923e9 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/java/org/springframework/cloud/kubernetes/core/k8s/it/ActuatorEndpointIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/java/org/springframework/cloud/kubernetes/core/k8s/it/ActuatorEndpointIT.java @@ -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 health = (Map) 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 health = rest.getForObject("http://localhost:80/core-k8s-client-it/actuator/health", - Map.class); Map components = (Map) health.get("components"); assertThat(components.containsKey("kubernetes")).isTrue(); Map kubernetes = (Map) components.get("kubernetes"); @@ -132,35 +124,24 @@ public class ActuatorEndpointIT { assertThat(details.containsKey("serviceAccount")).isTrue(); assertThat(components.containsKey("discoveryComposite")).isTrue(); - Map discoveryComposite = (Map) components.get("discoveryComposite"); + Map discoveryComposite = (Map) 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 info = (Map) 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 info = rest.getForObject("http://localhost:80/core-k8s-client-it/actuator/info", Map.class); Map kubernetes = (Map) 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); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9e284876 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/spring-cloud-kubernetes-core-k8s-client-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/spring-cloud-kubernetes-core-k8s-client-it-ingress.yaml index 90b7b89f..770915a5 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/spring-cloud-kubernetes-core-k8s-client-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-core-k8s-client-it/src/test/resources/spring-cloud-kubernetes-core-k8s-client-it-ingress.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/pom.xml deleted file mode 100644 index 7eb08b8e..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/pom.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - spring-cloud-kubernetes-integration-tests - org.springframework.cloud - 3.0.0-SNAPSHOT - - 4.0.0 - - spring-cloud-kubernetes-discoverclient-it - - - 1.8.0 - openjdk:8u222-slim - - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-starter-kubernetes-discoveryclient - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.cloud - spring-cloud-kubernetes-test-support - - - io.kubernetes - client-java - - - io.kubernetes - client-java-extended - - - com.github.docker-java - docker-java-core - test - - - com.github.docker-java - docker-java-transport-httpclient5 - test - - - - - - - - ../src/main/resources - true - - - src/main/resources - true - - - - - - - skaffold - - - - org.springframework.boot - spring-boot-maven-plugin - - - ${env.IMAGE} - - build-image - - - - package - - build-image - - - - - - - - - imagename - - - !env.IMAGE - - - - springcloud/${project.artifactId}:${project.version} - - - - - diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/main/resources/application.yaml deleted file mode 100644 index 1b70c818..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/main/resources/application.yaml +++ /dev/null @@ -1,13 +0,0 @@ -spring: - cloud: - kubernetes: - discovery: - discoveryServerUrl: http://spring-cloud-kubernetes-discoveryserver -management: - endpoint: - health: - show-details: always - endpoints: - web: - exposure: - include: "*" diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java deleted file mode 100644 index 3f9b62ad..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java +++ /dev/null @@ -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 health = rest.getForObject("http://localhost:80/discoveryclient-it/actuator/health", - Map.class); - Map components = (Map) health.get("components"); - - Map discoveryComposite = (Map) 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"); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml deleted file mode 100644 index 45aebedf..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml +++ /dev/null @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-ingress.yaml deleted file mode 100644 index d58cf1b4..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-ingress.yaml +++ /dev/null @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/k8s/deployment-it.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/k8s/deployment-it.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/k8s/deployment-it.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/k8s/deployment-it.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/k8s/service-it.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/k8s/service-it.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/k8s/service-it.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/k8s/service-it.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/pom.xml similarity index 62% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/pom.xml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/pom.xml index d03f6401..03541153 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/pom.xml @@ -9,12 +9,12 @@ 4.0.0 - spring-cloud-kubernetes-reactive-discoveryclient-it + spring-cloud-kubernetes-discoveryclient-it org.springframework.boot - spring-boot-starter-webflux + spring-boot-starter-web org.springframework.cloud @@ -46,6 +46,12 @@ docker-java-transport-httpclient5 test + + org.springframework.boot + spring-boot-starter-webflux + test + + @@ -59,6 +65,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/skaffold.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/skaffold.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/skaffold.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/skaffold.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/discoveryclient/it/KubernetesDiscoveryClientApplicationIt.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/main/java/org/springframework/cloud/kubernetes/discoveryclient/it/KubernetesDiscoveryClientApplicationIt.java similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/discoveryclient/it/KubernetesDiscoveryClientApplicationIt.java rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/main/java/org/springframework/cloud/kubernetes/discoveryclient/it/KubernetesDiscoveryClientApplicationIt.java diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/main/resources/application.yaml similarity index 80% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/main/resources/application.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/main/resources/application.yaml index 1b70c818..ae3c7411 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/main/resources/application.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/main/resources/application.yaml @@ -1,3 +1,7 @@ +server: + servlet: + context-path: /discoveryclient-it + spring: cloud: kubernetes: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java new file mode 100644 index 00000000..7832cd30 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/java/org/springframework/cloud/kubernetes/discoveryclient/it/DiscoveryClientIT.java @@ -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 health = (Map) serviceClient.method(HttpMethod.GET).retrieve() + .bodyToMono(ParameterizedTypeReference.forType(resolvableType.getType())).retryWhen(retrySpec()) + .block(); + + Map components = (Map) health.get("components"); + + Map discoveryComposite = (Map) 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); + } + +} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml similarity index 85% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml index d4f120a6..7e47e543 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml @@ -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 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml similarity index 74% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml index 45aebedf..dd39366a 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-ingress.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/client/spring-cloud-kubernetes-discoveryclient-it-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9e284876 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-deployment.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-deployment.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-ingress.yaml similarity index 75% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-ingress.yaml index d58cf1b4..d820d54e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-ingress.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-discoveryclient-it/src/test/resources/server/spring-cloud-kubernetes-discoveryserver-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/pom.xml index a59c913a..d670bf6d 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/pom.xml @@ -16,10 +16,6 @@ org.springframework.cloud spring-cloud-starter-kubernetes-fabric8-all - - org.springframework.cloud - spring-cloud-starter-bootstrap - org.springframework.cloud spring-cloud-kubernetes-test-support @@ -55,6 +51,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/main/resources/application.yaml new file mode 100644 index 00000000..ace4cb6b --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/main/resources/application.yaml @@ -0,0 +1,5 @@ +spring: + application: + name: my-configmap + config: + import: "kubernetes:" diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/main/resources/bootstrap.yml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/main/resources/bootstrap.yml deleted file mode 100644 index 7a0c1eae..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/main/resources/bootstrap.yml +++ /dev/null @@ -1,7 +0,0 @@ -spring: - cloud: - kubernetes: - config: - sources: - - name: my-configmap - namespace: default diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8ConfigMapIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8ConfigMapIT.java index 5ed7b928..fc3db950 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8ConfigMapIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8ConfigMapIT.java @@ -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); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/fabric8-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/fabric8-ingress.yaml index 197f078b..51dc769b 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/fabric8-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/fabric8-ingress.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9e284876 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-configmap/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml index 6b9cd73c..746b531b 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/pom.xml @@ -51,6 +51,54 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryIT.java index 82c75691..dfa2be10 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/java/org/springframework/cloud/kubernetes/fabric8/configmap/Fabric8DiscoveryIT.java @@ -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 result = (List) 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); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-ingress.yaml index 0a46fe3c..334508bf 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-ingress.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9e284876 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-wiremock-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/wiremock/fabric8-discovery-wiremock-deployment.yaml similarity index 92% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-wiremock-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/wiremock/fabric8-discovery-wiremock-deployment.yaml index 1dc89ac9..9d28eb62 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-wiremock-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/wiremock/fabric8-discovery-wiremock-deployment.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-wiremock-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/wiremock/fabric8-discovery-wiremock-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/fabric8-discovery-wiremock-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-discovery/src/test/resources/wiremock/fabric8-discovery-wiremock-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/pom.xml index af96c765..4ab0f5c5 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/pom.xml @@ -51,6 +51,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/main/resources/application.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/main/resources/application.yaml new file mode 100644 index 00000000..c83b17d1 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/main/resources/application.yaml @@ -0,0 +1,3 @@ +spring: + webflux: + base-path: /loadbalancer-it diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Fabric8ClientLoadbalancerIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Fabric8ClientLoadbalancerIT.java index 46b0d35c..3eeaba5e 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Fabric8ClientLoadbalancerIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/java/org/springframework/cloud/kubernetes/client/loadbalancer/it/Fabric8ClientLoadbalancerIT.java @@ -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 mapResult = (Map) 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 mapResult = (Map) 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); } } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9e284876 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-ingress.yaml index 3c140c5b..17aafe66 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-ingress.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-pod-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-pod-deployment.yaml index 5ecbe185..a3285b69 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-pod-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-pod-deployment.yaml @@ -22,10 +22,10 @@ spec: readinessProbe: httpGet: port: 8080 - path: /actuator/health/readiness + path: /loadbalancer-it/actuator/health/readiness livenessProbe: httpGet: port: 8080 - path: /actuator/health/liveness + path: /loadbalancer-it/actuator/health/liveness ports: - containerPort: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-service-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-service-deployment.yaml index c1903319..71d5120a 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-service-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/spring-cloud-kubernetes-fabric8-client-loadbalancer-service-deployment.yaml @@ -22,10 +22,10 @@ spec: readinessProbe: httpGet: port: 8080 - path: /actuator/health/readiness + path: /loadbalancer-it/actuator/health/readiness livenessProbe: httpGet: port: 8080 - path: /actuator/health/liveness + path: /loadbalancer-it/actuator/health/liveness ports: - containerPort: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-deployment.yaml similarity index 92% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-deployment.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-deployment.yaml index 1dc89ac9..9d28eb62 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-deployment.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-deployment.yaml @@ -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: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-ingress.yaml similarity index 75% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-ingress.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-ingress.yaml index 2d278b58..bd2d857a 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-ingress.yaml @@ -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: / pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-service.yaml rename to spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock/wiremock-service.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/pom.xml index 04d16d98..132ad6d1 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/pom.xml @@ -51,6 +51,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/java/SimpleCoreIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/java/SimpleCoreIT.java index 3138beb4..885428da 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/java/SimpleCoreIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/java/SimpleCoreIT.java @@ -14,8 +14,9 @@ * limitations under the License. */ -import java.io.FileInputStream; +import java.io.InputStream; import java.time.Duration; +import java.util.Objects; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; @@ -27,15 +28,17 @@ 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 @@ -44,6 +47,8 @@ class SimpleCoreIT { private static final String NAMESPACE = "default"; + private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-simple-core"; + private static KubernetesClient client; private static String deploymentName; @@ -52,26 +57,32 @@ class SimpleCoreIT { private static String ingressName; + 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-client-simple-core/message").build(); + void test() { + WebClient client = builder().baseUrl("localhost/message").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(); // value must come from application-kubernetes.yml @@ -124,16 +135,24 @@ class SimpleCoreIT { } - private static FileInputStream getService() throws Exception { + private static InputStream getService() { return Fabric8Utils.inputStream("simple-core-service.yaml"); } - private static FileInputStream getDeployment() throws Exception { + private static InputStream getDeployment() { return Fabric8Utils.inputStream("simple-core-deployment.yaml"); } - private static FileInputStream getIngress() throws Exception { + private static InputStream getIngress() { return Fabric8Utils.inputStream("simple-core-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); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9e284876 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/simple-core-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/simple-core-ingress.yaml index cd7de80b..d0b52aa4 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/simple-core-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-simple-core/src/test/resources/simple-core-ingress.yaml @@ -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: /fabric8-client-simple-core(/|$)(.*) + - path: / pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/pom.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/pom.xml index 0201b5cb..bef179e4 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/pom.xml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/pom.xml @@ -51,6 +51,55 @@ true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + docker.io/springcloud/${project.artifactId}:${project.version} + paketobuildpacks/builder + + + + package + + build-image + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + + + + + + ${testsToRun} + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/Fabric8IstioIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/Fabric8IstioIT.java index 98fa5941..eae29da6 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/Fabric8IstioIT.java +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/java/org/springframework/cloud/kubernetes/fabric8/istio/Fabric8IstioIT.java @@ -16,9 +16,11 @@ package org.springframework.cloud.kubernetes.fabric8.istio; -import java.io.FileInputStream; +import java.io.File; +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 +32,32 @@ 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.containers.Container; +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 Fabric8IstioIT { +class Fabric8IstioIT { private static final String NAMESPACE = "istio-test"; + private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-istio-it"; + + private static final String LOCAL_ISTIO_BIN_PATH = "../../istio-cli/istio-1.13.3/bin"; + + private static final String CONTAINER_ISTIO_BIN_PATH = "/tmp/istio/istio-bin/bin/"; + private static KubernetesClient client; private static String deploymentName; @@ -55,27 +66,54 @@ public class Fabric8IstioIT { private static String ingressName; + private static K3sContainer K3S; + @BeforeAll - public static void setup() { - Config config = Config.autoConfigure(null); + static void beforeAll() throws Exception { + // Path passed to K3S container must be absolute + String absolutePath = new File(LOCAL_ISTIO_BIN_PATH).getAbsolutePath(); + K3S = Commons.container().withFileSystemBind(absolutePath, CONTAINER_ISTIO_BIN_PATH); + K3S.start(); + Commons.validateImage(IMAGE_NAME, K3S); + Commons.loadImage(IMAGE_NAME, K3S); + + processExecResult(K3S.execInContainer("sh", "-c", "kubectl create namespace istio-test")); + processExecResult( + K3S.execInContainer("sh", "-c", "kubectl label namespace istio-test istio-injection=enabled")); + + // for Mac M1 with aarch64 + if (System.getProperty("os.arch").equals("aarch64")) { + processExecResult(K3S.execInContainer("sh", "-c", CONTAINER_ISTIO_BIN_PATH + "istioctl" + + " --kubeconfig=/etc/rancher/k3s/k3s.yaml install --set hub=docker.io/querycapistio --set profile=minimal -y")); + } + else { + processExecResult(K3S.execInContainer("sh", "-c", CONTAINER_ISTIO_BIN_PATH + "istioctl" + + " --kubeconfig=/etc/rancher/k3s/k3s.yaml install --set profile=minimal -y")); + } + + Config config = Config.fromKubeconfig(K3S.getKubeConfigYaml()); client = new DefaultKubernetesClient(config); + Fabric8Utils.setUpIstio(client, NAMESPACE); + deployManifests(); } @AfterAll - public static void after() { + static void afterAll() throws Exception { + Commons.cleanUp(IMAGE_NAME, K3S); + } + + @AfterAll + static void after() { deleteManifests(); } @Test - public void test() { - WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())) - .baseUrl("localhost/fabric8-client-istio/profiles").build(); + void test() { + WebClient client = builder().baseUrl("localhost/profiles").build(); @SuppressWarnings("unchecked") - List result = client.method(HttpMethod.GET).retrieve().bodyToMono(List.class) - .retryWhen(Retry.fixedDelay(15, Duration.ofSeconds(1)) - .filter(x -> ((WebClientResponseException) x).getStatusCode().value() == 503)) + List result = client.method(HttpMethod.GET).retrieve().bodyToMono(List.class).retryWhen(retrySpec()) .block(); // istio profile is present @@ -92,6 +130,7 @@ public class Fabric8IstioIT { } catch (Exception e) { + e.printStackTrace(); throw new RuntimeException(e); } @@ -118,26 +157,44 @@ public class Fabric8IstioIT { ingressName = ingress.getMetadata().getName(); client.network().v1().ingresses().inNamespace(NAMESPACE).create(ingress); + Fabric8Utils.waitForIngress(client, ingressName, NAMESPACE); Fabric8Utils.waitForDeployment(client, "spring-cloud-kubernetes-fabric8-istio-it-deployment", NAMESPACE, 2, 600); } catch (Exception e) { + e.printStackTrace(); throw new RuntimeException(e); } } - private static FileInputStream getService() throws Exception { + private static InputStream getService() { return Fabric8Utils.inputStream("istio-service.yaml"); } - private static FileInputStream getDeployment() throws Exception { + private static InputStream getDeployment() { return Fabric8Utils.inputStream("istio-deployment.yaml"); } - private static FileInputStream getIngress() throws Exception { + private static InputStream getIngress() { return Fabric8Utils.inputStream("istio-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); + } + + private static String processExecResult(Container.ExecResult execResult) { + if (execResult.getExitCode() != 0) { + throw new RuntimeException("stdout=" + execResult.getStdout() + "\n" + "stderr=" + execResult.getStderr()); + } + + return execResult.getStdout(); + } + } diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/istio-ingress.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/istio-ingress.yaml index 5def7c6c..f5e5d776 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/istio-ingress.yaml +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/istio-ingress.yaml @@ -3,13 +3,11 @@ kind: Ingress metadata: name: it-ingress namespace: istio-test - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /fabric8-client-istio(/|$)(.*) + - path: / pathType: Prefix backend: service: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/logback-test.xml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9e284876 --- /dev/null +++ b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-istio-it/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/k8s/deployment-it.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/k8s/deployment-it.yaml deleted file mode 100644 index 7e9b54fb..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/k8s/deployment-it.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - creationTimestamp: null - labels: - app: spring-cloud-kubernetes-reactive-discoveryclient-it - name: spring-cloud-kubernetes-reactive-discoveryclient-it-deployment -spec: - replicas: 1 - selector: - matchLabels: - app: spring-cloud-kubernetes-reactive-discoveryclient-it - strategy: {} - template: - metadata: - creationTimestamp: null - labels: - app: spring-cloud-kubernetes-reactive-discoveryclient-it - spec: - serviceAccountName: spring-cloud-kubernetes-serviceaccount - containers: - - image: springcloud/spring-cloud-kubernetes-reactive-discoveryclient-it:2.0.4-SNAPSHOT - imagePullPolicy: IfNotPresent - name: spring-cloud-kubernetes-reactive-discoveryclient-it - resources: {} -status: {} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/k8s/service-it.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/k8s/service-it.yaml deleted file mode 100644 index 5b253f93..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/k8s/service-it.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - creationTimestamp: null - labels: - app: spring-cloud-kubernetes-reactive-discoveryclient-it - name: spring-cloud-kubernetes-reactive-discoveryclient-it -spec: - ports: - - name: 80-8080 - port: 80 - protocol: TCP - targetPort: 8080 - selector: - app: spring-cloud-kubernetes-reactive-discoveryclient-it - type: ClusterIP -status: - loadBalancer: {} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/skaffold.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/skaffold.yaml deleted file mode 100644 index 599b2ebf..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/skaffold.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: skaffold/v2alpha3 -kind: Config -metadata: - name: spring-cloud-kubernetes-reactive-discoveryclient-it -build: - artifacts: - - image: springcloud/spring-cloud-kubernetes-reactive-discoveryclient-it - jib: { - args: [ "-Pjib" ] - } -# custom: -# buildCommand: "../../mvnw clean install -Pskaffold" -# dependencies: -# paths: -# - src -# - pom.xml -deploy: - kubectl: - manifests: - - k8s/deployment-it.yaml - - k8s/service-it.yaml - - ../permissions.yaml diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/reactive/discoveryclient/it/KubernetesReactiveDiscoveryClientApplicationIt.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/reactive/discoveryclient/it/KubernetesReactiveDiscoveryClientApplicationIt.java deleted file mode 100644 index 0974a80c..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/main/java/org/springframework/cloud/kubernetes/reactive/discoveryclient/it/KubernetesReactiveDiscoveryClientApplicationIt.java +++ /dev/null @@ -1,60 +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.reactive.discoveryclient.it; - -import java.util.List; -import java.util.stream.Collectors; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author Ryan Baxter - */ -@SpringBootApplication -@RestController -public class KubernetesReactiveDiscoveryClientApplicationIt { - - private final ReactiveDiscoveryClient discoveryClient; - - public KubernetesReactiveDiscoveryClientApplicationIt(ReactiveDiscoveryClient discoveryClient) { - this.discoveryClient = discoveryClient; - } - - public static void main(String[] args) { - SpringApplication.run(KubernetesReactiveDiscoveryClientApplicationIt.class, args); - } - - @GetMapping("/services") - public Mono> services() { - return discoveryClient.getServices().collect(Collectors.toList()); - } - - @GetMapping("/service/{serviceId}") - public Flux service(@PathVariable String serviceId) { - return discoveryClient.getInstances(serviceId); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/reactive/discoveryclient/it/ReactiveDiscoveryClientIT.java b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/reactive/discoveryclient/it/ReactiveDiscoveryClientIT.java deleted file mode 100644 index cdfd25b7..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/java/org/springframework/cloud/kubernetes/reactive/discoveryclient/it/ReactiveDiscoveryClientIT.java +++ /dev/null @@ -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.reactive.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 - */ -class ReactiveDiscoveryClientIT { - - private static final Log LOG = LogFactory.getLog(ReactiveDiscoveryClientIT.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) throws IOException { - - } - }); - 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 health = rest.getForObject("http://localhost:80/discoveryclient-it/actuator/health", - Map.class); - Map components = (Map) health.get("components"); - - Map discoveryComposite = (Map) 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 V1Service getDiscoveryService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryclient-it-service.yaml"); - } - - private static V1Ingress getDiscoveryServerIngress() throws Exception { - return (V1Ingress) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryserver-ingress.yaml"); - } - - private V1Ingress getDiscoveryItIngress() throws Exception { - return (V1Ingress) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryclient-it-ingress.yaml"); - } - - private static V1Service getDiscoveryServerService() throws Exception { - return (V1Service) K8SUtils.readYamlFromClasspath("spring-cloud-kubernetes-discoveryserver-service.yaml"); - } - -} diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml deleted file mode 100644 index e42d0c39..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-deployment.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: spring-cloud-kubernetes-discoveryclient-it-deployment -spec: - selector: - matchLabels: - app: spring-cloud-kubernetes-discoveryclient-it - template: - metadata: - labels: - app: spring-cloud-kubernetes-discoveryclient-it - spec: - serviceAccountName: spring-cloud-kubernetes-serviceaccount - containers: - - name: spring-cloud-kubernetes-discoveryclient-it - image: docker.io/springcloud/spring-cloud-kubernetes-reactive-discoveryclient-it - imagePullPolicy: IfNotPresent - readinessProbe: - httpGet: - port: 8080 - path: /actuator/health/readiness - livenessProbe: - httpGet: - port: 8080 - path: /actuator/health/liveness - ports: - - containerPort: 8080 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-service.yaml deleted file mode 100644 index 32b4bd7e..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryclient-it-service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: spring-cloud-kubernetes-discoveryclient-it - name: spring-cloud-kubernetes-discoveryclient-it -spec: - ports: - - name: http - port: 8080 - targetPort: 8080 - selector: - app: spring-cloud-kubernetes-discoveryclient-it - type: ClusterIP diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-deployment.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-deployment.yaml deleted file mode 100644 index 3672520d..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-deployment.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: spring-cloud-kubernetes-discoveryserver-deployment -spec: - selector: - matchLabels: - app: spring-cloud-kubernetes-discoveryserver - template: - metadata: - labels: - app: spring-cloud-kubernetes-discoveryserver - spec: - serviceAccountName: spring-cloud-kubernetes-serviceaccount - containers: - - name: spring-cloud-kubernetes-discoveryserver - image: docker.io/springcloud/spring-cloud-kubernetes-discoveryserver - imagePullPolicy: IfNotPresent - readinessProbe: - httpGet: - port: 8761 - path: /actuator/health/readiness - livenessProbe: - httpGet: - port: 8761 - path: /actuator/health/liveness - ports: - - containerPort: 8761 diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-service.yaml b/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-service.yaml deleted file mode 100644 index ba996118..00000000 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-reactive-discovery-client-it/src/test/resources/spring-cloud-kubernetes-discoveryserver-service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app: spring-cloud-kubernetes-discoveryserver - name: spring-cloud-kubernetes-discoveryserver -spec: - ports: - - name: http - port: 80 - targetPort: 8761 - selector: - app: spring-cloud-kubernetes-discoveryserver - type: ClusterIP diff --git a/spring-cloud-kubernetes-test-support/pom.xml b/spring-cloud-kubernetes-test-support/pom.xml index 94fa3ba0..0a0379fd 100644 --- a/spring-cloud-kubernetes-test-support/pom.xml +++ b/spring-cloud-kubernetes-test-support/pom.xml @@ -41,6 +41,18 @@ awaitility ${awaitility.version} + + + org.testcontainers + testcontainers + 1.16.3 + + + org.testcontainers + k3s + 1.16.3 + + diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java new file mode 100644 index 00000000..71414f8a --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Commons.java @@ -0,0 +1,129 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.integration.tests.commons; + +import java.io.File; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; + +import com.github.dockerjava.api.model.Image; +import org.testcontainers.k3s.K3sContainer; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.cloud.kubernetes.integration.tests.commons.K8SUtils.getPomVersion; + +/** + * A few commons things that can be re-used across clients. This is meant to be used for + * testing purposes only. + * + * @author wind57 + */ +public final class Commons { + + private Commons() { + throw new AssertionError("No instance provided"); + } + + /** + * Rancher version to use for test-containers. + */ + public static final String RANCHER = "rancher/k3s:v1.21.10-k3s1"; + + /** + * Command to use when starting rancher. Without "server" option, traefik si not + * installed + */ + public static final String RANCHER_COMMAND = "server"; + + /** + * Test containers exposed ports. + */ + public static final int[] EXPOSED_PORTS = new int[] { 80, 6443, 8080, 8888, 9092 }; + + /** + * Temporary folder where to load images. + */ + public static final String TEMP_FOLDER = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath(); + + private static final K3sContainer CONTAINER = new FixedPortsK3sContainer(DockerImageName.parse(Commons.RANCHER)) + .configureFixedPorts(EXPOSED_PORTS).withFileSystemBind(TEMP_FOLDER, TEMP_FOLDER) + .withCommand(Commons.RANCHER_COMMAND).withReuse(true); + + public static K3sContainer container() { + return CONTAINER; + } + + public static void loadImage(String image, K3sContainer container) throws Exception { + // save image + InputStream imageStream = container.getDockerClient().saveImageCmd("springcloud/" + image) + .withTag(getPomVersion()).exec(); + + Path imagePath = Paths.get(TEMP_FOLDER + "/" + image + ".tar"); + Files.deleteIfExists(imagePath); + Files.copy(imageStream, imagePath); + // import image with ctr. this works because TEMP_FOLDER is mounted in the + // container + container.execInContainer("ctr", "i", "import", TEMP_FOLDER + "/" + image + ".tar"); + } + + public static void cleanUp(String image, K3sContainer container) throws Exception { + container.execInContainer("crictl", "rmi", "docker.io/springcloud/" + image + ":" + getPomVersion()); + container.execInContainer("rm", TEMP_FOLDER + "/" + image + ".tar"); + } + + public static void cleanUpDownloadedImage(String image) throws Exception { + CONTAINER.execInContainer("crictl", "rmi", image); + } + + /** + * validates that the provided image does exist in the local docker registry. + */ + public static void validateImage(String image, K3sContainer container) { + List images = container.getDockerClient().listImagesCmd().exec(); + images.stream() + .filter(x -> Arrays.stream(x.getRepoTags() == null ? new String[] {} : x.getRepoTags()) + .anyMatch(y -> y.contains(image))) + .findFirst().orElseThrow(() -> new IllegalArgumentException("Image : " + image + " not build locally. " + + "You need to build it first, and then run the test")); + } + + /** + * A K3sContainer, but with fixed port mappings. This is needed because of the nature + * of some integration tests. + * + * @author wind57 + */ + private static final class FixedPortsK3sContainer extends K3sContainer { + + private FixedPortsK3sContainer(DockerImageName dockerImageName) { + super(dockerImageName); + } + + private FixedPortsK3sContainer configureFixedPorts(int[] ports) { + for (int port : ports) { + super.addFixedExposedPort(port, port); + } + return this; + } + + } + +} diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java index eb95c71e..93d25e10 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/Fabric8Utils.java @@ -16,13 +16,18 @@ package org.springframework.cloud.kubernetes.integration.tests.commons; -import java.io.File; -import java.io.FileInputStream; +import java.io.InputStream; import java.time.Duration; +import java.util.List; import java.util.concurrent.TimeUnit; import io.fabric8.kubernetes.api.model.Endpoints; +import io.fabric8.kubernetes.api.model.LoadBalancerIngress; +import io.fabric8.kubernetes.api.model.ServiceAccount; import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.api.model.rbac.Role; +import io.fabric8.kubernetes.api.model.rbac.RoleBinding; import io.fabric8.kubernetes.client.KubernetesClient; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -41,10 +46,8 @@ public final class Fabric8Utils { throw new AssertionError("no instance provided"); } - public static FileInputStream inputStream(String fileName) throws Exception { - ClassLoader classLoader = Fabric8Utils.class.getClassLoader(); - File file = new File(classLoader.getResource(fileName).getFile()); - return new FileInputStream(file); + public static InputStream inputStream(String fileName) { + return Fabric8Utils.class.getClassLoader().getResourceAsStream(fileName); } public static void waitForDeployment(KubernetesClient client, String deploymentName, String namespace, @@ -79,4 +82,77 @@ public final class Fabric8Utils { return endpoint.getSubsets().get(0).getAddresses().size() >= 1; } + public static void setUp(KubernetesClient client, String namespace) throws Exception { + InputStream serviceAccountAsStream = inputStream("setup/service-account.yaml"); + InputStream roleBindingAsStream = inputStream("setup/role-binding.yaml"); + InputStream roleAsStream = inputStream("setup/role.yaml"); + + innerSetup(client, namespace, serviceAccountAsStream, roleBindingAsStream, roleAsStream); + } + + public static void setUpIstio(KubernetesClient client, String namespace) throws Exception { + InputStream serviceAccountAsStream = inputStream("istio/service-account.yaml"); + InputStream roleBindingAsStream = inputStream("istio/role-binding.yaml"); + InputStream roleAsStream = inputStream("istio/role.yaml"); + + innerSetup(client, namespace, serviceAccountAsStream, roleBindingAsStream, roleAsStream); + } + + public static void waitForIngress(KubernetesClient client, String ingressName, String namespace) { + + try { + await().pollInterval(Duration.ofSeconds(2)).atMost(180, TimeUnit.SECONDS).until(() -> { + Ingress ingress = client.network().v1().ingresses().inNamespace(namespace).withName(ingressName).get(); + + if (ingress == null) { + System.out.println("ingress : " + ingressName + " not ready yet present"); + return false; + } + + List loadBalancerIngress = ingress.getStatus().getLoadBalancer().getIngress(); + if (loadBalancerIngress == null || loadBalancerIngress.isEmpty()) { + System.out.println( + "ingress : " + ingressName + " not ready yet (loadbalancer ingress not yet present)"); + return false; + } + + String ip = loadBalancerIngress.get(0).getIp(); + if (ip == null) { + System.out.println("ingress : " + ingressName + " not ready yet"); + return false; + } + + System.out.println("ingress : " + ingressName + " ready with ip : " + ip); + return true; + + }); + } + catch (Exception e) { + System.out.println("Error waiting for ingress"); + e.printStackTrace(); + } + + } + + private static void innerSetup(KubernetesClient client, String namespace, InputStream serviceAccountAsStream, + InputStream roleBindingAsStream, InputStream roleAsStream) { + ServiceAccount serviceAccountFromStream = client.serviceAccounts().load(serviceAccountAsStream).get(); + if (client.serviceAccounts().inNamespace(namespace).withName(serviceAccountFromStream.getMetadata().getName()) + .get() == null) { + client.serviceAccounts().inNamespace(namespace).create(serviceAccountFromStream); + } + + RoleBinding roleBindingFromStream = client.rbac().roleBindings().load(roleBindingAsStream).get(); + if (client.rbac().roleBindings().inNamespace(namespace).withName(roleBindingFromStream.getMetadata().getName()) + .get() == null) { + client.rbac().roleBindings().inNamespace(namespace).create(roleBindingFromStream); + } + + Role roleFromStream = client.rbac().roles().load(roleAsStream).get(); + if (client.rbac().roles().inNamespace(namespace).withName(roleFromStream.getMetadata().getName()) + .get() == null) { + client.rbac().roles().inNamespace(namespace).create(roleFromStream); + } + } + } diff --git a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/K8SUtils.java b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/K8SUtils.java index 10feae86..dedf09af 100644 --- a/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/K8SUtils.java +++ b/spring-cloud-kubernetes-test-support/src/main/java/org/springframework/cloud/kubernetes/integration/tests/commons/K8SUtils.java @@ -17,32 +17,42 @@ package org.springframework.cloud.kubernetes.integration.tests.commons; import java.io.BufferedReader; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.StringReader; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.Configuration; 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.apis.RbacAuthorizationV1Api; import io.kubernetes.client.openapi.models.V1Deployment; import io.kubernetes.client.openapi.models.V1DeploymentBuilder; import io.kubernetes.client.openapi.models.V1DeploymentList; import io.kubernetes.client.openapi.models.V1Endpoints; import io.kubernetes.client.openapi.models.V1EndpointsList; import io.kubernetes.client.openapi.models.V1EnvVar; +import io.kubernetes.client.openapi.models.V1Ingress; +import io.kubernetes.client.openapi.models.V1LoadBalancerIngress; +import io.kubernetes.client.openapi.models.V1LoadBalancerStatus; import io.kubernetes.client.openapi.models.V1ReplicationController; import io.kubernetes.client.openapi.models.V1ReplicationControllerList; +import io.kubernetes.client.openapi.models.V1Role; +import io.kubernetes.client.openapi.models.V1RoleBinding; import io.kubernetes.client.openapi.models.V1Service; +import io.kubernetes.client.openapi.models.V1ServiceAccount; import io.kubernetes.client.openapi.models.V1ServiceBuilder; import io.kubernetes.client.util.Config; import io.kubernetes.client.util.Yaml; @@ -64,12 +74,20 @@ public class K8SUtils { private static final String KUBERNETES_VERSION_FILE = "META-INF/springcloudkubernetes-version.txt"; + private static final String WIREMOCK_DEPLOYMENT_NAME = "servicea-wiremock-deployment"; + + private static final String WIREMOCK_APP_NAME = "servicea-wiremock"; + private final Log log = LogFactory.getLog(getClass()); private final CoreV1Api api; private final AppsV1Api appsApi; + private final NetworkingV1Api networkingApi; + + private final RbacAuthorizationV1Api rbacApi; + public static ApiClient createApiClient() throws IOException { return createApiClient(false, Duration.ofSeconds(15)); } @@ -97,9 +115,19 @@ public class K8SUtils { return client; } + public static ApiClient createApiClient(String configFile) throws IOException { + ApiClient client = Config.fromConfig(new StringReader(configFile)); + client.setHttpClient(client.getHttpClient().newBuilder().readTimeout(Duration.ofSeconds(15)).build()); + client.setDebugging(false); + Configuration.setDefaultApiClient(client); + return client; + } + public K8SUtils(CoreV1Api api, AppsV1Api appsApi) { this.api = api; this.appsApi = appsApi; + this.networkingApi = new NetworkingV1Api(); + this.rbacApi = new RbacAuthorizationV1Api(); } public Object readYaml(String urlString) throws Exception { @@ -125,7 +153,8 @@ public class K8SUtils { public static Object readYamlFromClasspath(String fileName) throws Exception { ClassLoader classLoader = K8SUtils.class.getClassLoader(); - File file = new File(classLoader.getResource(fileName).getFile()); + String file = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream(fileName))).lines() + .collect(Collectors.joining("\n")); return Yaml.load(file); } @@ -185,7 +214,7 @@ public class K8SUtils { V1ReplicationController replicationController = controllerList.getItems().get(0); Integer availableReplicas = replicationController.getStatus().getAvailableReplicas(); - log.info("Available replicas for " + name + ": " + availableReplicas); + log.info("Available replicas for " + name + ": " + (availableReplicas == null ? 0 : availableReplicas)); return availableReplicas != null && availableReplicas >= 1; } @@ -195,6 +224,41 @@ public class K8SUtils { .until(() -> isDeploymentReady(deploymentName, namespace)); } + public void waitForIngress(String ingressName, String namespace) { + await().timeout(Duration.ofSeconds(90)).pollInterval(Duration.ofSeconds(3)).until(() -> { + try { + V1LoadBalancerStatus status = networkingApi + .readNamespacedIngress(ingressName, namespace, null, null, null).getStatus().getLoadBalancer(); + + if (status == null) { + log.info("ingress : " + ingressName + " not ready yet (loadbalancer not yet present)"); + return false; + } + + List loadBalancerIngress = status.getIngress(); + if (loadBalancerIngress == null) { + log.info("ingress : " + ingressName + " not ready yet (loadbalancer ingress not yet present)"); + return false; + } + + String ip = loadBalancerIngress.get(0).getIp(); + if (ip == null) { + log.info("ingress : " + ingressName + " not ready yet"); + return false; + } + + log.info("ingress : " + ingressName + " ready with ip : " + ip); + return true; + } + catch (ApiException e) { + if (e.getCode() == HttpURLConnection.HTTP_NOT_FOUND) { + return false; + } + throw new RuntimeException(e); + } + }); + } + public void waitForDeploymentToBeDeleted(String deploymentName, String namespace) { await().timeout(Duration.ofSeconds(90)).until(() -> { try { @@ -218,8 +282,125 @@ public class K8SUtils { } V1Deployment deployment = deployments.getItems().get(0); Integer availableReplicas = deployment.getStatus().getAvailableReplicas(); - log.info("Available replicas for " + deploymentName + ": " + availableReplicas); + log.info("Available replicas for " + deploymentName + ": " + + (availableReplicas == null ? 0 : availableReplicas)); return availableReplicas != null && availableReplicas >= 1; } + public void setUp(String namespace) throws Exception { + + V1ServiceAccount serviceAccount = getConfigK8sClientItServiceAccount(); + CheckedSupplier accountSupplier = () -> api + .readNamespacedServiceAccount(serviceAccount.getMetadata().getName(), namespace, null, null, null); + CheckedSupplier accountDefaulter = () -> api.createNamespacedServiceAccount(namespace, + serviceAccount, null, null, null); + notExistsHandler(accountSupplier, accountDefaulter); + + V1RoleBinding roleBinding = getConfigK8sClientItRoleBinding(); + notExistsHandler(() -> rbacApi.readNamespacedRoleBinding(roleBinding.getMetadata().getName(), namespace, null), + () -> rbacApi.createNamespacedRoleBinding(namespace, roleBinding, null, null, null)); + + V1Role role = getConfigK8sClientItRole(); + notExistsHandler(() -> rbacApi.readNamespacedRole(role.getMetadata().getName(), namespace, null), + () -> rbacApi.createNamespacedRole(namespace, role, null, null, null)); + } + + public static V1ServiceAccount getConfigK8sClientItServiceAccount() throws Exception { + return (V1ServiceAccount) K8SUtils.readYamlFromClasspath("setup/service-account.yaml"); + } + + public static V1RoleBinding getConfigK8sClientItRoleBinding() throws Exception { + return (V1RoleBinding) K8SUtils.readYamlFromClasspath("setup/role-binding.yaml"); + } + + public static V1Role getConfigK8sClientItRole() throws Exception { + return (V1Role) K8SUtils.readYamlFromClasspath("setup/role.yaml"); + } + + public void deployWiremock(String namespace, boolean rootPath) throws Exception { + innerDeployWiremock(namespace, rootPath); + + // Check to make sure the wiremock deployment is ready + waitForDeployment(WIREMOCK_DEPLOYMENT_NAME, namespace); + + // Check to see if endpoint is ready + waitForEndpointReady(WIREMOCK_APP_NAME, namespace); + } + + /** + * this removes wiremock related manifests, but keeps the image loaded in the + * container. As such can be used across tests. + */ + public void cleanUpWiremock(String namespace) 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); + waitForDeploymentToBeDeleted(WIREMOCK_DEPLOYMENT_NAME, namespace); + } + + /** + * this one should be called once all tests in a suite are done, as it removes the + * image from a running container. + */ + public void removeWiremockImage() throws Exception { + V1Deployment wiremockDeployment = getWiremockDeployment(); + String wiremockImage = wiremockDeployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage(); + Commons.cleanUpDownloadedImage(wiremockImage); + } + + private void innerDeployWiremock(String namespace, boolean rootPath) throws Exception { + appsApi.createNamespacedDeployment(namespace, getWiremockDeployment(), null, null, null); + api.createNamespacedService(namespace, getWiremockAppService(), null, null, null); + + V1Ingress ingress; + if (rootPath) { + ingress = getWiremockRootPathIngress(); + } + else { + ingress = getWiremockIngress(); + } + + networkingApi.createNamespacedIngress(namespace, ingress, null, null, null); + waitForIngress(ingress.getMetadata().getName(), namespace); + } + + private static V1Ingress getWiremockIngress() throws Exception { + return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock/wiremock-ingress.yaml"); + } + + private static V1Ingress getWiremockRootPathIngress() throws Exception { + return (V1Ingress) K8SUtils.readYamlFromClasspath("wiremock/wiremock-root-path-ingress.yaml"); + } + + private static V1Service getWiremockAppService() throws Exception { + return (V1Service) K8SUtils.readYamlFromClasspath("wiremock/wiremock-service.yaml"); + } + + private static V1Deployment getWiremockDeployment() throws Exception { + return (V1Deployment) K8SUtils.readYamlFromClasspath("wiremock/wiremock-deployment.yaml"); + } + + private static void notExistsHandler(CheckedSupplier callee, CheckedSupplier defaulter) throws Exception { + try { + callee.get(); + } + catch (Exception exception) { + if (exception instanceof ApiException apiException) { + if (apiException.getCode() == 404) { + defaulter.get(); + return; + } + } + throw new RuntimeException(exception); + } + } + + private interface CheckedSupplier { + + T get() throws Exception; + + } + } diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/istio/role-binding.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/istio/role-binding.yaml new file mode 100644 index 00000000..cd1bff4b --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/istio/role-binding.yaml @@ -0,0 +1,14 @@ +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 diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/istio/role.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/istio/role.yaml new file mode 100644 index 00000000..2765eee3 --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/istio/role.yaml @@ -0,0 +1,9 @@ +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" ] diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/istio/service-account.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/istio/service-account.yaml new file mode 100644 index 00000000..2f4ac50c --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/istio/service-account.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: istio-integration-test + name: spring-cloud-kubernetes-istio-serviceaccount + namespace: istio-test diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/setup/role-binding.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/setup/role-binding.yaml new file mode 100644 index 00000000..43d1e2a9 --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/setup/role-binding.yaml @@ -0,0 +1,14 @@ +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 diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/setup/role.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/setup/role.yaml new file mode 100644 index 00000000..83ba8006 --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/setup/role.yaml @@ -0,0 +1,9 @@ +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"] diff --git a/spring-cloud-kubernetes-test-support/src/main/resources/setup/service-account.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/setup/service-account.yaml new file mode 100644 index 00000000..e847e1d7 --- /dev/null +++ b/spring-cloud-kubernetes-test-support/src/main/resources/setup/service-account.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: integration-test + name: spring-cloud-kubernetes-serviceaccount diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-deployment.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-deployment.yaml similarity index 89% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-deployment.yaml rename to spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-deployment.yaml index 1dc89ac9..8ee19126 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-deployment.yaml +++ b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-deployment.yaml @@ -13,7 +13,8 @@ spec: spec: containers: - name: servicea-wiremock - image: rodolpheche/wiremock:2.27.2 + image: wiremock/wiremock:2.32.0 + args: ["--verbose"] imagePullPolicy: IfNotPresent readinessProbe: httpGet: diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-ingress.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-ingress.yaml similarity index 74% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-ingress.yaml rename to spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-ingress.yaml index 61bb48a0..8786d59a 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-client-reactive-discovery-client-it/src/test/resources/wiremock-ingress.yaml +++ b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-ingress.yaml @@ -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: @@ -17,4 +15,3 @@ spec: port: number: 8080 - diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-ingress.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-root-path-ingress.yaml similarity index 58% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-ingress.yaml rename to spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-root-path-ingress.yaml index 546618fe..bd2d857a 100644 --- a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-configuration-watcher-it/src/test/resources/wiremock-ingress.yaml +++ b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-root-path-ingress.yaml @@ -1,18 +1,17 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: nginx-ingress + name: wiremock-ingress namespace: default - annotations: - nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - http: paths: - - path: /wiremock(/|$)(.*) + - path: / pathType: Prefix backend: service: - name: config-watcher-wiremock + name: servicea-wiremock port: number: 8080 + diff --git a/spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-service.yaml b/spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-service.yaml similarity index 100% rename from spring-cloud-kubernetes-integration-tests/spring-cloud-kubernetes-fabric8-client-loadbalancer/src/test/resources/wiremock-service.yaml rename to spring-cloud-kubernetes-test-support/src/main/resources/wiremock/wiremock-service.yaml