From 6b87e9dddca6a1a1097426ee332cba341d4dac16 Mon Sep 17 00:00:00 2001 From: hanbingleixue Date: Tue, 25 Feb 2025 18:48:30 +0800 Subject: [PATCH] Add integration tests for xDS flow control Signed-off-by: hanbingleixue --- .../common/plugin-change-check/action.yml | 11 +- .../xds-service/xds-flowcontrol/action.yml | 97 +++++ .../xds-service/xds-router-lb/action.yml | 2 +- .../client-envoy/action.yml | 2 +- .../sermant-only/action.yml | 2 +- .../server-envoy/action.yml | 2 +- .github/workflows/agentcore_service_test.yml | 94 ++++- .../xds-service-test/pom.xml | 11 + ...spring-client-sermant-xds-flowcontrol.yaml | 47 +++ ...-cloud-client-sermant-xds-flowcontrol.yaml | 47 +++ .../flowcontrol/deployment/spring-server.yaml | 97 +++++ ...ing-server-destination-circuitbreaker.yaml | 37 ++ .../spring-server-envoyfilter.yaml | 85 ++++ ...-server-virtureservice-circuitbreaker.yaml | 50 +++ .../spring-server-virtureservice-fault.yaml | 122 ++++++ ...pring-server-virtureservice-ratelimit.yaml | 50 +++ .../spring-server-virtureservice-retry.yaml | 116 ++++++ .../xds-service-test/spring-client/pom.xml | 6 + .../flowcontrol/FlowControlController.java | 89 +++++ .../demo/spring/client/util/HttpUtil.java | 254 ++++++++++++ .../spring-cloud-client/pom.xml | 6 + .../client/SpringFlowControlController.java | 70 ++++ .../xds-service-test/spring-common/pom.xml | 19 + .../sermant/demo.spring.common/Constants.java | 38 ++ .../demo.spring.common/HttpClientType.java | 60 +++ .../demo.spring.common/entity/Result.java | 78 ++++ .../flowcontrol/CircuitBreakerController.java | 63 +++ .../server/flowcontrol/FaultController.java | 40 ++ .../flowcontrol/RateLimitController.java | 40 ++ .../server/flowcontrol/RetryController.java | 157 ++++++++ .../src/main/resources/application.yml | 3 + .../xds-service-integration-test/pom.xml | 6 + .../xds/service/entity/HttpClientType.java | 61 +++ .../io/sermant/xds/service/entity/Result.java | 78 ++++ .../flowcontrol/XdsFlowControlTest.java | 374 ++++++++++++++++++ .../ConnectFailureRetryCondition.java | 14 +- .../retry/condition/ResetRetryCondition.java | 11 +- 37 files changed, 2313 insertions(+), 26 deletions(-) create mode 100644 .github/actions/scenarios/xds-service/xds-flowcontrol/action.yml create mode 100644 sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-client-sermant-xds-flowcontrol.yaml create mode 100644 sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-cloud-client-sermant-xds-flowcontrol.yaml create mode 100644 sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-server.yaml create mode 100644 sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-destination-circuitbreaker.yaml create mode 100644 sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-envoyfilter.yaml create mode 100644 sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-circuitbreaker.yaml create mode 100644 sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-fault.yaml create mode 100644 sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-ratelimit.yaml create mode 100644 sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-retry.yaml create mode 100644 sermant-integration-tests/xds-service-test/spring-client/src/main/java/io/sermant/demo/spring/client/flowcontrol/FlowControlController.java create mode 100644 sermant-integration-tests/xds-service-test/spring-client/src/main/java/io/sermant/demo/spring/client/util/HttpUtil.java create mode 100644 sermant-integration-tests/xds-service-test/spring-cloud-client/src/main/java/io/sermant/demo/springcloud/client/SpringFlowControlController.java create mode 100644 sermant-integration-tests/xds-service-test/spring-common/pom.xml create mode 100644 sermant-integration-tests/xds-service-test/spring-common/src/main/java/io/sermant/demo.spring.common/Constants.java create mode 100644 sermant-integration-tests/xds-service-test/spring-common/src/main/java/io/sermant/demo.spring.common/HttpClientType.java create mode 100644 sermant-integration-tests/xds-service-test/spring-common/src/main/java/io/sermant/demo.spring.common/entity/Result.java create mode 100644 sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/CircuitBreakerController.java create mode 100644 sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/FaultController.java create mode 100644 sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/RateLimitController.java create mode 100644 sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/RetryController.java create mode 100644 sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/entity/HttpClientType.java create mode 100644 sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/entity/Result.java create mode 100644 sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/flowcontrol/XdsFlowControlTest.java diff --git a/.github/actions/common/plugin-change-check/action.yml b/.github/actions/common/plugin-change-check/action.yml index cf88e6842f..0cf6a5a6e3 100644 --- a/.github/actions/common/plugin-change-check/action.yml +++ b/.github/actions/common/plugin-change-check/action.yml @@ -932,13 +932,13 @@ runs: # ==========dynamic config service is needed to test?========== if [ ${{ env.sermantAgentCoreDynamicConfigServiceChanged }} == 'true' -o \ ${{ steps.changed-common-action.outputs.changed }} == 'true' -o ${{ env.triggerPushEvent }} == 'true' ];then - echo "enableDynamicConfigServicAction=true" >> $GITHUB_ENV + echo "enableDynamicConfigServiceAction=true" >> $GITHUB_ENV fi # ==========xds service is needed to test?========== if [ ${{ env.sermantAgentCoreXdsServiceChanged }} == 'true' -o \ ${{ steps.changed-common-action.outputs.changed }} == 'true' -o ${{ env.triggerPushEvent }} == 'true' ];then - echo "enableXdsServicAction=true" >> $GITHUB_ENV + echo "enableXdsServiceAction=true" >> $GITHUB_ENV fi # ==========mq grayscale rocketmq is needed to test?========== @@ -947,3 +947,10 @@ runs: ${{ steps.changed-common-action.outputs.changed }} == 'true' -o ${{ env.triggerPushEvent }} == 'true' ];then echo "enableMqGrayscaleRocketMqAction=true" >> $GITHUB_ENV fi + + # ==========xds service is needed to test?========== + if [ ${{ env.sermantAgentCoreXdsServiceChanged }} == 'true' -o \ + ${{ steps.changed-common-action.outputs.changed }} == 'true' -o ${{ env.triggerPushEvent }} == 'true' -o \ + ${{ env.sermantFlowcontrolChanged }} == 'true'];then + echo "enableXdsFlowControl=true" >> $GITHUB_ENV + fi diff --git a/.github/actions/scenarios/xds-service/xds-flowcontrol/action.yml b/.github/actions/scenarios/xds-service/xds-flowcontrol/action.yml new file mode 100644 index 0000000000..ebb4420e67 --- /dev/null +++ b/.github/actions/scenarios/xds-service/xds-flowcontrol/action.yml @@ -0,0 +1,97 @@ +name: "xDS router an lb Test" +description: "Auto test for xds router and lb with router plugin" +runs: + using: composite + steps: + - name: prepare image + shell: bash + run: | + echo -e "plugins:\n - flowcontrol" > sermant-agent-${{ env.sermantVersion }}/agent/config/plugins.yaml + sudo sed -i '/x-sermant-retriable-status-codes:/a\ \ \ - 502' sermant-agent-${{ env.sermantVersion }}/agent/pluginPackage/flowcontrol/config/config.yaml + sudo sed -i '/x-sermant-retriable-header-names:/a\ \ \ - needRetry' sermant-agent-${{ env.sermantVersion }}/agent/pluginPackage/flowcontrol/config/config.yaml + cat sermant-agent-${{ env.sermantVersion }}/agent/config/plugins.yaml + cat sermant-agent-${{ env.sermantVersion }}/agent/pluginPackage/flowcontrol/config/config.yaml + cp -r sermant-agent-*/agent sermant-integration-tests/xds-service-test/product/spring-client/ + cp -r sermant-agent-*/agent sermant-integration-tests/xds-service-test/product/spring-cloud-client/ + cp -r sermant-agent-*/agent sermant-integration-tests/xds-service-test/product/spring-server/ + mvn clean package -Dspringboot.version=${{ matrix.springBootVersion }} -Dsnakeyaml.version=${{ matrix.snakeyamlVersion }} -Dspringcloud.version=${{ matrix.springCloudVersion }} -Dhttpclient.version=${{ matrix.httpClientVersion }} -Dokhttp2.version=${{ matrix.okHttp2Version }} -Dhttpclient.async.version=${{ matrix.httpAsyncClientVersion }} -Dokhttp3.version=${{ matrix.okHttp3Version }} -DskipTests -pl spring-common,spring-client,spring-cloud-client,spring-server -Pxds-flowcontrol --file sermant-integration-tests/xds-service-test/pom.xml + - name: build docker image + shell: bash + run: | + cd sermant-integration-tests/xds-service-test/product/spring-server/ + minikube image build -t spring-server:1.0.0 . + cd ../spring-client/ + minikube image build -t spring-client:1.0.0 . + cd ../spring-cloud-client/ + minikube image build -t spring-cloud-client:1.0.0 . + eval $(minikube docker-env) + docker images + - name: start zookeeper + shell: bash + run: | + kubectl apply -f sermant-integration-tests/xds-service-test/script/zookeeper.yaml + kubectl wait --for=condition=ready pod -l app=zookeeper --timeout=10s + sleep 15s + - name: start spring-client + shell: bash + run: | + kubectl apply -f sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-client-sermant-xds-flowcontrol.yaml + kubectl wait --for=condition=ready pod -l app=spring-client --timeout=10s + sleep 15s + nohup kubectl port-forward svc/spring-client 8080:8080 & + sleep 2s + bash ./sermant-integration-tests/scripts/checkService.sh http://127.0.0.1:8080/checkStatus 150 + - name: start spring-cloud-client + shell: bash + run: | + kubectl apply -f sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-cloud-client-sermant-xds-flowcontrol.yaml + kubectl wait --for=condition=ready pod -l app=spring-cloud-client --timeout=10s + sleep 15s + nohup kubectl port-forward svc/spring-cloud-client 8082:8082 & + sleep 2s + bash ./sermant-integration-tests/scripts/checkService.sh http://127.0.0.1:8082/router/checkStatus 150 + - name: start spring-server + shell: bash + run: | + kubectl apply -f sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-server.yaml + kubectl wait --for=condition=ready pod -l app=spring-server --timeout=10s + sleep 15s + nohup kubectl port-forward svc/spring-server 8081:8081 & + sleep 100s + bash ./sermant-integration-tests/scripts/checkService.sh http://127.0.0.1:8081/hello 150 + bash ./sermant-integration-tests/scripts/checkService.sh http://127.0.0.1:8081/hello 150 + bash ./sermant-integration-tests/scripts/checkService.sh "http://127.0.0.1:8080/flowControl/testHttpClient?host=spring-server&version=v1&path=testFault" 150 + bash ./sermant-integration-tests/scripts/checkService.sh "http://127.0.0.1:8080/flowControl/testOkHttp2?host=spring-server&version=v1&path=testFault" 150 + bash ./sermant-integration-tests/scripts/checkService.sh "http://127.0.0.1:8080/flowControl/testHttpUrlConnection?host=spring-server&version=v1&path=testFault" 150 + bash ./sermant-integration-tests/scripts/checkService.sh "http://127.0.0.1:8082/flowControl/testOkHttp3?host=spring-server&version=v1&path=testFault" 150 + pkill -f "kubectl port-forward svc/spring-server" + - name: test flowcontrol + shell: bash + run: | + kubectl apply -f sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-destination-circuitbreaker.yaml + kubectl apply -f sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-fault.yaml + sleep 5s + mvn test -Dxds.service.integration.test.type=FLOW_CONTROL_FAULT --file \ + sermant-integration-tests/xds-service-test/xds-service-integration-test/pom.xml + kubectl delete -f sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-fault.yaml + kubectl apply -f sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-retry.yaml + sleep 5s + mvn test -Dxds.service.integration.test.type=FLOW_CONTROL_RETRY --file \ + sermant-integration-tests/xds-service-test/xds-service-integration-test/pom.xml + kubectl delete -f sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-retry.yaml + kubectl apply -f sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-circuitbreaker.yaml + sleep 5s + mvn test -Dxds.service.integration.test.type=FLOW_CONTROL_CIRCUIT_BREAKER --file \ + sermant-integration-tests/xds-service-test/xds-service-integration-test/pom.xml + kubectl delete -f sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-circuitbreaker.yaml + kubectl apply -f sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-ratelimit.yaml + kubectl apply -f sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-envoyfilter.yaml + sleep 5s + mvn test -Dxds.service.integration.test.type=FLOW_CONTROL_RATE_LIMIT --file \ + sermant-integration-tests/xds-service-test/xds-service-integration-test/pom.xml + - name: close all service + shell: bash + run: | + kubectl delete -f sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-client-sermant-xds-flowcontrol.yaml + kubectl delete -f sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-cloud-client-sermant-xds-flowcontrol.yaml + kubectl delete -f sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-server.yaml diff --git a/.github/actions/scenarios/xds-service/xds-router-lb/action.yml b/.github/actions/scenarios/xds-service/xds-router-lb/action.yml index e2982e2055..06b2d8d449 100644 --- a/.github/actions/scenarios/xds-service/xds-router-lb/action.yml +++ b/.github/actions/scenarios/xds-service/xds-router-lb/action.yml @@ -9,7 +9,7 @@ runs: cp -r sermant-integration-tests/xds-service-test/product/sermant-agent-*/agent sermant-integration-tests/xds-service-test/product/spring-client/ cp -r sermant-integration-tests/xds-service-test/product/sermant-agent-*/agent sermant-integration-tests/xds-service-test/product/spring-cloud-client/ cp -r sermant-integration-tests/xds-service-test/product/sermant-agent-*/agent sermant-integration-tests/xds-service-test/product/spring-server/ - mvn package -Dspringboot.version=${{ matrix.springBootVersion }} -Dsnakeyaml.version=${{ matrix.snakeyamlVersion }} -Dspringcloud.version=${{ matrix.springCloudVersion }} -Dhttpclient.version=${{ matrix.httpClientVersion }} -Dokhttp2.version=${{ matrix.okHttp2Version }} -Dhttpclient.async.version=${{ matrix.httpAsyncClientVersion }} -Dokhttp3.version=${{ matrix.okHttp3Version }} -DskipTests -pl spring-client,spring-cloud-client,spring-server -Pxds-router-lb --file \ + mvn package -Dspringboot.version=${{ matrix.springBootVersion }} -Dsnakeyaml.version=${{ matrix.snakeyamlVersion }} -Dspringcloud.version=${{ matrix.springCloudVersion }} -Dhttpclient.version=${{ matrix.httpClientVersion }} -Dokhttp2.version=${{ matrix.okHttp2Version }} -Dhttpclient.async.version=${{ matrix.httpAsyncClientVersion }} -Dokhttp3.version=${{ matrix.okHttp3Version }} -DskipTests -pl spring-common,spring-client,spring-cloud-client,spring-server -Pxds-router-lb --file \ sermant-integration-tests/xds-service-test/pom.xml - name: build docker image shell: bash diff --git a/.github/actions/scenarios/xds-service/xds-service-discovery/client-envoy/action.yml b/.github/actions/scenarios/xds-service/xds-service-discovery/client-envoy/action.yml index 9cb0187ffb..6367fe7784 100644 --- a/.github/actions/scenarios/xds-service/xds-service-discovery/client-envoy/action.yml +++ b/.github/actions/scenarios/xds-service/xds-service-discovery/client-envoy/action.yml @@ -8,7 +8,7 @@ runs: run: | cp -r sermant-integration-tests/xds-service-test/product/sermant-agent-*/agent sermant-integration-tests/xds-service-test/product/spring-client/ cp -r sermant-integration-tests/xds-service-test/product/sermant-agent-*/agent sermant-integration-tests/xds-service-test/product/spring-server/ - mvn package -DskipTests -pl spring-client,spring-server -Pxds-discovery --file \ + mvn package -DskipTests -pl spring-common,spring-client,spring-server -Pxds-discovery --file \ sermant-integration-tests/xds-service-test/pom.xml - name: build docker image shell: bash diff --git a/.github/actions/scenarios/xds-service/xds-service-discovery/sermant-only/action.yml b/.github/actions/scenarios/xds-service/xds-service-discovery/sermant-only/action.yml index 9879780c4a..5352c76101 100644 --- a/.github/actions/scenarios/xds-service/xds-service-discovery/sermant-only/action.yml +++ b/.github/actions/scenarios/xds-service/xds-service-discovery/sermant-only/action.yml @@ -8,7 +8,7 @@ runs: run: | cp -r sermant-integration-tests/xds-service-test/product/sermant-agent-*/agent sermant-integration-tests/xds-service-test/product/spring-client/ cp -r sermant-integration-tests/xds-service-test/product/sermant-agent-*/agent sermant-integration-tests/xds-service-test/product/spring-server/ - mvn package -DskipTests -pl spring-client,spring-server -Pxds-discovery --file \ + mvn package -DskipTests -pl spring-common,spring-client,spring-server -Pxds-discovery --file \ sermant-integration-tests/xds-service-test/pom.xml - name: build docker image shell: bash diff --git a/.github/actions/scenarios/xds-service/xds-service-discovery/server-envoy/action.yml b/.github/actions/scenarios/xds-service/xds-service-discovery/server-envoy/action.yml index 416706fbbc..f2bb1aae7a 100644 --- a/.github/actions/scenarios/xds-service/xds-service-discovery/server-envoy/action.yml +++ b/.github/actions/scenarios/xds-service/xds-service-discovery/server-envoy/action.yml @@ -8,7 +8,7 @@ runs: run: | cp -r sermant-integration-tests/xds-service-test/product/sermant-agent-*/agent sermant-integration-tests/xds-service-test/product/spring-client/ cp -r sermant-integration-tests/xds-service-test/product/sermant-agent-*/agent sermant-integration-tests/xds-service-test/product/spring-server/ - mvn package -DskipTests -pl spring-client,spring-server -Pxds-discovery --file \ + mvn package -DskipTests -pl spring-common,spring-client,spring-server -Pxds-discovery --file \ sermant-integration-tests/xds-service-test/pom.xml - name: build docker image shell: bash diff --git a/.github/workflows/agentcore_service_test.yml b/.github/workflows/agentcore_service_test.yml index db5561191a..25b3f5aff0 100644 --- a/.github/workflows/agentcore_service_test.yml +++ b/.github/workflows/agentcore_service_test.yml @@ -22,6 +22,7 @@ on: - '.github/actions/common/exit/action.yml' - 'sermant-plugins/sermant-router/router-common/**' - 'sermant-plugins/sermant-router/spring-router-plugin/**' + - 'sermant-plugins/sermant-flowcontrol/**' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.head_ref }} cancel-in-progress: true @@ -39,15 +40,16 @@ jobs: - name: set-outputs id: set-outputs run: | - echo "enableDynamicConfigServicAction=${{env.enableDynamicConfigServicAction}}" >> $GITHUB_OUTPUT - echo "enableXdsServicAction=${{env.enableXdsServicAction}}" >> $GITHUB_OUTPUT + echo "enableDynamicConfigServiceAction=${{env.enableDynamicConfigServiceAction}}" >> $GITHUB_OUTPUT + echo "enableXdsServiceAction=${{env.enableXdsServiceAction}}" >> $GITHUB_OUTPUT + echo "enableXdsFlowControl=${{env.enableXdsFlowControl}}" >> $GITHUB_OUTPUT outputs: - enableDynamicConfigServicAction: ${{ steps.set-outputs.outputs.enableDynamicConfigServicAction }} - enableXdsServicAction: ${{ steps.set-outputs.outputs.enableXdsServicAction }} + enableDynamicConfigServiceAction: ${{ steps.set-outputs.outputs.enableDynamicConfigServiceAction }} + enableXdsServiceAction: ${{ steps.set-outputs.outputs.enableXdsServiceAction }} download-midwares-and-cache: name: download midwares and cache runs-on: ubuntu-latest - if: needs.set-execution-conditions.outputs.enableDynamicConfigServicAction == 'true' + if: needs.set-execution-conditions.outputs.enableDynamicConfigServiceAction == 'true' needs: [ set-execution-conditions ] steps: - uses: actions/checkout@v4 @@ -87,7 +89,7 @@ jobs: build-agent-and-cache: name: build agent and cache runs-on: ubuntu-latest - if: needs.set-execution-conditions.outputs.enableDynamicConfigServicAction == 'true' || needs.set-execution-conditions.outputs.enableXdsServicAction == 'true' + if: needs.set-execution-conditions.outputs.enableDynamicConfigServiceAction == 'true' || needs.set-execution-conditions.outputs.enableXdsServiceAction == 'true' needs: [set-execution-conditions] steps: - uses: actions/checkout@v4 @@ -126,7 +128,7 @@ jobs: test-for-agentcore-dynamic-config: name: Test for agentcore dynamic config runs-on: ubuntu-latest - if: needs.set-execution-conditions.outputs.enableDynamicConfigServicAction == 'true' + if: needs.set-execution-conditions.outputs.enableDynamicConfigServiceAction == 'true' needs: [ set-execution-conditions, build-agent-and-cache, download-midwares-and-cache ] strategy: matrix: @@ -150,7 +152,7 @@ jobs: test-for-xds-service-discovery-onlysermant: name: Test for xds service discovery with only sermant runs-on: ubuntu-latest - if: needs.set-execution-conditions.outputs.enableXdsServicAction == 'true' + if: needs.set-execution-conditions.outputs.enableXdsServiceAction == 'true' needs: [set-execution-conditions, build-agent-and-cache] steps: - uses: actions/checkout@v4 @@ -166,7 +168,7 @@ jobs: test-for-xds-service-discovery-with-server-envoy: name: Test for xds service discovery with spring-server using envoy runs-on: ubuntu-latest - if: needs.set-execution-conditions.outputs.enableXdsServicAction == 'true' + if: needs.set-execution-conditions.outputs.enableXdsServiceAction == 'true' needs: [set-execution-conditions, build-agent-and-cache] steps: - uses: actions/checkout@v4 @@ -182,7 +184,7 @@ jobs: test-for-xds-service-discovery-with-client-envoy: name: Test for xds service discovery with spring-client using enovy runs-on: ubuntu-latest - if: needs.set-execution-conditions.outputs.enableXdsServicAction == 'true' + if: needs.set-execution-conditions.outputs.enableXdsServiceAction == 'true' needs: [set-execution-conditions, build-agent-and-cache] steps: - uses: actions/checkout@v4 @@ -198,7 +200,7 @@ jobs: test-for-xds-router-lb: name: Test for xds router and lb with router plugin runs-on: ubuntu-latest - if: needs.set-execution-conditions.outputs.enableXdsServicAction == 'true' + if: needs.set-execution-conditions.outputs.enableXdsServiceAction == 'true' needs: [set-execution-conditions, build-agent-and-cache] strategy: matrix: @@ -264,3 +266,73 @@ jobs: uses: ./.github/actions/common/xds-service - name: xds router and lb test uses: ./.github/actions/scenarios/xds-service/xds-router-lb + test-for-xds-flowcontrol: + name: Test for xds flow control + runs-on: ubuntu-latest + if: needs.set-execution-conditions.outputs.enableXdsServiceAction == 'true' + || needs.set-execution-conditions.outputs.enableXdsFlowControl == 'true' + needs: [ set-execution-conditions, build-agent-and-cache ] + strategy: + matrix: + include: + - springBootVersion: "2.0.2.RELEASE" + snakeyamlVersion: "1.19" + springCloudVersion: "Finchley.RELEASE" + httpClientVersion: "4.4" + okHttp2Version: "2.2.0" + okHttp3Version: "3.5.0" + httpAsyncClientVersion: "4.0.1" + - springBootVersion: "2.1.0.RELEASE" + snakeyamlVersion: "1.23" + springCloudVersion: "Greenwich.RELEASE" + httpClientVersion: "4.4.1" + okHttp2Version: "2.3.0" + okHttp3Version: "3.9.1" + httpAsyncClientVersion: "4.0.2" + - springBootVersion: "2.2.0.RELEASE" + snakeyamlVersion: "1.25" + springCloudVersion: "Hoxton.RELEASE" + httpClientVersion: "4.5" + okHttp2Version: "2.4.0" + okHttp3Version: "3.12.13" + httpAsyncClientVersion: "4.1" + - springBootVersion: "2.3.0.RELEASE" + snakeyamlVersion: "1.26" + springCloudVersion: "Hoxton.RELEASE" + httpClientVersion: "4.5.3" + okHttp2Version: "2.5.0" + okHttp3Version: "3.14.9" + httpAsyncClientVersion: "4.1.1" + - springBootVersion: "2.4.0" + snakeyamlVersion: "1.27" + springCloudVersion: "2020.0.0" + httpClientVersion: "4.5.7" + okHttp2Version: "2.6.0" + okHttp3Version: "4.2.2" + httpAsyncClientVersion: "4.1.2" + - springBootVersion: "2.6.2" + snakeyamlVersion: "1.29" + springCloudVersion: "2021.0.0" + httpClientVersion: "4.5.10" + okHttp2Version: "2.7.3" + okHttp3Version: "4.7.2" + httpAsyncClientVersion: "4.1.3" + - springBootVersion: "2.7.17" + snakeyamlVersion: "1.30" + springCloudVersion: "2021.0.3" + httpClientVersion: "4.5.13" + okHttp2Version: "2.7.5" + okHttp3Version: "4.12.0" + httpAsyncClientVersion: "4.1.5" + fail-fast: false + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 100 + - name: set java version to environment + run: | + echo "javaVersion=8" >> $GITHUB_ENV + - name: xds common operation + uses: ./.github/actions/common/xds-service + - name: xds flow control test + uses: ./.github/actions/scenarios/xds-service/xds-flowcontrol diff --git a/sermant-integration-tests/xds-service-test/pom.xml b/sermant-integration-tests/xds-service-test/pom.xml index f1a0732cef..6cd557c3ee 100644 --- a/sermant-integration-tests/xds-service-test/pom.xml +++ b/sermant-integration-tests/xds-service-test/pom.xml @@ -59,6 +59,7 @@ xds-discovery + spring-common spring-client spring-server @@ -66,6 +67,16 @@ xds-router-lb + spring-common + spring-client + spring-server + spring-cloud-client + + + + xds-flowcontrol + + spring-common spring-client spring-server spring-cloud-client diff --git a/sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-client-sermant-xds-flowcontrol.yaml b/sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-client-sermant-xds-flowcontrol.yaml new file mode 100644 index 0000000000..c16fb41c2f --- /dev/null +++ b/sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-client-sermant-xds-flowcontrol.yaml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-client +spec: + replicas: 1 + selector: + matchLabels: + app: spring-client + template: + metadata: + labels: + app: spring-client + spec: + containers: + - name: spring-client + image: spring-client:1.0.0 + imagePullPolicy: Never + ports: + - containerPort: 8080 + env: + - name: agent_service_dynamic_config_enable + value: "false" + - name: agent_service_xds_service_enable + value: "true" + - name: xds_flow_control_config_enable + value: "true" + - name: xds_service_discovery_enabled + value: "false" + - name: JAVA_TOOL_OPTIONS + value: "-javaagent:/home/agent/sermant-agent.jar" + imagePullSecrets: + - name: default-secret +--- +apiVersion: v1 +kind: Service +metadata: + name: spring-client +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: spring-client diff --git a/sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-cloud-client-sermant-xds-flowcontrol.yaml b/sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-cloud-client-sermant-xds-flowcontrol.yaml new file mode 100644 index 0000000000..2ff48764d7 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-cloud-client-sermant-xds-flowcontrol.yaml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-cloud-client +spec: + replicas: 1 + selector: + matchLabels: + app: spring-cloud-client + template: + metadata: + labels: + app: spring-cloud-client + spec: + containers: + - name: spring-cloud-client + image: spring-cloud-client:1.0.0 + imagePullPolicy: Never + ports: + - containerPort: 8082 + env: + - name: agent_service_dynamic_config_enable + value: "false" + - name: agent_service_xds_service_enable + value: "true" + - name: xds_flow_control_config_enable + value: "true" + - name: ZOOKEEPER_IP + value: "zookeeper.default.svc.cluster.local" + - name: JAVA_TOOL_OPTIONS + value: "-javaagent:/home/agent/sermant-agent.jar" + imagePullSecrets: + - name: default-secret +--- +apiVersion: v1 +kind: Service +metadata: + name: spring-cloud-client +spec: + type: ClusterIP + ports: + - port: 8082 + targetPort: 8082 + protocol: TCP + name: http + selector: + app: spring-cloud-client diff --git a/sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-server.yaml b/sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-server.yaml new file mode 100644 index 0000000000..98ca1189c2 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/script/flowcontrol/deployment/spring-server.yaml @@ -0,0 +1,97 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-server-v1 +spec: + replicas: 1 + selector: + matchLabels: + app: spring-server + version: v1 + template: + metadata: + labels: + app: spring-server + version: v1 + spec: + containers: + - name: spring-server + image: spring-server:1.0.0 + imagePullPolicy: Never + ports: + - containerPort: 8081 + env: + - name: SERVER_VERSION + value: "v1" + - name: ZOOKEEPER_IP + value: "zookeeper.default.svc.cluster.local" + - name: ZOOKEEPER_ENABLED + value: "true" + - name: agent_service_xds_service_enable + value: "true" + - name: xds_flow_control_config_enable + value: "true" + - name: xds_service_discovery_enabled + value: "false" + - name: agent_service_dynamic_config_enable + value: "false" + - name: JAVA_TOOL_OPTIONS + value: "-DstatusCode=200 -Dservice.meta.service=spring-server -javaagent:/home/agent/sermant-agent.jar" + imagePullSecrets: + - name: default-secret +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-server-v2 +spec: + replicas: 1 + selector: + matchLabels: + app: spring-server + version: v2 + template: + metadata: + labels: + app: spring-server + version: v2 + spec: + containers: + - name: spring-server + image: spring-server:1.0.0 + imagePullPolicy: Never + ports: + - containerPort: 8081 + env: + - name: SERVER_VERSION + value: "v2" + - name: ZOOKEEPER_IP + value: "zookeeper.default.svc.cluster.local" + - name: ZOOKEEPER_ENABLED + value: "true" + - name: agent_service_xds_service_enable + value: "true" + - name: xds_flow_control_config_enable + value: "true" + - name: xds_service_discovery_enabled + value: "false" + - name: agent_service_dynamic_config_enable + value: "false" + - name: JAVA_TOOL_OPTIONS + value: "-DstatusCode=502 -Dservice.meta.service=spring-server -javaagent:/home/agent/sermant-agent.jar" + imagePullSecrets: + - name: default-secret +--- +apiVersion: v1 +kind: Service +metadata: + name: spring-server +spec: + type: ClusterIP + ports: + - port: 8081 + targetPort: 8081 + protocol: TCP + name: http + selector: + app: spring-server diff --git a/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-destination-circuitbreaker.yaml b/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-destination-circuitbreaker.yaml new file mode 100644 index 0000000000..8750d7523e --- /dev/null +++ b/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-destination-circuitbreaker.yaml @@ -0,0 +1,37 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: spring-server-destinationrule +spec: + host: spring-server.default.svc.cluster.local + trafficPolicy: + loadBalancer: + simple: ROUND_ROBIN + subsets: + - name: v1 + trafficPolicy: + loadBalancer: + simple: ROUND_ROBIN + - name: v2 + trafficPolicy: + connectionPool: + http: + http2MaxRequests: 1 + - name: v3 + trafficPolicy: + loadBalancer: + simple: ROUND_ROBIN + outlierDetection: + consecutiveGatewayErrors: 20 + interval: 10s + baseEjectionTime: 5s + maxEjectionPercent: 100 + - name: v4 + trafficPolicy: + loadBalancer: + simple: ROUND_ROBIN + outlierDetection: + consecutive5xxErrors: 20 + interval: 10s + baseEjectionTime: 5s + maxEjectionPercent: 100 diff --git a/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-envoyfilter.yaml b/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-envoyfilter.yaml new file mode 100644 index 0000000000..926846ab39 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-envoyfilter.yaml @@ -0,0 +1,85 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: EnvoyFilter +metadata: + name: filter-local-ratelimit + namespace: istio-system +spec: + configPatches: + - applyTo: HTTP_ROUTE + match: + routeConfiguration: + vhost: + route: + name: testRateLimitV1 + patch: + operation: MERGE + value: + typed_per_filter_config: + envoy.filters.http.local_ratelimit: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit + value: + token_bucket: + max_tokens: 3 + tokens_per_fill: 3 + fill_interval: 10s + filter_enabled: + default_value: + numerator: 0 + denominator: HUNDRED + response_headers_to_add: + header: + key: x-local-rate-limit + value: 'true' + - applyTo: HTTP_ROUTE + match: + routeConfiguration: + vhost: + route: + name: testRateLimitV2 + patch: + operation: MERGE + value: + typed_per_filter_config: + envoy.filters.http.local_ratelimit: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit + value: + token_bucket: + max_tokens: 3 + tokens_per_fill: 3 + fill_interval: 10s + filter_enabled: + default_value: + numerator: 50 + denominator: HUNDRED + response_headers_to_add: + header: + key: x-local-rate-limit + value: 'true' + - applyTo: HTTP_ROUTE + match: + routeConfiguration: + vhost: + route: + name: testRateLimitV3 + patch: + operation: MERGE + value: + typed_per_filter_config: + envoy.filters.http.local_ratelimit: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit + value: + token_bucket: + max_tokens: 2 + tokens_per_fill: 2 + fill_interval: 10s + filter_enabled: + default_value: + numerator: 100 + denominator: HUNDRED + response_headers_to_add: + header: + key: x-local-rate-limit + value: 'true' diff --git a/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-circuitbreaker.yaml b/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-circuitbreaker.yaml new file mode 100644 index 0000000000..60f577e915 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-circuitbreaker.yaml @@ -0,0 +1,50 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: spring-server-virtual-service-circuit-breaker +spec: + hosts: + - spring-server + http: + - name: "testRequestCircuitBreaker" + match: + - headers: + version: + exact: v1 + uri: + exact: /testRequestCircuitBreaker + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v2 + port: + number: 8081 + - name: "testInstanceCircuitBreakerV2" + match: + - headers: + version: + exact: v2 + uri: + exact: /testInstanceCircuitBreaker + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v3 + port: + number: 8081 + - name: "testInstanceCircuitBreakerV3" + match: + - headers: + version: + exact: v3 + uri: + exact: /testInstanceCircuitBreaker + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v4 + port: + number: 8081 diff --git a/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-fault.yaml b/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-fault.yaml new file mode 100644 index 0000000000..6bda220101 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-fault.yaml @@ -0,0 +1,122 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: spring-server-virtual-service-fault +spec: + hosts: + - spring-server + http: + - name: "testDelayV1" + match: + - headers: + version: + exact: v1 + uri: + exact: /testFault + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + fault: + delay: + percentage: + value: 0 + fixedDelay: 5s + - name: "testDelayV2" + match: + - headers: + version: + exact: v2 + uri: + exact: /testFault + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + fault: + delay: + percentage: + value: 50 + fixedDelay: 5s + - name: "testDelayV3" + match: + - headers: + version: + exact: v3 + uri: + exact: /testFault + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + fault: + delay: + percentage: + value: 100 + fixedDelay: 5s + - name: "testAboutV1" + match: + - headers: + version: + exact: v4 + uri: + exact: /testFault + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + fault: + abort: + percentage: + value: 0 + httpStatus: 400 + - name: "testAboutV2" + match: + - headers: + version: + exact: v5 + uri: + exact: /testFault + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + fault: + abort: + percentage: + value: 50 + httpStatus: 400 + - name: "testAboutV3" + match: + - headers: + version: + exact: v6 + uri: + exact: /testFault + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + fault: + abort: + percentage: + value: 100 + httpStatus: 400 diff --git a/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-ratelimit.yaml b/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-ratelimit.yaml new file mode 100644 index 0000000000..35e32e452b --- /dev/null +++ b/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-ratelimit.yaml @@ -0,0 +1,50 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: spring-server-virtual-service-ratelimit +spec: + hosts: + - spring-server + http: + - name: "testRateLimitV1" + match: + - headers: + version: + exact: v1 + uri: + exact: /testRateLimit + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + - name: "testRateLimitV2" + match: + - headers: + version: + exact: v2 + uri: + exact: /testRateLimit + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + - name: "testRateLimitV3" + match: + - headers: + version: + exact: v3 + uri: + exact: /testRateLimit + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 diff --git a/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-retry.yaml b/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-retry.yaml new file mode 100644 index 0000000000..c6ee447ac1 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/script/flowcontrol/flowcontrol-rule/spring-server-virtureservice-retry.yaml @@ -0,0 +1,116 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: spring-server-virtual-service-retry +spec: + hosts: + - spring-server + http: + - name: "testRetryOnGateWayError" + match: + - headers: + version: + exact: v1 + uri: + exact: /testGateWayError + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + retries: + attempts: 2 + perTryTimeout: 1s + retryOn: "gateway-error" + - name: "testRetryOnHeader" + match: + - headers: + version: + exact: v1 + uri: + exact: /testRetryOnHeader + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + retries: + attempts: 2 + perTryTimeout: 1s + retryOn: "retriable-headers" + - name: "testRetryOn5xxError" + match: + - headers: + version: + exact: v1 + uri: + exact: /test5xxError + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + retries: + attempts: 2 + perTryTimeout: 1s + retryOn: "5xx" + - name: "testRetryOn4xxError" + match: + - headers: + version: + exact: v1 + uri: + exact: /test4xxError + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + retries: + attempts: 2 + perTryTimeout: 1s + retryOn: "retriable-4xx" + - name: "testRetryOnTimeout" + match: + - headers: + version: + exact: v1 + uri: + exact: /testTimeOut + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + retries: + attempts: 2 + perTryTimeout: 1s + retryOn: "reset" + - name: "testRetryOnStatusCode" + match: + - headers: + version: + exact: v1 + uri: + exact: /testRetryOnStatusCode + ignoreUriCase: false + route: + - destination: + host: spring-server + subset: v1 + port: + number: 8081 + retries: + attempts: 2 + perTryTimeout: 1s + retryOn: "retriable-status-codes" diff --git a/sermant-integration-tests/xds-service-test/spring-client/pom.xml b/sermant-integration-tests/xds-service-test/spring-client/pom.xml index 1fa1009061..46a2475cb5 100644 --- a/sermant-integration-tests/xds-service-test/spring-client/pom.xml +++ b/sermant-integration-tests/xds-service-test/spring-client/pom.xml @@ -14,6 +14,7 @@ 8 8 + 1.0.0 @@ -26,6 +27,11 @@ httpclient 4.5.13 + + io.sermant.integration + spring-common + ${project.version} + org.apache.httpcomponents httpasyncclient diff --git a/sermant-integration-tests/xds-service-test/spring-client/src/main/java/io/sermant/demo/spring/client/flowcontrol/FlowControlController.java b/sermant-integration-tests/xds-service-test/spring-client/src/main/java/io/sermant/demo/spring/client/flowcontrol/FlowControlController.java new file mode 100644 index 0000000000..cecce598a8 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/spring-client/src/main/java/io/sermant/demo/spring/client/flowcontrol/FlowControlController.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * 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 + * + * http://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 io.sermant.demo.spring.client.flowcontrol; + +import io.sermant.demo.spring.client.util.HttpUtil; +import io.sermant.demo.spring.common.Constants; +import io.sermant.demo.spring.common.HttpClientType; +import io.sermant.demo.spring.common.entity.Result; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * flow control + * + * @author zhp + * @since 2025-01-13 + **/ +@RequestMapping("flowControl") +@RestController +public class FlowControlController { + private static final String VERSION = "version"; + + /** + * Test the flow control functionality of the HttpClient client + * + * @param host the service address of upstream service + * @param path request path + * @param version version + * @return result + */ + @GetMapping("testHttpClient") + public Result testHttpClient(String host, String path, String version) { + Map headers = new HashMap<>(); + headers.put(VERSION, version); + return HttpUtil.sendGetRequest(Constants.HTTP_PROTOCOL + host + Constants.SLASH + path, + HttpClientType.HTTP_CLIENT, headers); + } + + /** + * Test the flow control functionality of the OkHttp2 + * + * @param host the service address of upstream service + * @param path request path + * @param version version + * @return result + */ + @GetMapping("testOkHttp2") + public Result testOkHttp2(String host, String path, String version) { + Map headers = new HashMap<>(); + headers.put(VERSION, version); + return HttpUtil.sendGetRequest(Constants.HTTP_PROTOCOL + host + Constants.SLASH + path, + HttpClientType.OK_HTTP2, headers); + } + + /** + * Test the flow control functionality of the HttpUrlConnection + * + * @param host the service address of upstream service + * @param path request path + * @param version version + * @return result + */ + @GetMapping("testHttpUrlConnection") + public Result testHttpUrlConnection(String host, String path, String version) { + Map headers = new HashMap<>(); + headers.put(VERSION, version); + return HttpUtil.sendGetRequest(Constants.HTTP_PROTOCOL + host + Constants.SLASH + path, + HttpClientType.HTTP_URL_CONNECTION, headers); + } +} diff --git a/sermant-integration-tests/xds-service-test/spring-client/src/main/java/io/sermant/demo/spring/client/util/HttpUtil.java b/sermant-integration-tests/xds-service-test/spring-client/src/main/java/io/sermant/demo/spring/client/util/HttpUtil.java new file mode 100644 index 0000000000..cbd466e5e4 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/spring-client/src/main/java/io/sermant/demo/spring/client/util/HttpUtil.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * 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 + * + * http://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 io.sermant.demo.spring.client.util; + +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; +import com.squareup.okhttp.Response; +import com.squareup.okhttp.ResponseBody; + +import io.sermant.demo.spring.common.HttpClientType; +import io.sermant.demo.spring.common.entity.Result; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.util.EntityUtils; +import org.springframework.http.HttpMethod; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * controller + * + * @author zhp + * @since 2025-01-13 + **/ +public class HttpUtil { + private static final int MAX_TOTAL = 200; + + private static final int MAX_PER_ROUTE = 20; + + private static final int CONNECT_TIMEOUT = 2000; + + private static final int SOCKET_TIMEOUT = 2000; + + private static final String EMPTY_STR = ""; + + private static final CloseableHttpClient HTTP_CLIENT; + + private static final OkHttpClient CLIENT; + + static { + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(MAX_TOTAL); + connectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE); + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(CONNECT_TIMEOUT) + .setSocketTimeout(SOCKET_TIMEOUT) + .setConnectionRequestTimeout(CONNECT_TIMEOUT) + .build(); + HTTP_CLIENT = HttpClients.custom() + .setConnectionManager(connectionManager) + .setDefaultRequestConfig(requestConfig) + .build(); + CLIENT = new OkHttpClient(); + CLIENT.setConnectTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS); + CLIENT.setWriteTimeout(SOCKET_TIMEOUT, TimeUnit.MILLISECONDS); + CLIENT.setReadTimeout(SOCKET_TIMEOUT, TimeUnit.MILLISECONDS); + } + + private HttpUtil() { + } + + /** + * send Http get request + * + * @param url request url + * @param headerMap request header information + * @param httpClientType Client type for sending HTTP requests + * @return response + */ + public static Result sendGetRequest(String url, HttpClientType httpClientType, Map headerMap) { + if (httpClientType == HttpClientType.HTTP_CLIENT) { + return sendGetRequestWithHttpClient(url, headerMap); + } + if (httpClientType == HttpClientType.OK_HTTP2) { + return sendGetRequestWithOkHttp(url, headerMap); + } + if (httpClientType == HttpClientType.HTTP_URL_CONNECTION) { + return sendRequestWithHttpUrlConnection(url, null, headerMap, HttpMethod.GET.name()); + } + return new Result(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Unsupported HttpClient", null); + } + + /** + * send Http Post request + * + * @param url request url + * @param body request body + * @param httpClientType Client type for sending HTTP requests + * @param headerMap the header information of request + * @return response + */ + public static Result sendPostRequest(String url, Object body, HttpClientType httpClientType, + Map headerMap) { + if (httpClientType == HttpClientType.HTTP_CLIENT) { + return sendPostRequestWithHttpClient(url, body, headerMap); + } + if (httpClientType == HttpClientType.OK_HTTP2) { + return sendPostRequestWithOkHttp(url, body, headerMap); + } + if (httpClientType == HttpClientType.HTTP_URL_CONNECTION) { + return sendRequestWithHttpUrlConnection(url, body, headerMap, HttpMethod.POST.name()); + } + return new Result(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Unsupported HttpClient", null); + } + + private static Result sendGetRequestWithHttpClient(String url, Map headerMap) { + HttpGet httpGet = new HttpGet(url); + return sendRequestWithHttpClient(headerMap, httpGet); + } + + private static Result sendRequestWithHttpClient(Map headerMap, HttpRequestBase httpRequestBase) { + for (Map.Entry entry : headerMap.entrySet()) { + httpRequestBase.addHeader(entry.getKey(), entry.getValue()); + } + try (CloseableHttpResponse response = HTTP_CLIENT.execute(httpRequestBase)) { + return new Result(response.getStatusLine().getStatusCode(), EMPTY_STR, + EntityUtils.toString(response.getEntity())); + } catch (IOException e) { + return new Result(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage(), null); + } + } + + private static Result sendPostRequestWithHttpClient(String url, Object body, Map headerMap) { + HttpPost httpPost = new HttpPost(url); + if (body instanceof HttpEntity) { + httpPost.setEntity((HttpEntity) body); + } + return sendRequestWithHttpClient(headerMap, httpPost); + } + + private static Result sendGetRequestWithOkHttp(String url, Map headerMap) { + Request.Builder builder = new Request.Builder(); + for (Map.Entry entry : headerMap.entrySet()) { + builder.addHeader(entry.getKey(), entry.getValue()); + } + Request request = builder.url(url).build(); + return sendRequestWithOkHttp(request); + } + + private static Result sendPostRequestWithOkHttp(String url, Object body, Map headerMap) { + Request.Builder builder = new Request.Builder(); + for (Map.Entry entry : headerMap.entrySet()) { + builder.addHeader(entry.getKey(), entry.getValue()); + } + if (body instanceof RequestBody) { + builder.post((RequestBody) body); + } + Request request = builder.url(url).build(); + return sendRequestWithOkHttp(request); + } + + private static Result sendRequestWithOkHttp(Request request) { + try { + Response response = CLIENT.newCall(request).execute(); + try (ResponseBody responseBody = response.body()) { + return new Result(response.code(), response.message(), new String(responseBody.bytes())); + } + } catch (IOException e) { + return new Result(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage(), null); + } + } + + private static Result sendRequestWithHttpUrlConnection(String urlStr, Object body, + Map headerMap, String httpMethod) { + HttpURLConnection connection = null; + BufferedReader in = null; + try { + connection = buildConnection(urlStr, headerMap, httpMethod); + if (body != null) { + connection.setDoOutput(true); + try (OutputStream os = connection.getOutputStream()) { + byte[] input = body.toString().getBytes(StandardCharsets.UTF_8); + os.write(input, 0, input.length); + } + } + in = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String inputLine; + StringBuilder response = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + return new Result(getResponseCode(connection), EMPTY_STR, response); + } catch (IOException e) { + return new Result(getResponseCode(connection), e.getMessage(), null); + } finally { + if (connection != null) { + connection.disconnect(); + } + if (in != null) { + try { + in.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + private static int getResponseCode(HttpURLConnection connection) { + if (connection == null) { + return HttpStatus.SC_INTERNAL_SERVER_ERROR; + } + try { + return connection.getResponseCode(); + } catch (IOException e) { + return HttpStatus.SC_INTERNAL_SERVER_ERROR; + } + } + + private static HttpURLConnection buildConnection(String urlStr, Map headerMap, + String methodType) throws IOException { + URL url = new URL(urlStr); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod(methodType); + connection.setConnectTimeout(CONNECT_TIMEOUT); + connection.setReadTimeout(SOCKET_TIMEOUT); + for (Map.Entry entry : headerMap.entrySet()) { + connection.addRequestProperty(entry.getKey(), entry.getValue()); + } + return connection; + } +} diff --git a/sermant-integration-tests/xds-service-test/spring-cloud-client/pom.xml b/sermant-integration-tests/xds-service-test/spring-cloud-client/pom.xml index 7f9b803b6a..3a32a48eb8 100644 --- a/sermant-integration-tests/xds-service-test/spring-cloud-client/pom.xml +++ b/sermant-integration-tests/xds-service-test/spring-cloud-client/pom.xml @@ -14,6 +14,7 @@ 8 8 + 1.0.0 @@ -39,6 +40,11 @@ okhttp ${okhttp3.version} + + io.sermant.integration + spring-common + ${project.version} + diff --git a/sermant-integration-tests/xds-service-test/spring-cloud-client/src/main/java/io/sermant/demo/springcloud/client/SpringFlowControlController.java b/sermant-integration-tests/xds-service-test/spring-cloud-client/src/main/java/io/sermant/demo/springcloud/client/SpringFlowControlController.java new file mode 100644 index 0000000000..ff391d157a --- /dev/null +++ b/sermant-integration-tests/xds-service-test/spring-cloud-client/src/main/java/io/sermant/demo/springcloud/client/SpringFlowControlController.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * 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 + * + * http://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 io.sermant.demo.springcloud.client; + +import io.sermant.demo.spring.common.Constants; +import io.sermant.demo.spring.common.entity.Result; +import okhttp3.OkHttpClient; + +import org.apache.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * SpringRouterController + * + * @author zhp + * @since 2025-01-13 + **/ +@RequestMapping("flowControl") +@RestController +public class SpringFlowControlController { + private static final int TIMEOUT = 3000; + + /** + * Test the flow control functionality of the HttpClient client + * + * @param host the service address of upstream service + * @param path request path + * @param version version + * @return result + */ + @GetMapping("testOkHttp3") + public Result testOkHttp3(String host, String path, String version) { + String url = Constants.HTTP_PROTOCOL + host + Constants.SLASH + path; + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS).writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS); + OkHttpClient client = builder.build(); + okhttp3.Request request = new okhttp3.Request.Builder() + .url(url) + .addHeader("version", version) + .build(); + try (okhttp3.Response response = client.newCall(request).execute()) { + if (response.body() != null) { + return new Result(response.code(), "", new String(response.body().bytes())); + } + return new Result(response.code(), "", ""); + } catch (IOException e) { + return new Result(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage(), null); + } + } +} diff --git a/sermant-integration-tests/xds-service-test/spring-common/pom.xml b/sermant-integration-tests/xds-service-test/spring-common/pom.xml new file mode 100644 index 0000000000..74a2130088 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/spring-common/pom.xml @@ -0,0 +1,19 @@ + + + + xds-service-test + io.sermant.integration + 1.0.0 + + 4.0.0 + + spring-common + + + 8 + 8 + + + diff --git a/sermant-integration-tests/xds-service-test/spring-common/src/main/java/io/sermant/demo.spring.common/Constants.java b/sermant-integration-tests/xds-service-test/spring-common/src/main/java/io/sermant/demo.spring.common/Constants.java new file mode 100644 index 0000000000..730c4d43b4 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/spring-common/src/main/java/io/sermant/demo.spring.common/Constants.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * 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 + * + * http://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 io.sermant.demo.spring.common; + +/** + * Constants + * + * @author zhp + * @since 2025-01-13 + **/ +public class Constants { + /** + * http protocol + */ + public static final String HTTP_PROTOCOL = "http://"; + + /** + * slash + */ + public static final String SLASH = "/"; + + private Constants() { + } +} diff --git a/sermant-integration-tests/xds-service-test/spring-common/src/main/java/io/sermant/demo.spring.common/HttpClientType.java b/sermant-integration-tests/xds-service-test/spring-common/src/main/java/io/sermant/demo.spring.common/HttpClientType.java new file mode 100644 index 0000000000..69926a558a --- /dev/null +++ b/sermant-integration-tests/xds-service-test/spring-common/src/main/java/io/sermant/demo.spring.common/HttpClientType.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * 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 + * + * http://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 io.sermant.demo.spring.common; + +/** + * HTTP client types + * + * @author zhp + * @since 2025-01-13 + **/ +public enum HttpClientType { + /** + * httpClient + */ + HTTP_CLIENT("HTTP_CLIENT"), + + /** + * okHttp2 + */ + OK_HTTP2("OK_HTTP2"), + + /** + * okHttp3 + */ + OK_HTTP3("OK_HTTP3"), + + /** + * httpUrlConnection + */ + HTTP_URL_CONNECTION("HTTP_URL_CONNECTION"); + + private final String clientName; + + /** + * Constructor + * + * @param clientName clent name + */ + HttpClientType(String clientName) { + this.clientName = clientName; + } + + public String getClientName() { + return clientName; + } +} diff --git a/sermant-integration-tests/xds-service-test/spring-common/src/main/java/io/sermant/demo.spring.common/entity/Result.java b/sermant-integration-tests/xds-service-test/spring-common/src/main/java/io/sermant/demo.spring.common/entity/Result.java new file mode 100644 index 0000000000..38d78f3151 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/spring-common/src/main/java/io/sermant/demo.spring.common/entity/Result.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2025-2025 Huawei Technologies Co., Ltd. All rights reserved. + * + * 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 + * + * http://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 io.sermant.demo.spring.common.entity; + +/** + * Result Information + * + * @author zhp + * @since 2025-01-13 + */ +public class Result { + /** + * result code + */ + private int code; + + /** + * result message + */ + private String message; + + /** + * result data + */ + private Object data; + + /** + * Constructor + * + * @param code result code + * @param message result message + * @param data result data + */ + public Result(int code, String message, Object data) { + this.code = code; + this.message = message; + this.data = data; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } +} diff --git a/sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/CircuitBreakerController.java b/sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/CircuitBreakerController.java new file mode 100644 index 0000000000..2c87eb926e --- /dev/null +++ b/sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/CircuitBreakerController.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * 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 + * + * http://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 io.sermant.demo.spring.server.flowcontrol; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * CircuitBreakerController + * + * @author zhp + * @since 2025-01-13 + **/ +@RestController +public class CircuitBreakerController { + @Value("${connectTimeout}") + private int timeout; + + @Value("${statusCode}") + private int statusCode; + + /** + * testRequestCircuitBreaker + * + * @return the result of request circuit breaker + */ + @GetMapping("/testRequestCircuitBreaker") + public ResponseEntity testRequestCircuitBreaker() { + try { + Thread.sleep(timeout); + } catch (InterruptedException e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("testRequestCircuitBreaker"); + } + return ResponseEntity.ok().body("testRequestCircuitBreaker"); + } + + /** + * testInstanceCircuitBreaker + * + * @return the result of instance circuit breaker + */ + @GetMapping("/testInstanceCircuitBreaker") + public ResponseEntity testInstanceCircuitBreaker() { + return ResponseEntity.status(statusCode).body(String.valueOf(statusCode)); + } +} diff --git a/sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/FaultController.java b/sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/FaultController.java new file mode 100644 index 0000000000..91c5a90f06 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/FaultController.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * 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 + * + * http://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 io.sermant.demo.spring.server.flowcontrol; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * FaultController + * + * @author zhp + * @since 2025-01-13 + **/ +@RestController +public class FaultController { + /** + * test fault injector + * + * @return the result of fault injector + */ + @RequestMapping("testFault") + public ResponseEntity testFault() { + return ResponseEntity.ok().body(""); + } +} diff --git a/sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/RateLimitController.java b/sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/RateLimitController.java new file mode 100644 index 0000000000..0918c55fe1 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/RateLimitController.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * 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 + * + * http://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 io.sermant.demo.spring.server.flowcontrol; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * RateLimitController + * + * @author zhp + * @since 2025-01-13 + **/ +@RestController +public class RateLimitController { + /** + * test rate limit + * + * @return the result of rate limit + */ + @RequestMapping("testRateLimit") + public ResponseEntity testRateLimit() { + return ResponseEntity.ok().body("testRateLimit"); + } +} diff --git a/sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/RetryController.java b/sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/RetryController.java new file mode 100644 index 0000000000..c30ae36860 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/spring-server/src/main/java/io/sermant/demo/spring/server/flowcontrol/RetryController.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * 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 + * + * http://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 io.sermant.demo.spring.server.flowcontrol; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * RetryController + * + * @author zhp + * @since 2025-01-13 + **/ +@RestController +public class RetryController { + @Value("${retryTimeout}") + private int timeout; + + private int requestCount = 0; + + /** + * testGateWayError + * + * @return the result of retry on GetWayError + */ + @RequestMapping("testGateWayError") + public ResponseEntity testGateWayError() { + if (requestCount > 0) { + requestCount++; + return ResponseEntity.ok().body(String.valueOf(requestCount)); + } + requestCount++; + return ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(String.valueOf(requestCount)); + } + + /** + * testRetryOnHeader + * + * @return the result of retry on response header + */ + @GetMapping("/testRetryOnHeader") + public ResponseEntity testRetryOnHeader() { + if (requestCount > 0) { + requestCount++; + return ResponseEntity.ok().body(String.valueOf(requestCount)); + } + requestCount++; + HttpHeaders headers = new HttpHeaders(); + headers.add("needRetry", "true"); + return ResponseEntity.status(HttpStatus.BAD_GATEWAY).headers(headers).body(String.valueOf(requestCount)); + } + + /** + * testRetryOnStatusCode + * + * @return the result of retry on status code + */ + @GetMapping("/testRetryOnStatusCode") + public ResponseEntity testRetryOnStatusCode() { + if (requestCount > 0) { + requestCount++; + return ResponseEntity.ok().body(String.valueOf(requestCount)); + } + requestCount++; + return ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(String.valueOf(requestCount)); + } + + /** + * testTimeOut + * + * @return the result of retry on request time out + */ + @GetMapping("/testTimeOut") + public ResponseEntity testTimeOut() { + if (requestCount > 0) { + requestCount++; + return ResponseEntity.ok().body(String.valueOf(requestCount)); + } + requestCount++; + try { + Thread.sleep(timeout); + } catch (InterruptedException e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(String.valueOf(requestCount)); + } + return ResponseEntity.ok().body(String.valueOf(requestCount)); + } + + /** + * testConnectError + * + * @return the result of retry on 4xx error + */ + @GetMapping("/test4xxError") + public ResponseEntity test4xxError() { + if (requestCount > 0) { + requestCount++; + return ResponseEntity.ok().body(String.valueOf(requestCount)); + } + requestCount++; + return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(String.valueOf(requestCount)); + } + + /** + * test5xxError + * + * @return the result of retry on 5xx error + */ + @GetMapping("/test5xxError") + public ResponseEntity test5xxError() { + if (requestCount > 0) { + requestCount++; + return ResponseEntity.ok().body(String.valueOf(requestCount)); + } + requestCount++; + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(String.valueOf(requestCount)); + } + + /** + * get the request count + * + * @return the result of request count + */ + @GetMapping("/getRequestCount") + public ResponseEntity getRequestCount() { + return ResponseEntity.ok().body(String.valueOf(requestCount)); + } + + /** + * reset the request count + * + * @return the result of request count + */ + @GetMapping("/reset") + public ResponseEntity reset() { + requestCount = 0; + return ResponseEntity.ok().body(String.valueOf(requestCount)); + } +} diff --git a/sermant-integration-tests/xds-service-test/spring-server/src/main/resources/application.yml b/sermant-integration-tests/xds-service-test/spring-server/src/main/resources/application.yml index 2d4efb462b..82d2fe55b4 100644 --- a/sermant-integration-tests/xds-service-test/spring-server/src/main/resources/application.yml +++ b/sermant-integration-tests/xds-service-test/spring-server/src/main/resources/application.yml @@ -7,3 +7,6 @@ spring: enabled: ${ZOOKEEPER_ENABLED:false} server: port: 8081 +connectTimeout: 5000 +retryTimeout: 10000 +statusCode: 200 diff --git a/sermant-integration-tests/xds-service-test/xds-service-integration-test/pom.xml b/sermant-integration-tests/xds-service-test/xds-service-integration-test/pom.xml index 54df56a05b..a54a698b49 100644 --- a/sermant-integration-tests/xds-service-test/xds-service-integration-test/pom.xml +++ b/sermant-integration-tests/xds-service-test/xds-service-integration-test/pom.xml @@ -20,6 +20,7 @@ 1.7.35 5.8.1 3.0.0 + 1.0.0 @@ -59,6 +60,11 @@ ${commons-log.version} test + + com.alibaba + fastjson + ${fastjson.version} + diff --git a/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/entity/HttpClientType.java b/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/entity/HttpClientType.java new file mode 100644 index 0000000000..b871912af2 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/entity/HttpClientType.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * 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 + * + * http://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 io.sermant.xds.service.entity; + +/** + * HTTP client types + * + * @author zhp + * @since 2025-01-13 + **/ +public enum HttpClientType { + /** + * httpClient + */ + HTTP_CLIENT("HTTP_CLIENT"), + + /** + * okHttp2 + */ + OK_HTTP2("OK_HTTP2"), + + /** + * okHttp3 + */ + OK_HTTP3("OK_HTTP3"), + + /** + * httpUrlConnection + */ + HTTP_URL_CONNECTION("HTTP_URL_CONNECTION"); + + private final String clientName; + + /** + * Constructor + * + * @param clientName clent name + */ + HttpClientType(String clientName) { + this.clientName = clientName; + } + + public String getClientName() { + return clientName; + } + +} diff --git a/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/entity/Result.java b/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/entity/Result.java new file mode 100644 index 0000000000..42b5583dec --- /dev/null +++ b/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/entity/Result.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2025-2025 Huawei Technologies Co., Ltd. All rights reserved. + * + * 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 + * + * http://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 io.sermant.xds.service.entity; + +/** + * Result Information + * + * @author zhp + * @since 2025-01-13 + */ +public class Result { + /** + * result code + */ + private int code; + + /** + * result message + */ + private String message; + + /** + * result data + */ + private Object data; + + /** + * Constructor + * + * @param code result code + * @param message result message + * @param data result data + */ + public Result(int code, String message, Object data) { + this.code = code; + this.message = message; + this.data = data; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } +} diff --git a/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/flowcontrol/XdsFlowControlTest.java b/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/flowcontrol/XdsFlowControlTest.java new file mode 100644 index 0000000000..d255c200d6 --- /dev/null +++ b/sermant-integration-tests/xds-service-test/xds-service-integration-test/src/test/java/io/sermant/xds/service/flowcontrol/XdsFlowControlTest.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2025-2025 Sermant Authors. All rights reserved. + * + * 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 + * + * http://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 io.sermant.xds.service.flowcontrol; + +import com.alibaba.fastjson.JSONObject; +import io.sermant.xds.service.entity.HttpClientType; +import io.sermant.xds.service.entity.Result; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * xDS flow control test + * + * @author zhp + * @since 2025-01-13 + **/ +public class XdsFlowControlTest { + private static final int CONNECT_TIMEOUT = 30000; + + private static final int SOCKET_TIMEOUT = 30000; + + private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(25); + + private static final String OKHTTP_URL_PREFIX = + "http://127.0.0.1:8080/flowControl/testOkHttp2?host=spring-server&version="; + + private static final String OKHTTP3_URL_PREFIX = + "http://127.0.0.1:8082/flowControl/testOkHttp3?host=spring-server&version="; + + private static final String HTTP_CLIENT_URL_PREFIX = + "http://127.0.0.1:8080/flowControl/testHttpClient?host=spring-server&version="; + + private static final String HTTP_URL_CONNECTION_URL_PREFIX = + "http://127.0.0.1:8080/flowControl/testHttpUrlConnection?host=spring-server&version="; + + /** + * test fault + */ + @Test + @EnabledIfSystemProperty(named = "xds.service.integration.test.type", matches = "FLOW_CONTROL_FAULT") + public void testFault() throws InterruptedException { + // Test the case where the request delay probability is 0 + testFaultProbability(buildFaultUrl(HttpClientType.HTTP_CLIENT, "v1"), 0, 0, 2000); + testFaultProbability(buildFaultUrl(HttpClientType.OK_HTTP2, "v1"), 0, 0, 2000); + testFaultProbability(buildFaultUrl(HttpClientType.HTTP_URL_CONNECTION, "v1"), 0, 0, 2000); + testFaultProbability(buildFaultUrl(HttpClientType.OK_HTTP3, "v1"), 0, 0, 2000); + + // Test the case where the request delay probability is 50% + testFaultProbability(buildFaultUrl(HttpClientType.HTTP_CLIENT, "v2"), 10, 40, 15000); + testFaultProbability(buildFaultUrl(HttpClientType.OK_HTTP2, "v2"), 10, 40, 15000); + testFaultProbability(buildFaultUrl(HttpClientType.HTTP_URL_CONNECTION, "v2"), 10, 40, 15000); + testFaultProbability(buildFaultUrl(HttpClientType.OK_HTTP3, "v2"), 10, 40, 15000); + + // Test the case where the request delay probability is 100% + testFaultProbability(buildFaultUrl(HttpClientType.HTTP_CLIENT, "v3"), 50, 50, 15000); + testFaultProbability(buildFaultUrl(HttpClientType.OK_HTTP2, "v3"), 50, 50, 15000); + testFaultProbability(buildFaultUrl(HttpClientType.HTTP_URL_CONNECTION, "v3"), 50, 50, 15000); + testFaultProbability(buildFaultUrl(HttpClientType.OK_HTTP3, "v3"), 50, 50, 15000); + + // Test the case where the request abort probability is 0 + testFaultProbability(buildFaultUrl(HttpClientType.HTTP_CLIENT, "v4"), 0, 0, 2000); + testFaultProbability(buildFaultUrl(HttpClientType.OK_HTTP2, "v4"), 0, 0, 2000); + testFaultProbability(buildFaultUrl(HttpClientType.HTTP_URL_CONNECTION, "v4"), 0, 0, 2000); + testFaultProbability(buildFaultUrl(HttpClientType.OK_HTTP3, "v4"), 0, 0, 2000); + + // Test the case where the request abort probability is 50% + testFaultProbability(buildFaultUrl(HttpClientType.HTTP_CLIENT, "v5"), 10, 40, 2000); + testFaultProbability(buildFaultUrl(HttpClientType.OK_HTTP2, "v5"), 10, 40, 2000); + testFaultProbability(buildFaultUrl(HttpClientType.HTTP_URL_CONNECTION, "v5"), 10, 40, 2000); + testFaultProbability(buildFaultUrl(HttpClientType.OK_HTTP3, "v5"), 10, 40, 2000); + + // Test the case where the request abort probability is 100% + testFaultProbability(buildFaultUrl(HttpClientType.HTTP_CLIENT, "v6"), 50, 50, 2000); + testFaultProbability(buildFaultUrl(HttpClientType.OK_HTTP2, "v6"), 50, 50, 2000); + testFaultProbability(buildFaultUrl(HttpClientType.HTTP_URL_CONNECTION, "v6"), 50, 50, 2000); + testFaultProbability(buildFaultUrl(HttpClientType.OK_HTTP3, "v6"), 50, 50, 2000); + } + + /** + * test retry + */ + @Test + @EnabledIfSystemProperty(named = "xds.service.integration.test.type", matches = "FLOW_CONTROL_RETRY") + public void testRetry() { + // Test does not meet the matching rules, retry will not be triggered + resetRequestCount(); + Result result = doGet(buildGateWayErrorUrl(HttpClientType.HTTP_CLIENT, "v2")); + Assertions.assertEquals(HttpStatus.SC_BAD_GATEWAY, result.getCode()); + resetRequestCount(); + result = doGet(buildGateWayErrorUrl(HttpClientType.OK_HTTP2, "v2")); + Assertions.assertEquals(HttpStatus.SC_BAD_GATEWAY, result.getCode()); + resetRequestCount(); + result = doGet(buildGateWayErrorUrl(HttpClientType.OK_HTTP3, "v2")); + Assertions.assertEquals(HttpStatus.SC_BAD_GATEWAY, result.getCode()); + resetRequestCount(); + result = doGet(buildGateWayErrorUrl(HttpClientType.HTTP_URL_CONNECTION, "v2")); + Assertions.assertEquals(HttpStatus.SC_BAD_GATEWAY, result.getCode()); + resetRequestCount(); + + doGet(buildGateWayErrorUrl(HttpClientType.HTTP_CLIENT, "v1")); + doGet(buildGateWayErrorUrl(HttpClientType.HTTP_CLIENT, "v1")); + + // Test meet the matching rules, retry will not be triggered + result = doGet(buildGateWayErrorUrl(HttpClientType.HTTP_CLIENT, "v1")); + Assertions.assertEquals("3", result.getData()); + result = doGet(buildGateWayErrorUrl(HttpClientType.OK_HTTP2, "v1")); + Assertions.assertEquals("3", result.getData()); + result = doGet(buildGateWayErrorUrl(HttpClientType.OK_HTTP2, "v1")); + Assertions.assertEquals("4", result.getData()); + result = doGet(buildGateWayErrorUrl(HttpClientType.HTTP_URL_CONNECTION, "v1")); + Assertions.assertEquals("4", result.getData()); + result = doGet(buildGateWayErrorUrl(HttpClientType.OK_HTTP3, "v1")); + Assertions.assertEquals("5", result.getData()); + resetRequestCount(); + + // Test the retry be triggered + testAllRetryCondition("http://127.0.0.1:8080/flowControl/testHttpClient"); + testAllRetryCondition("http://127.0.0.1:8080/flowControl/testOkHttp2"); + testAllRetryCondition("http://127.0.0.1:8080/flowControl/testHttpUrlConnection"); + testAllRetryCondition("http://127.0.0.1:8082/flowControl/testOkHttp3"); + } + + private static void testAllRetryCondition(String urlPrefix) { + // Test the case of retries when a gateway error occurs + testRequestFailureCount(urlPrefix + "?host=spring-server&version=v1&path=testGateWayError"); + // Test the case of retries when the response contains the specified response header + testRequestFailureCount(urlPrefix + "?host=spring-server&version=v1&path=testRetryOnHeader"); + // Test the case of retries when the response contains the specified response code + testRequestFailureCount(urlPrefix + "?host=spring-server&version=v1&path=testRetryOnStatusCode"); + // Test the case of retries when a request timeout occurs + testRequestFailureCount(urlPrefix + "?host=spring-server&version=v1&path=testTimeOut"); + // Test the case of retries when the response code is 4xx + testRequestFailureCount(urlPrefix + "?host=spring-server&version=v1&path=test4xxError"); + // Test the case of retries when the response code is 5xx + testRequestFailureCount(urlPrefix + "?host=spring-server&version=v1&path=test5xxError"); + } + + /** + * test Circuit Breaker + */ + @Test + @EnabledIfSystemProperty(named = "xds.service.integration.test.type", matches = "FLOW_CONTROL_CIRCUIT_BREAKER") + public void testCircuitBreaker() throws InterruptedException { + // Test Circuit Breaker Function Based on Active Request Count + EXECUTOR_SERVICE.execute(() -> doGet(buildRequestCircuitBreakerUrl(HttpClientType.HTTP_CLIENT))); + EXECUTOR_SERVICE.execute(() -> doGet(buildRequestCircuitBreakerUrl(HttpClientType.OK_HTTP3))); + Thread.sleep(500); + EXECUTOR_SERVICE.execute(() -> { + Result result = doGet(buildRequestCircuitBreakerUrl(HttpClientType.HTTP_CLIENT)); + Assertions.assertNotEquals(HttpStatus.SC_OK, result.getCode()); + }); + EXECUTOR_SERVICE.execute(() -> { + Result result = doGet(buildRequestCircuitBreakerUrl(HttpClientType.OK_HTTP2)); + Assertions.assertNotEquals(HttpStatus.SC_OK, result.getCode()); + }); + EXECUTOR_SERVICE.execute(() -> { + Result result = doGet(buildRequestCircuitBreakerUrl(HttpClientType.OK_HTTP3)); + Assertions.assertNotEquals(HttpStatus.SC_OK, result.getCode()); + }); + EXECUTOR_SERVICE.execute(() -> { + Result result = doGet(buildRequestCircuitBreakerUrl(HttpClientType.HTTP_URL_CONNECTION)); + Assertions.assertNotEquals(HttpStatus.SC_OK, result.getCode()); + }); + + // Test Instance Circuit Breaker Base on GateWayError + testRemovedCircuitBreakerInstance("v2"); + + // Test Instance Circuit Breaker Base on 5XX error + testRemovedCircuitBreakerInstance("v3"); + Thread.sleep(10000); + + // Test instance recovery + Result result = doGet(buildInstanceCircuitBreakerUrl(HttpClientType.HTTP_CLIENT, "v2")); + Result result1 = doGet(buildInstanceCircuitBreakerUrl(HttpClientType.HTTP_CLIENT, "v2")); + Assertions.assertTrue(HttpStatus.SC_OK != result.getCode() || result1.getCode() != HttpStatus.SC_OK); + result = doGet(buildInstanceCircuitBreakerUrl(HttpClientType.OK_HTTP2, "v2")); + result1 = doGet(buildInstanceCircuitBreakerUrl(HttpClientType.OK_HTTP2, "v2")); + Assertions.assertTrue(HttpStatus.SC_OK != result.getCode() || result1.getCode() != HttpStatus.SC_OK); + result = doGet(buildInstanceCircuitBreakerUrl(HttpClientType.OK_HTTP3, "v2")); + result1 = doGet(buildInstanceCircuitBreakerUrl(HttpClientType.OK_HTTP3, "v2")); + Assertions.assertTrue(HttpStatus.SC_OK != result.getCode() || result1.getCode() != HttpStatus.SC_OK); + result = doGet(buildInstanceCircuitBreakerUrl(HttpClientType.HTTP_URL_CONNECTION, "v2")); + result1 = doGet(buildInstanceCircuitBreakerUrl(HttpClientType.HTTP_URL_CONNECTION, "v2")); + Assertions.assertTrue(HttpStatus.SC_OK != result.getCode() || result1.getCode() != HttpStatus.SC_OK); + } + + private static void testRemovedCircuitBreakerInstance(String version) { + for (int i = 0; i < 40; i++) { + doGet(buildInstanceCircuitBreakerUrl(HttpClientType.HTTP_CLIENT, version)); + doGet(buildInstanceCircuitBreakerUrl(HttpClientType.OK_HTTP3, version)); + } + for (int i = 0; i < 40; i++) { + Result result = doGet(buildInstanceCircuitBreakerUrl(HttpClientType.HTTP_CLIENT, version)); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + result = doGet(buildInstanceCircuitBreakerUrl(HttpClientType.OK_HTTP2, version)); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + result = doGet(buildInstanceCircuitBreakerUrl(HttpClientType.OK_HTTP3, version)); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + result = doGet(buildInstanceCircuitBreakerUrl(HttpClientType.HTTP_URL_CONNECTION, version)); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + } + } + + /** + * test Circuit Breaker + */ + @Test + @EnabledIfSystemProperty(named = "xds.service.integration.test.type", matches = "FLOW_CONTROL_RATE_LIMIT") + public void testRateLimit() throws InterruptedException { + // Test the case where the request delay probability is 0 + for (int i = 0; i < 10; i++) { + Result result = doGet(buildRateLimitUrl(HttpClientType.HTTP_CLIENT, "v1")); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + result = doGet(buildRateLimitUrl(HttpClientType.OK_HTTP2, "v1")); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + result = doGet(buildRateLimitUrl(HttpClientType.OK_HTTP3, "v1")); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + result = doGet(buildRateLimitUrl(HttpClientType.HTTP_URL_CONNECTION, "v1")); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + } + + // Test the case where the probability is 50% + testRateLimitProbability(buildRateLimitUrl(HttpClientType.HTTP_CLIENT, "v2"), 30, 70); + testRateLimitProbability(buildRateLimitUrl(HttpClientType.OK_HTTP2, "v2"), 30, 70); + testRateLimitProbability(buildRateLimitUrl(HttpClientType.OK_HTTP3, "v2"), 30, 70); + testRateLimitProbability(buildRateLimitUrl(HttpClientType.HTTP_URL_CONNECTION, "v2"), 30, 70); + + // Test the case where the probability is 100% + testRateLimitProbability(buildRateLimitUrl(HttpClientType.HTTP_CLIENT, "v3"), 90, 104); + testRateLimitProbability(buildRateLimitUrl(HttpClientType.OK_HTTP2, "v3"), 90, 104); + testRateLimitProbability(buildRateLimitUrl(HttpClientType.OK_HTTP3, "v3"), 90, 104); + testRateLimitProbability(buildRateLimitUrl(HttpClientType.HTTP_URL_CONNECTION, "v3"), 90, 104); + + // Test token filling + Thread.sleep(10000); + Result result = doGet(buildRateLimitUrl(HttpClientType.HTTP_CLIENT, "v2")); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + result = doGet(buildRateLimitUrl(HttpClientType.HTTP_URL_CONNECTION, "v2")); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + result = doGet(buildRateLimitUrl(HttpClientType.OK_HTTP2, "v2")); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + result = doGet(buildRateLimitUrl(HttpClientType.OK_HTTP3, "v2")); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + } + + private static void testRateLimitProbability(String url, int minFailureCount, int maxFailureCount) { + Result result; + int requestFailureCount = 0; + for (int i = 0; i < 104; i++) { + result = doGet(url); + if (result.getCode() != HttpStatus.SC_OK) { + requestFailureCount++; + } + } + Assertions.assertTrue(requestFailureCount >= minFailureCount && requestFailureCount <= maxFailureCount); + } + + private static void testRequestFailureCount(String url) { + Result result = doGet(url); + Assertions.assertEquals(HttpStatus.SC_OK, result.getCode()); + Assertions.assertEquals(3, getRequestCount()); + resetRequestCount(); + } + + private static void testFaultProbability(String url, int minFailureCount, int maxFailureCount, int sleepTime) + throws InterruptedException { + final AtomicInteger faultCount = new AtomicInteger(); + for (int i = 0; i < 50; i++) { + EXECUTOR_SERVICE.execute(() -> { + long start = System.currentTimeMillis(); + Result result = doGet(url); + long elapsed = System.currentTimeMillis() - start; + if (result.getCode() != HttpStatus.SC_OK || elapsed > 5000) { + faultCount.incrementAndGet(); + } + }); + } + Thread.sleep(sleepTime); + int count = faultCount.get(); + Assertions.assertTrue(count >= minFailureCount && count <= maxFailureCount); + } + + private static void resetRequestCount() { + // The server has two instances, and it needs to be called twice + doGet("http://127.0.0.1:8080/flowControl/testHttpClient?host=spring-server&version=v1&path=reset"); + doGet("http://127.0.0.1:8080/flowControl/testHttpClient?host=spring-server&version=v1&path=reset"); + } + + private static int getRequestCount() { + // The server has two instances, and it needs to be called twice + Result result = doGet("http://127.0.0.1:8080/flowControl/testHttpClient?host=spring-server&version=v1&path=getRequestCount"); + int requestFailureCount = Integer.parseInt(result.getData().toString()); + result = doGet("http://127.0.0.1:8080/flowControl/testHttpClient?host=spring-server&version=v1&path=getRequestCount"); + return requestFailureCount + Integer.parseInt(result.getData().toString()); + } + + + private static Result doGet(String url) { + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT) + .setSocketTimeout(SOCKET_TIMEOUT).build(); + HttpGet httpGet = new HttpGet(url); + httpGet.setConfig(requestConfig); + try (CloseableHttpResponse response = httpClient.execute(httpGet)) { + return JSONObject.parseObject(EntityUtils.toString(response.getEntity()), Result.class); + } + } catch (IOException e) { + return new Result(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage(), null); + } + } + + private static String buildRateLimitUrl(HttpClientType clientType, String version) { + return buildUrl(clientType, version, "&path=testRateLimit"); + } + + private static String buildInstanceCircuitBreakerUrl(HttpClientType clientType, String version) { + return buildUrl(clientType, version, "&path=testInstanceCircuitBreaker"); + } + + private static String buildRequestCircuitBreakerUrl(HttpClientType clientType) { + return buildUrl(clientType, "v1", "&path=testRequestCircuitBreaker"); + } + + private static String buildGateWayErrorUrl(HttpClientType clientType, String version) { + return buildUrl(clientType, version, "&path=testGateWayError"); + } + + private static String buildFaultUrl(HttpClientType clientType, String version) { + return buildUrl(clientType, version, "&path=testFault"); + } + + private static String buildUrl(HttpClientType clientType, String version, String path) { + if (clientType == HttpClientType.OK_HTTP2) { + return OKHTTP_URL_PREFIX + version + path; + } + if (clientType == HttpClientType.OK_HTTP3) { + return OKHTTP3_URL_PREFIX + version + path; + } + if (clientType == HttpClientType.HTTP_CLIENT) { + return HTTP_CLIENT_URL_PREFIX + version + path; + } + if (clientType == HttpClientType.HTTP_URL_CONNECTION) { + return HTTP_URL_CONNECTION_URL_PREFIX + version + path; + } + return ""; + } +} diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-common/src/main/java/io/sermant/flowcontrol/common/xds/retry/condition/ConnectFailureRetryCondition.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-common/src/main/java/io/sermant/flowcontrol/common/xds/retry/condition/ConnectFailureRetryCondition.java index 5896b74b77..2354c2919e 100644 --- a/sermant-plugins/sermant-flowcontrol/flowcontrol-common/src/main/java/io/sermant/flowcontrol/common/xds/retry/condition/ConnectFailureRetryCondition.java +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-common/src/main/java/io/sermant/flowcontrol/common/xds/retry/condition/ConnectFailureRetryCondition.java @@ -21,9 +21,10 @@ import io.sermant.flowcontrol.common.util.StringUtils; import io.sermant.flowcontrol.common.xds.retry.RetryCondition; +import java.io.InterruptedIOException; import java.net.ConnectException; import java.net.NoRouteToHostException; -import java.net.SocketTimeoutException; +import java.util.Locale; import java.util.concurrent.TimeoutException; /** @@ -53,12 +54,17 @@ private boolean isConnectErrorException(Throwable ex) { if (isRequestTimeoutException(ex)) { return false; } - return ex instanceof SocketTimeoutException || ex instanceof ConnectException || ex instanceof TimeoutException + return ex instanceof InterruptedIOException || ex instanceof ConnectException || ex instanceof TimeoutException || ex instanceof NoRouteToHostException; } private boolean isRequestTimeoutException(Throwable ex) { - return (ex instanceof SocketTimeoutException || ex instanceof TimeoutException) - && !StringUtils.isEmpty(ex.getMessage()) && ex.getMessage().contains("Read timed out"); + String message = ex.getMessage(); + if (StringUtils.isEmpty(message)) { + return false; + } + message = message.toLowerCase(Locale.ROOT); + return (ex instanceof InterruptedIOException || ex instanceof TimeoutException) + && (message.contains("read timed out") || message.contains("timeout")); } } diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-common/src/main/java/io/sermant/flowcontrol/common/xds/retry/condition/ResetRetryCondition.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-common/src/main/java/io/sermant/flowcontrol/common/xds/retry/condition/ResetRetryCondition.java index 3875486563..740b16b2f0 100644 --- a/sermant-plugins/sermant-flowcontrol/flowcontrol-common/src/main/java/io/sermant/flowcontrol/common/xds/retry/condition/ResetRetryCondition.java +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-common/src/main/java/io/sermant/flowcontrol/common/xds/retry/condition/ResetRetryCondition.java @@ -22,7 +22,7 @@ import io.sermant.flowcontrol.common.xds.retry.RetryCondition; import java.io.IOException; -import java.net.SocketTimeoutException; +import java.io.InterruptedIOException; import java.util.Locale; import java.util.concurrent.TimeoutException; @@ -50,15 +50,16 @@ public boolean isNeedRetry(Retry retry, Throwable ex, String statusCode, Object if (StringUtils.isEmpty(message)) { return false; } - if (isRequestTimeoutException(ex, message)) { + message = message.toLowerCase(Locale.ROOT); + if (isRequestTimeoutException(realException, message)) { return true; } return realException instanceof IOException - && (message.contains("Connection reset") || message.toLowerCase(Locale.ROOT).contains("broken pipe")); + && (message.contains("connection reset") || message.toLowerCase(Locale.ROOT).contains("broken pipe")); } private static boolean isRequestTimeoutException(Throwable ex, String message) { - return (ex instanceof SocketTimeoutException || ex instanceof TimeoutException) - && message.contains("Read timed out"); + return (ex instanceof InterruptedIOException || ex instanceof TimeoutException) + && (message.contains("read timed out") || message.contains("timeout")); } }