This is an automated email from the ASF dual-hosted git repository.

yuluo-yx pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new 48120c1366 Stabilize k8s ingress startup healthchecks (#6361)
48120c1366 is described below

commit 48120c13668150b9e9b4c9f0786f671451d58ecc
Author: aias00 <[email protected]>
AuthorDate: Mon Jun 8 16:55:11 2026 +0800

    Stabilize k8s ingress startup healthchecks (#6361)
    
    * goalx: snapshot before shenyu-analysis
    
    * chore(ci): optimize workflow build cache and mvnd parallelism
    
    * Stabilize integration startup healthchecks
    
    The grpc ingress CI failure timed out while a healthcheck curl waited on a 
newly created kind deployment. The workflow now waits for shenyu-ingress 
deployments to roll out before HTTP probing, bounds each curl attempt, and 
preserves cluster diagnostics even when startup fails.
    
    The HTTP integration workflow also exposed a Sentinel fallback flake where 
a POST request relied on an external redirect fallback and hung until the 
180-second client timeout. The Sentinel test now uses internal fallback 
dispatch, and the example accepted endpoint supports POST for that fallback 
path.
    
    Constraint: GitHub-hosted kind and docker-compose startup can vary with 
runner load and JVM boot time
    
    Rejected: Only increase the Actions timeout | leaves a hanging curl and no 
readiness boundary
    
    Rejected: Only add diagnostic logging | improves evidence but still allows 
avoidable flakes
    
    Rejected: Keep Sentinel fallback as a client-followed 302 redirect | keeps 
the flaky non-2xx fallback boundary
    
    Confidence: medium
    
    Scope-risk: moderate
    
    Directive: Keep k8s ingress healthchecks bounded and rollout-aware; do not 
reintroduce unbounded curl startup waits
    
    Tested: bash -n for helper and ingress healthcheck scripts
    
    Tested: git diff --check
    
    Tested: local fake kubectl plus HTTP 200 success path
    
    Tested: local fake kubectl plus unreachable service failure path
    
    Tested: ./mvnw -f shenyu-examples/shenyu-examples-http/pom.xml -DskipTests 
-Dmaven.javadoc.skip=true compile
    
    Tested: ./mvnw -f shenyu-integrated-test/pom.xml -pl 
shenyu-integrated-test-http -am -DskipTests -Pit -Dmaven.javadoc.skip=true 
test-compile
    
    Not-tested: full GitHub Actions kind matrix run
    
    Not-tested: local docker-compose shenyu-integrated-test-http runtime
    
    * Keep ingress healthchecks compatible with CI kubectl
    
    The k8s ingress startup helper must run on the kubectl v1.24 binary 
installed by the workflow. That binary rejects rollout status with deployment 
--all, so enumerate deployments first and wait on each named resource becoming 
Available before starting HTTP health probes.
    
    Constraint: CI installs kubectl v1.24.14
    
    Rejected: Keep rollout status deployment --all | rejected by the linked PR 
6361 CI job with unknown flag: --all
    
    Confidence: high
    
    Scope-risk: narrow
    
    Directive: Keep this helper compatible with the kubectl version pinned in 
integrated-test-k8s-ingress.yml
    
    Tested: bash -n for helper and ingress healthcheck scripts
    
    Tested: git diff --check for helper
    
    Tested: fake kubectl/curl success, failed service, and no-deployment helper 
paths
    
    * Stop k8s example probes from killing ready services
    
    The k8s ingress apache-dubbo job reached the new bounded healthcheck path, 
then failed because the example service nodePort stayed unreachable. The 
diagnostic dump showed kubelet restarting the Dubbo example after liveness 
probes hit localhost:8081, while the application, containerPort, and service 
all use 8011. The SOFA example had the same stale probe port against an 
application that serves health on 28011, so align both probes with their real 
server ports.
    
    Constraint: The k8s ingress workflow now waits on deployment availability 
and then verifies the example NodePort health endpoints
    
    Rejected: Increase healthcheck retries only | would leave kubelet 
repeatedly restarting a healthy app due to the wrong probe port
    
    Confidence: high
    
    Scope-risk: narrow
    
    Directive: Keep k8s liveness probe ports aligned with each example 
application's server.port and service targetPort
    
    Tested: git diff --check for changed manifests
    
    Tested: Ruby YAML parser for changed manifests
    
    Not-tested: local kind run; CI job is the available end-to-end validation
    
    * Expose ingress healthcheck node ports in kind
    
    The apache-dubbo ingress job now reaches healthy pods with zero restarts, 
but the host healthcheck still curls localhost:31187. In kind, localhost access 
requires explicit extraPortMappings; only the ingress controller port 30095 was 
mapped, so service NodePorts such as 31187 were unreachable from the runner 
even when Kubernetes reported the service and pod ready.
    
    Map each active ingress services.list localhost port through the case kind 
config. Also align the latent SOFA services.list port with its actual example 
service NodePort and map it for future activation.
    
    Constraint: GitHub Actions probes services from the host runner via 
localhost against a kind cluster
    
    Rejected: Probe only Kubernetes deployment readiness | misses host-to-kind 
NodePort reachability that the tests depend on
    
    Rejected: Increase curl retries | localhost can never reach unmapped kind 
NodePorts
    
    Confidence: high
    
    Scope-risk: narrow
    
    Directive: Any localhost port added to an ingress services.list must also 
be present as a kind extraPortMappings hostPort
    
    Tested: services.list localhost ports match kind hostPort mappings for 
http, grpc, websocket, apache-dubbo, and sofa
    
    Tested: Ruby YAML parser for changed kind configs and related manifests
    
    Tested: git diff --check for changed files
    
    Not-tested: local kind run; CI job is the available end-to-end validation
    
    * Align grpc ingress health probes with app path
    
    After NodePort mappings were fixed, apache-dubbo passed and the remaining 
k8s ingress failure moved to grpc. The grpc example was restarted by liveness 
because the probe hit /actuator/health while the app serves under the /grpc 
context path. The host healthcheck also probed NodePort 31184, which maps 
service port 8080, while the application HTTP server is 55290 exposed as 
NodePort 31183.
    
    Use the same health endpoint shape as the docker-compose grpc integration 
check: /grpc/actuator/health on port 55290, exposed to the runner as 
localhost:31183 in kind.
    
    Constraint: shenyu-examples-grpc sets server.servlet.context-path=/grpc and 
server.port=55290
    
    Rejected: Keep probing 31184 | that NodePort maps port 8080 rather than the 
app health server
    
    Rejected: Increase retries | the liveness path was wrong and caused restarts
    
    Confidence: high
    
    Scope-risk: narrow
    
    Directive: Keep grpc k8s health probes consistent with 
server.servlet.context-path and service NodePort 31183
    
    Tested: services.list localhost ports match kind hostPort mappings for all 
ingress cases
    
    Tested: Ruby YAML parser for changed manifests
    
    Tested: git diff --check
    
    Not-tested: local kind run; CI job is the available end-to-end validation
    
    * Use stable websocket ingress health endpoint
    
    The websocket ingress preflight polled a historical HTTP path that is not 
implemented by the annotation websocket example. CI reached the service but 
received repeated 404 responses, so the cluster startup step failed before the 
real websocket test could run.
    
    Use the actuator health endpoint that the same example already exposes for 
Kubernetes liveness, and give the example a longer initial liveness delay to 
avoid startup probe restarts observed in CI diagnostics.
    
    Constraint: The preflight check must use an HTTP URL, while the actual 
integration coverage remains the ws://localhost:30095/myWs test.
    
    Rejected: Add a dummy HTTP handler for /new/feature/get/mapping/path | it 
would preserve a stale health endpoint unrelated to websocket coverage.
    
    Confidence: high
    
    Scope-risk: narrow
    
    Tested: git diff --check
    
    Tested: Ruby YAML parse for websocket Kubernetes manifest
    
    Tested: kind hostPort mapping consistency check for ingress service lists
    
    Tested: ./mvnw -f shenyu-integrated-test/pom.xml -pl 
shenyu-integrated-test-k8s-ingress-websocket -am -DskipTests -Pit 
-Dmaven.javadoc.skip=true test-compile
    
    Not-tested: full Kubernetes ingress websocket workflow locally; requires CI 
kind/docker environment
    
    * Avoid invalid websocket ingress HTTP health probe
    
    The websocket ingress preflight still failed after moving to the example 
actuator endpoint because the service is registered in the ingress controller 
as a websocket upstream. HTTP actuator probes against that path consistently 
returned 404, while the actual integration test only needs the shenyu ingress 
controller on port 30095 and then exercises ws://localhost:30095/myWs.
    
    Use the ingress controller's own actuator health endpoint for websocket 
preflight, remove the unused host mapping for the example NodePort, and add a 
startup probe so the example liveness check does not restart the app during 
slow startup.
    
    Constraint: Websocket ingress health preflight must not depend on an HTTP 
request flowing through a websocket upstream.
    
    Rejected: Keep probing localhost:31188 | CI showed it returns 404 after 
websocket upstream registration and does not represent the test path.
    
    Confidence: high
    
    Scope-risk: narrow
    
    Tested: git diff --check
    
    Tested: Ruby YAML parse for websocket Kubernetes manifest and kind config
    
    Tested: kind hostPort mapping consistency check for ingress service lists
    
    Tested: ./mvnw -f shenyu-integrated-test/pom.xml -pl 
shenyu-integrated-test-k8s-ingress-websocket -am -DskipTests -Pit 
-Dmaven.javadoc.skip=true test-compile
    
    Not-tested: full Kubernetes ingress websocket workflow locally; requires CI 
kind/docker environment
    
    * Wait for ingress backend deployments before tests
    
    The websocket ingress healthcheck now avoids probing the websocket upstream 
with an HTTP health path, which exposed a race where the CI test could start 
while the default namespace example deployment was still becoming available. In 
that state the ingress controller can observe empty websocket rule handles and 
the integration test fails even though gateway health passed. The shared helper 
now waits for deployments in both shenyu-ingress and default before polling 
service endpoints.\n\ [...]
    
    * Allow optional ingress backend namespaces
    
    The apache-dubbo ingress example deploys all workloads in shenyu-ingress, 
so requiring default namespace deployments made the shared readiness helper 
fail before it could poll the actual health endpoints. The helper now treats 
shenyu-ingress as required and default as optional, preserving the websocket 
backend readiness gate without breaking cases that do not use 
default.\n\nConstraint: Ingress examples do not all use the same namespace 
layout.\nRejected: Remove default namespace wait [...]
    
    * Expose websocket example actuator health
    
    The websocket ingress example deployment probes /actuator/health on port 
8001, but the annotation websocket example did not include Spring Boot 
Actuator. In CI the pod stayed 0/1 Available for ten minutes, restarted after 
startupProbe failures, and blocked the shared ingress healthcheck. Adding the 
actuator starter makes the existing Kubernetes startup and liveness probes 
target a real endpoint.\n\nConstraint: The k8s manifest already uses 
/actuator/health as the readiness signal.\nRe [...]
    
    * Refresh websocket ingress upstreams from endpoints
    
    The endpoint reconciler only looked up divide selectors for services mapped 
from an ingress. Websocket ingress services therefore had no matching selector 
ids, which produced addAll(null) NPEs and left the websocket selector handle 
empty after the backend endpoint became ready. The reconciler now skips missing 
selector caches and updates both divide and websocket selectors with 
plugin-appropriate upstream handles.\n\nConstraint: Kubernetes endpoint events 
can arrive after ingress pars [...]
    
    * Use real sentinel fallback in HTTP integration test
    
    The HTTP integrated matrix exposed that the sentinel fallback test was 
using an upstream /http route as a fallback: target. Fallback dispatch is 
gateway-local, so the test could return a backend 404 instead of proving the 
sentinel fallback path ran.
    
    This points the test at the existing /fallback/sentinel controller and 
asserts the sentinel fallback enum code and message so a plain rate-limit 
response cannot satisfy the fallback URI case.
    
    Constraint: fallback: URIs are handled by the gateway DispatcherHandler, 
not as a fresh divide upstream request
    
    Rejected: Keep /http/test/request/accepted | CI proved it can resolve as a 
missing backend static resource
    
    Confidence: high
    
    Scope-risk: narrow
    
    Tested: ./mvnw -B -f shenyu-integrated-test/pom.xml -pl 
shenyu-integrated-test-http -am -DskipTests -Dmaven.javadoc.skip=true 
test-compile
    
    Tested: git diff --check
    
    Not-tested: Full docker-compose HTTP integrated test locally; CI owns the 
full runtime environment
    
    * Allow fallback endpoints to preserve request methods
    
    Sentinel fallback dispatch preserves the original request method. The HTTP 
integrated test uses POST, so the gateway-local /fallback/sentinel endpoint 
returned 405 while the fallback path itself was correct.
    
    The default fallback endpoints are fallback targets rather than public 
method-specific APIs, so they now accept any method. A POST regression test 
covers the sentinel path that failed in CI.
    
    Constraint: FallbackHandler mutates only the URI and keeps the original 
HTTP method
    
    Rejected: Change the sentinel test back to a 429-only assertion | it would 
not prove fallbackUri dispatch and could mask ordinary rate limiting
    
    Confidence: high
    
    Scope-risk: narrow
    
    Tested: ./mvnw -B -pl shenyu-web -DskipITs -DskipTests=false 
-Dtest=DefaultFallbackControllerTest -Dmaven.javadoc.skip=true test
    
    Tested: ./mvnw -B -f shenyu-integrated-test/pom.xml -pl 
shenyu-integrated-test-http -am -DskipTests -Dmaven.javadoc.skip=true 
test-compile
    
    Tested: git diff --check
    
    Not-tested: Full docker-compose HTTP integrated test locally; CI owns the 
full runtime environment
    
    * Retry docker base image pulls in integration CI
    
    The integration workflow removes runner image caches to free disk, then 
docker-maven-plugin pulls Docker Hub base images during a large Maven build. 
Two consecutive runs failed before tests because alpine:latest and centos:7 
timed out from registry-1.docker.io.
    
    Pre-pulling the admin/bootstrap base images with retries warms the local 
Docker cache before Maven starts, reducing transient registry failures without 
changing the build artifacts.
    
    Constraint: it matrix jobs build docker images from Docker Hub bases after 
deleting runner caches
    
    Rejected: Only rerun the failed workflow | repeated reruns hit the same 
registry timeout and leave the CI easy to fail
    
    Confidence: medium
    
    Scope-risk: narrow
    
    Tested: ruby -e 'require "yaml"; 
YAML.load_file(".github/workflows/integrated-test.yml")'
    
    Tested: git diff --check
    
    Not-tested: Full GitHub Actions workflow locally
    
    * Assert sentinel fallback response shape directly
    
    CI proved the sentinel fallback path now returns the intended fallback 
response, but the integration test deserialized it into ResultBean. That DTO 
uses msg while the fallback response exposes message, so the behavior was 
correct but the assertion read the wrong field.
    
    The test now reads the fallback response as a map and asserts the actual 
code/message fields emitted by the fallback endpoint.
    
    Constraint: ShenyuResultWrap fallback responses use message, while 
integrated-test ResultBean uses msg
    
    Rejected: Change ResultBean or fallback response shape | broader contract 
change not needed for this test
    
    Confidence: high
    
    Scope-risk: narrow
    
    Tested: ./mvnw -B -f shenyu-integrated-test/pom.xml -pl 
shenyu-integrated-test-http -am -DskipTests -Dmaven.javadoc.skip=true 
test-compile
    
    Tested: git diff --check
    
    Not-tested: Full docker-compose HTTP integrated test locally; CI owns the 
full runtime environment
    
    * Retry transient CI dependency downloads
    
    GitHub Actions runs failed before project code executed when Temurin JDK 
and k3s binaries returned transient 504/download errors. Add an in-repo 
setup-java retry wrapper for observed setup-java callers and retry k3s 
installation in the k8s examples workflow.
    
    Constraint: The failures happened before Maven or integration tests could 
run, so test-code changes cannot address them.
    
    Rejected: Only rerun failed jobs | leaves the same external download point 
fragile on the next PR run
    
    Confidence: medium
    
    Scope-risk: moderate
    
    Directive: Keep workflow download retries close to the failing external 
install step so real build/test failures remain visible.
    
    Tested: Ruby YAML parse for changed workflow/action files; git diff --check
    
    Not-tested: actionlint is not installed locally; full GitHub Actions 
validation requires PR runs
    
    * Apply Java setup retries across CI workflows
    
    A later e2e job failed at the same pre-test Temurin download point as ci 
and it. Route the remaining workflow setup-java calls through the shared retry 
wrapper so transient GitHub release 504s do not fail unrelated test lanes 
before project code runs.
    
    Constraint: GitHub-hosted runners download some requested JDKs from GitHub 
releases at job runtime.
    
    Rejected: Patch only the failed e2e job | leaves CodeQL, it-k8s, and 
publish workflows with the same single-attempt setup path
    
    Confidence: medium
    
    Scope-risk: moderate
    
    Directive: Keep actions/setup-java@v4 usage centralized in 
actions/setup-java-with-retry unless a workflow has a documented reason to 
bypass retries.
    
    Tested: Ruby YAML parse for changed workflows and retry action; git diff 
--check; rg confirms no direct workflow setup-java@v4 uses remain
    
    Not-tested: actionlint is not installed locally; full workflow execution 
requires GitHub Actions
---
 .github/workflows/ci.yml                           |   2 +-
 .github/workflows/codeql-analysis.yml              |   2 +-
 .github/workflows/docker-publish-dockerhub.yml     |   2 +-
 .github/workflows/docker-publish.yml               |   2 +-
 .github/workflows/e2e-k8s.yml                      |   6 +-
 .github/workflows/integrated-test-k8s-ingress.yml  | 102 ++++++--------------
 .github/workflows/integrated-test.yml              |  86 ++++++++---------
 .github/workflows/k8s-examples-http.yml            |  68 ++++++-------
 actions/setup-java-with-retry/action.yml           |  63 +++++++++++++
 .../k8s/shenyu-examples-dubbo.yml                  |   2 +-
 .../k8s/shenyu-examples-grpc.yml                   |   2 +-
 .../http/controller/HttpTestController.java        |  11 +++
 .../k8s/shenyu-examples-sofa.yml                   |   2 +-
 .../shenyu-example-spring-annotation-websocket.yml |  11 ++-
 .../pom.xml                                        |   4 +
 shenyu-integrated-test/k8s-ingress-healthcheck.sh  |  77 +++++++++++++++
 .../test/http/combination/SentinelPluginTest.java  |   9 +-
 .../deploy/kind-config.yaml                        |   3 +
 .../script/healthcheck.sh                          |  18 +---
 .../deploy/kind-config.yaml                        |   3 +
 .../script/healthcheck.sh                          |  18 +---
 .../script/services.list                           |   2 +-
 .../deploy/kind-config.yaml                        |   3 +
 .../script/healthcheck.sh                          |  18 +---
 .../deploy/kind-config.yaml                        |   3 +
 .../script/services.list                           |   2 +-
 .../script/healthcheck.sh                          |  18 +---
 .../script/services.list                           |   2 +-
 .../shenyu/k8s/reconciler/EndpointsReconciler.java |  81 +++++++++++-----
 .../apache/shenyu/k8s/EndpointsReconcilerTest.java | 105 +++++++++++++++++++++
 .../web/fallback/DefaultFallbackController.java    |   9 +-
 .../fallback/DefaultFallbackControllerTest.java    |   7 ++
 32 files changed, 473 insertions(+), 270 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0da28afeac..15acf77117 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -89,7 +89,7 @@ jobs:
           key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
           restore-keys: |
             ${{ runner.os }}-maven-
-      - uses: actions/setup-java@v4
+      - uses: ./actions/setup-java-with-retry
         if: steps.filter.outputs.code == 'true'
         with:
           java-version: ${{ matrix.java }}
diff --git a/.github/workflows/codeql-analysis.yml 
b/.github/workflows/codeql-analysis.yml
index 2e835dcbbe..510fbea7a5 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -42,7 +42,7 @@ jobs:
         with:
           submodules: true
       - name: Set up JDK 17
-        uses: actions/setup-java@v4
+        uses: ./actions/setup-java-with-retry
         with:
           java-version: 17
           distribution: 'temurin'
diff --git a/.github/workflows/docker-publish-dockerhub.yml 
b/.github/workflows/docker-publish-dockerhub.yml
index 7ca7a63fad..525b13d503 100644
--- a/.github/workflows/docker-publish-dockerhub.yml
+++ b/.github/workflows/docker-publish-dockerhub.yml
@@ -63,7 +63,7 @@ jobs:
           restore-keys: |
             ${{ runner.os }}-maven-
 
-      - uses: actions/setup-java@v4
+      - uses: ./actions/setup-java-with-retry
         with:
           java-version: 17
           distribution: "temurin"
diff --git a/.github/workflows/docker-publish.yml 
b/.github/workflows/docker-publish.yml
index e50138fb20..969b39e596 100644
--- a/.github/workflows/docker-publish.yml
+++ b/.github/workflows/docker-publish.yml
@@ -67,7 +67,7 @@ jobs:
           restore-keys: |
             ${{ runner.os }}-maven-
 
-      - uses: actions/setup-java@v4
+      - uses: ./actions/setup-java-with-retry
         with:
           java-version: 17
           distribution: "temurin"
diff --git a/.github/workflows/e2e-k8s.yml b/.github/workflows/e2e-k8s.yml
index b9451ecff7..10c7f64e0e 100644
--- a/.github/workflows/e2e-k8s.yml
+++ b/.github/workflows/e2e-k8s.yml
@@ -79,7 +79,7 @@ jobs:
           df --human-readable
 
       - name: Set up JDK 17 for Building ShenYu
-        uses: actions/setup-java@v4
+        uses: ./actions/setup-java-with-retry
         with:
           java-version: "17"
           distribution: "temurin"
@@ -191,7 +191,7 @@ jobs:
       #          cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
 
       - name: Set up JDK 17 for Building ShenYu
-        uses: actions/setup-java@v4
+        uses: ./actions/setup-java-with-retry
         with:
           java-version: "17"
           distribution: "temurin"
@@ -288,7 +288,7 @@ jobs:
       #          cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
 
       - name: Set up JDK 17 for Building ShenYu
-        uses: actions/setup-java@v4
+        uses: ./actions/setup-java-with-retry
         with:
           java-version: "17"
           distribution: "temurin"
diff --git a/.github/workflows/integrated-test-k8s-ingress.yml 
b/.github/workflows/integrated-test-k8s-ingress.yml
index cb8fdb08a8..763d923da6 100644
--- a/.github/workflows/integrated-test-k8s-ingress.yml
+++ b/.github/workflows/integrated-test-k8s-ingress.yml
@@ -67,20 +67,14 @@ jobs:
           sudo rm -rf "/usr/local/share/boost"
           sudo rm -rf "$AGENT_TOOLSDIRECTORY"
 
-      - name: Cache Maven Repos
-        if: steps.filter.outputs.k8s-ingress == 'true'
-        uses: actions/cache@v3
-        with:
-          path: ~/.m2/repository
-          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
-          restore-keys: |
-            ${{ runner.os }}-maven-
-
-      - uses: actions/setup-java@v4
+      - uses: ./actions/setup-java-with-retry
         if: steps.filter.outputs.k8s-ingress == 'true'
         with:
           java-version: 17
           distribution: "temurin"
+          cache: "maven"
+          cache-dependency-path: |
+            **/pom.xml
 
       - name: Install Go
         uses: actions/setup-go@v3
@@ -94,41 +88,17 @@ jobs:
           curl -LO 
https://storage.googleapis.com/kubernetes-release/release/v1.24.14/bin/linux/amd64/kubectl
 && sudo install kubectl /usr/local/bin/kubectl
           kind create cluster --image=kindest/node:v1.21.1 
--config=./shenyu-integrated-test/${{ matrix.case }}/deploy/kind-config.yaml
 
-      - name: Cache Maven Repos
-        if: steps.filter.outputs.k8s-ingress == 'true'
-        uses: actions/cache@v3
-        with:
-          path: ~/.m2/repository
-          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
-          restore-keys: |
-            ${{ runner.os }}-maven-
-
-      - uses: actions/setup-java@v4
-        if: steps.filter.outputs.k8s-ingress == 'true'
-        with:
-          java-version: 17
-          distribution: "temurin"
-
       - name: Install mvnd
         if: steps.filter.outputs.k8s-ingress == 'true'
         shell: bash
         run: |
           MVND_VERSION=1.0.2
-          if [[ "${{ runner.os }}" == "Windows" ]]; then
-            curl -sL 
https://downloads.apache.org/maven/mvnd/${MVND_VERSION}/maven-mvnd-${MVND_VERSION}-windows-amd64.zip
 -o mvnd.zip
-            unzip -q mvnd.zip
-            mkdir -p $HOME/.local
-            mv maven-mvnd-${MVND_VERSION}-windows-amd64 $HOME/.local/mvnd
-            echo "$HOME/.local/mvnd/bin" >> $GITHUB_PATH
-            echo "MVND_HOME=$HOME/.local/mvnd" >> $GITHUB_ENV
-          else
-            curl -sL 
https://downloads.apache.org/maven/mvnd/${MVND_VERSION}/maven-mvnd-${MVND_VERSION}-linux-amd64.zip
 -o mvnd.zip
-            unzip -q mvnd.zip
-            mkdir -p $HOME/.local
-            mv maven-mvnd-${MVND_VERSION}-linux-amd64 $HOME/.local/mvnd
-            echo "$HOME/.local/mvnd/bin" >> $GITHUB_PATH
-            echo "MVND_HOME=$HOME/.local/mvnd" >> $GITHUB_ENV
-          fi
+          curl -sL 
https://downloads.apache.org/maven/mvnd/${MVND_VERSION}/maven-mvnd-${MVND_VERSION}-linux-amd64.zip
 -o mvnd.zip
+          unzip -q mvnd.zip
+          mkdir -p $HOME/.local
+          mv maven-mvnd-${MVND_VERSION}-linux-amd64 $HOME/.local/mvnd
+          echo "$HOME/.local/mvnd/bin" >> $GITHUB_PATH
+          echo "MVND_HOME=$HOME/.local/mvnd" >> $GITHUB_ENV
 
       - name: Build with Maven
         if: steps.filter.outputs.k8s-ingress == 'true'
@@ -136,14 +106,10 @@ jobs:
         run: |
           if mvnd --version > /dev/null 2>&1; then
             echo "Using mvnd for build"
-            mvnd -B clean install -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true
+            mvnd -B -T 1C clean install -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true
           else
             echo "Falling back to maven wrapper"
-            if [[ "${{ runner.os }}" == "Windows" ]]; then
-              ./mvnw.cmd -B clean install -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true
-            else
-              ./mvnw -B clean install -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true
-            fi
+            ./mvnw -B -T 1C clean install -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true
           fi
 
       - name: Build integrated tests
@@ -152,14 +118,10 @@ jobs:
         run: |
           if mvnd --version > /dev/null 2>&1; then
             echo "Using mvnd for build integrated tests"
-            mvnd -B clean install -Pit -DskipTests -am -f 
./shenyu-integrated-test/pom.xml
+            mvnd -B -T 1C clean install -Pit -DskipTests -am -f 
./shenyu-integrated-test/pom.xml
           else
             echo "Falling back to maven wrapper for integrated tests"
-            if [[ "${{ runner.os }}" == "Windows" ]]; then
-              ./mvnw.cmd -B clean install -Pit -DskipTests -am -f 
./shenyu-integrated-test/pom.xml
-            else
-              ./mvnw -B clean install -Pit -DskipTests -am -f 
./shenyu-integrated-test/pom.xml
-            fi
+            ./mvnw -B -T 1C clean install -Pit -DskipTests -am -f 
./shenyu-integrated-test/pom.xml
           fi
 
       - name: Build examples
@@ -168,14 +130,10 @@ jobs:
         run: |
           if mvnd --version > /dev/null 2>&1; then
             echo "Using mvnd for build examples"
-            mvnd -B clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -am -f ./shenyu-examples/pom.xml
+            mvnd -B -T 1C clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -am -f ./shenyu-examples/pom.xml
           else
             echo "Falling back to maven wrapper for examples"
-            if [[ "${{ runner.os }}" == "Windows" ]]; then
-              ./mvnw.cmd -B clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -am -f ./shenyu-examples/pom.xml
-            else
-              ./mvnw -B clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -am -f ./shenyu-examples/pom.xml
-            fi
+            ./mvnw -B -T 1C clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -am -f ./shenyu-examples/pom.xml
           fi
 
       - name: Build k8s Cluster
@@ -183,8 +141,9 @@ jobs:
         run: bash ./shenyu-integrated-test/${{ matrix.case 
}}/script/build_k8s_cluster.sh
 
       - name: Wait for k8s Cluster Start up
+        id: healthcheck
         if: steps.filter.outputs.k8s-ingress == 'true'
-        timeout-minutes: 15
+        timeout-minutes: 25
         run: |
           bash ./shenyu-integrated-test/${{ matrix.case 
}}/script/healthcheck.sh
 
@@ -195,29 +154,30 @@ jobs:
         run: |
           if mvnd --version > /dev/null 2>&1; then
             echo "Using mvnd for running tests"
-            mvnd test -Pit -f ./shenyu-integrated-test/${{ matrix.case 
}}/pom.xml
+            mvnd -T 1C test -Pit -f ./shenyu-integrated-test/${{ matrix.case 
}}/pom.xml
           else
             echo "Falling back to maven wrapper for tests"
-            if [[ "${{ runner.os }}" == "Windows" ]]; then
-              ./mvnw.cmd test -Pit -f ./shenyu-integrated-test/${{ matrix.case 
}}/pom.xml
-            else
-              ./mvnw test -Pit -f ./shenyu-integrated-test/${{ matrix.case 
}}/pom.xml
-            fi
+            ./mvnw -T 1C test -Pit -f ./shenyu-integrated-test/${{ matrix.case 
}}/pom.xml
           fi
         continue-on-error: true
 
       - name: Cluster Test after Healthcheck
-        if: steps.filter.outputs.k8s-ingress == 'true'
+        if: always() && steps.filter.outputs.k8s-ingress == 'true'
         run: |
           echo "----------kubectl get all -o wide----------"
-          kubectl get all -o wide
+          kubectl get all -o wide || true
           echo "----------kubectl get all -n shenyu-ingress -o wide----------"
-          kubectl get all -n shenyu-ingress -o wide
+          kubectl get all -n shenyu-ingress -o wide || true
           echo "----------kubectl get events --all-namespaces----------"
-          kubectl get events --all-namespaces
+          kubectl get events --all-namespaces || true
+          echo "----------kubectl describe pod -n shenyu-ingress----------"
+          kubectl describe pod -n shenyu-ingress || true
           echo "----------kubectl logs -l app=shenyu-ingress-controller -n 
shenyu-ingress --tail=-1----------"
-          kubectl logs -l app=shenyu-ingress-controller -n shenyu-ingress 
--tail=-1
-          if [[ ${{steps.test.outcome}} == "failure" ]]; then
+          kubectl logs -l app=shenyu-ingress-controller -n shenyu-ingress 
--tail=-1 || true
+          if [[ ${{steps.healthcheck.outcome}} == "failure" ]]; then
+            echo "Healthcheck Failed"
+            exit 1
+          elif [[ ${{steps.test.outcome}} == "failure" ]]; then
             echo "Test Failed"
             exit 1
           else
diff --git a/.github/workflows/integrated-test.yml 
b/.github/workflows/integrated-test.yml
index 86073803a3..3bcce36ba3 100644
--- a/.github/workflows/integrated-test.yml
+++ b/.github/workflows/integrated-test.yml
@@ -74,53 +74,55 @@ jobs:
               - '!NOTICE'
               - '!.github/ISSUE_TEMPLATE/**'
               - '!.github/PULL_REQUEST_TEMPLATE'
-      - name: Restore ShenYu Maven Repos
-        if: steps.filter.outputs.integration == 'true'
-        uses: actions/cache/restore@v3
-        with:
-          path: ~/.m2/repository
-          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
-          restore-keys: |
-            ${{ runner.os }}-maven-
-      - uses: actions/setup-java@v4
+      - uses: ./actions/setup-java-with-retry
         if: steps.filter.outputs.integration == 'true'
         with:
           java-version: 17
           distribution: "temurin"
+          cache: "maven"
+          cache-dependency-path: |
+            **/pom.xml
       - name: Install mvnd
         if: steps.filter.outputs.integration == 'true'
         shell: bash
         run: |
           MVND_VERSION=1.0.2
-          if [[ "${{ runner.os }}" == "Windows" ]]; then
-            curl -sL 
https://downloads.apache.org/maven/mvnd/${MVND_VERSION}/maven-mvnd-${MVND_VERSION}-windows-amd64.zip
 -o mvnd.zip
-            unzip -q mvnd.zip
-            mkdir -p $HOME/.local
-            mv maven-mvnd-${MVND_VERSION}-windows-amd64 $HOME/.local/mvnd
-            echo "$HOME/.local/mvnd/bin" >> $GITHUB_PATH
-            echo "MVND_HOME=$HOME/.local/mvnd" >> $GITHUB_ENV
-          else
-            curl -sL 
https://downloads.apache.org/maven/mvnd/${MVND_VERSION}/maven-mvnd-${MVND_VERSION}-linux-amd64.zip
 -o mvnd.zip
-            unzip -q mvnd.zip
-            mkdir -p $HOME/.local
-            mv maven-mvnd-${MVND_VERSION}-linux-amd64 $HOME/.local/mvnd
-            echo "$HOME/.local/mvnd/bin" >> $GITHUB_PATH
-            echo "MVND_HOME=$HOME/.local/mvnd" >> $GITHUB_ENV
-          fi
+          curl -sL 
https://downloads.apache.org/maven/mvnd/${MVND_VERSION}/maven-mvnd-${MVND_VERSION}-linux-amd64.zip
 -o mvnd.zip
+          unzip -q mvnd.zip
+          mkdir -p $HOME/.local
+          mv maven-mvnd-${MVND_VERSION}-linux-amd64 $HOME/.local/mvnd
+          echo "$HOME/.local/mvnd/bin" >> $GITHUB_PATH
+          echo "MVND_HOME=$HOME/.local/mvnd" >> $GITHUB_ENV
+      - name: Pre-pull docker base images
+        if: steps.filter.outputs.integration == 'true'
+        shell: bash
+        run: |
+          pull_with_retry() {
+            local image="$1"
+            for attempt in 1 2 3; do
+              if docker pull "$image"; then
+                return 0
+              fi
+              echo "docker pull $image failed on attempt $attempt"
+              sleep $((attempt * 10))
+            done
+            docker pull "$image"
+          }
+
+          pull_with_retry alpine:latest
+          pull_with_retry amazoncorretto:17.0.11-alpine3.19
+          pull_with_retry centos:7
+          pull_with_retry eclipse-temurin:17-centos7
       - name: Build with Maven
         if: steps.filter.outputs.integration == 'true'
         shell: bash
         run: |
           if mvnd --version > /dev/null 2>&1; then
             echo "Using mvnd for build"
-            mvnd -B clean install -Prelease,docker -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true
+            mvnd -B -T 1C clean install -Prelease,docker 
-Dmaven.javadoc.skip=true -Dmaven.test.skip=true
           else
             echo "Falling back to maven wrapper"
-            if [[ "${{ runner.os }}" == "Windows" ]]; then
-              ./mvnw.cmd -B clean install -Prelease,docker 
-Dmaven.javadoc.skip=true -Dmaven.test.skip=true
-            else
-              ./mvnw -B clean install -Prelease,docker 
-Dmaven.javadoc.skip=true -Dmaven.test.skip=true
-            fi
+            ./mvnw -B -T 1C clean install -Prelease,docker 
-Dmaven.javadoc.skip=true -Dmaven.test.skip=true
           fi
       - name: Build examples
         if: steps.filter.outputs.integration == 'true'
@@ -128,14 +130,10 @@ jobs:
         run: |
           if mvnd --version > /dev/null 2>&1; then
             echo "Using mvnd for build examples"
-            mvnd -B clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -am -f ./shenyu-examples/pom.xml
+            mvnd -B -T 1C clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -am -f ./shenyu-examples/pom.xml
           else
             echo "Falling back to maven wrapper for examples"
-            if [[ "${{ runner.os }}" == "Windows" ]]; then
-              ./mvnw.cmd -B clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -am -f ./shenyu-examples/pom.xml
-            else
-              ./mvnw -B clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -am -f ./shenyu-examples/pom.xml
-            fi
+            ./mvnw -B -T 1C clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -am -f ./shenyu-examples/pom.xml
           fi
       - name: Build integrated tests
         if: steps.filter.outputs.integration == 'true'
@@ -143,14 +141,10 @@ jobs:
         run: |
           if mvnd --version > /dev/null 2>&1; then
             echo "Using mvnd for build integrated tests"
-            mvnd -B clean install -Pit -DskipTests -f 
./shenyu-integrated-test/pom.xml
+            mvnd -B -T 1C clean install -Pit -DskipTests -f 
./shenyu-integrated-test/pom.xml
           else
             echo "Falling back to maven wrapper for integrated tests"
-            if [[ "${{ runner.os }}" == "Windows" ]]; then
-              ./mvnw.cmd -B clean install -Pit -DskipTests -f 
./shenyu-integrated-test/pom.xml
-            else
-              ./mvnw -B clean install -Pit -DskipTests -f 
./shenyu-integrated-test/pom.xml
-            fi
+            ./mvnw -B -T 1C clean install -Pit -DskipTests -f 
./shenyu-integrated-test/pom.xml
           fi
       - name: Start docker compose
         if: steps.filter.outputs.integration == 'true'
@@ -168,14 +162,10 @@ jobs:
         run: |
           if mvnd --version > /dev/null 2>&1; then
             echo "Using mvnd for running tests"
-            mvnd test -Pit -f ./shenyu-integrated-test/${{ matrix.case 
}}/pom.xml
+            mvnd -T 1C test -Pit -f ./shenyu-integrated-test/${{ matrix.case 
}}/pom.xml
           else
             echo "Falling back to maven wrapper for tests"
-            if [[ "${{ runner.os }}" == "Windows" ]]; then
-              ./mvnw.cmd test -Pit -f ./shenyu-integrated-test/${{ matrix.case 
}}/pom.xml
-            else
-              ./mvnw test -Pit -f ./shenyu-integrated-test/${{ matrix.case 
}}/pom.xml
-            fi
+            ./mvnw -T 1C test -Pit -f ./shenyu-integrated-test/${{ matrix.case 
}}/pom.xml
           fi
         continue-on-error: true
       - name: Check test result
diff --git a/.github/workflows/k8s-examples-http.yml 
b/.github/workflows/k8s-examples-http.yml
index 7f3cb06de7..d8803f57bc 100644
--- a/.github/workflows/k8s-examples-http.yml
+++ b/.github/workflows/k8s-examples-http.yml
@@ -60,57 +60,55 @@ jobs:
       - name: Install k8s
         if: steps.filter.outputs.k8s-examples == 'true'
         run: |
-          curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.29.6+k3s2 
K3S_KUBECONFIG_MODE=777 sh -
+          install_k3s() {
+            curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.29.6+k3s2 
K3S_KUBECONFIG_MODE=777 sh -
+          }
+
+          for attempt in 1 2 3; do
+            if install_k3s; then
+              break
+            fi
+            if [ "$attempt" = 3 ]; then
+              echo "k3s install failed after $attempt attempts"
+              exit 1
+            fi
+            echo "k3s install failed on attempt $attempt"
+            sleep $((attempt * 15))
+          done
+
           cat /etc/rancher/k3s/k3s.yaml
           mkdir -p ~/.kube
           cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
-      - name: Restore ShenYu Maven Repos
-        if: steps.filter.outputs.k8s-examples == 'true'
-        uses: actions/cache/restore@v3
-        with:
-          path: ~/.m2/repository
-          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
-          restore-keys: |
-            ${{ runner.os }}-maven-
-      - uses: actions/setup-java@v4
+      - name: Setup Java and Maven cache
         if: steps.filter.outputs.k8s-examples == 'true'
+        uses: ./actions/setup-java-with-retry
         with:
           java-version: 17
           distribution: "temurin"
+          cache: "maven"
+          cache-dependency-path: |
+            **/pom.xml
       - name: Install mvnd
         if: steps.filter.outputs.k8s-examples == 'true'
         shell: bash
         run: |
           MVND_VERSION=1.0.2
-          if [[ "${{ runner.os }}" == "Windows" ]]; then
-            curl -sL 
https://downloads.apache.org/maven/mvnd/${MVND_VERSION}/maven-mvnd-${MVND_VERSION}-windows-amd64.zip
 -o mvnd.zip
-            unzip -q mvnd.zip
-            mkdir -p $HOME/.local
-            mv maven-mvnd-${MVND_VERSION}-windows-amd64 $HOME/.local/mvnd
-            echo "$HOME/.local/mvnd/bin" >> $GITHUB_PATH
-            echo "MVND_HOME=$HOME/.local/mvnd" >> $GITHUB_ENV
-          else
-            curl -sL 
https://downloads.apache.org/maven/mvnd/${MVND_VERSION}/maven-mvnd-${MVND_VERSION}-linux-amd64.zip
 -o mvnd.zip
-            unzip -q mvnd.zip
-            mkdir -p $HOME/.local
-            mv maven-mvnd-${MVND_VERSION}-linux-amd64 $HOME/.local/mvnd
-            echo "$HOME/.local/mvnd/bin" >> $GITHUB_PATH
-            echo "MVND_HOME=$HOME/.local/mvnd" >> $GITHUB_ENV
-          fi
+          curl -sL 
https://downloads.apache.org/maven/mvnd/${MVND_VERSION}/maven-mvnd-${MVND_VERSION}-linux-amd64.zip
 -o mvnd.zip
+          unzip -q mvnd.zip
+          mkdir -p $HOME/.local
+          mv maven-mvnd-${MVND_VERSION}-linux-amd64 $HOME/.local/mvnd
+          echo "$HOME/.local/mvnd/bin" >> $GITHUB_PATH
+          echo "MVND_HOME=$HOME/.local/mvnd" >> $GITHUB_ENV
       - name: Build with Maven
         if: steps.filter.outputs.k8s-examples == 'true'
         shell: bash
         run: |
           if mvnd --version > /dev/null 2>&1; then
             echo "Using mvnd for build"
-            mvnd -B clean install -Prelease,docker -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true
+            mvnd -B -T 1C clean install -Prelease,docker 
-Dmaven.javadoc.skip=true -Dmaven.test.skip=true
           else
             echo "Falling back to maven wrapper"
-            if [[ "${{ runner.os }}" == "Windows" ]]; then
-              ./mvnw.cmd -B clean install -Prelease,docker 
-Dmaven.javadoc.skip=true -Dmaven.test.skip=true
-            else
-              ./mvnw -B clean install -Prelease,docker 
-Dmaven.javadoc.skip=true -Dmaven.test.skip=true
-            fi
+            ./mvnw -B -T 1C clean install -Prelease,docker 
-Dmaven.javadoc.skip=true -Dmaven.test.skip=true
           fi
       - name: Build examples
         if: steps.filter.outputs.k8s-examples == 'true'
@@ -118,14 +116,10 @@ jobs:
         run: |
           if mvnd --version > /dev/null 2>&1; then
             echo "Using mvnd for build examples"
-            mvnd -B clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -f ./shenyu-examples/pom.xml
+            mvnd -B -T 1C clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -f ./shenyu-examples/pom.xml
           else
             echo "Falling back to maven wrapper for examples"
-            if [[ "${{ runner.os }}" == "Windows" ]]; then
-              ./mvnw.cmd -B clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -f ./shenyu-examples/pom.xml
-            else
-              ./mvnw -B clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -f ./shenyu-examples/pom.xml
-            fi
+            ./mvnw -B -T 1C clean install -Pexample -Dmaven.javadoc.skip=true 
-Dmaven.test.skip=true -f ./shenyu-examples/pom.xml
           fi
       - name: Build k8s Cluster
         if: steps.filter.outputs.k8s-examples == 'true'
diff --git a/actions/setup-java-with-retry/action.yml 
b/actions/setup-java-with-retry/action.yml
new file mode 100644
index 0000000000..68a4a2c24f
--- /dev/null
+++ b/actions/setup-java-with-retry/action.yml
@@ -0,0 +1,63 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+name: 'Setup Java With Retry'
+description: 'Retries actions/setup-java to reduce transient GitHub release 
download failures.'
+inputs:
+  java-version:
+    description: 'Java version to install.'
+    required: true
+  distribution:
+    description: 'Java distribution to install.'
+    required: false
+    default: 'temurin'
+  cache:
+    description: 'Package manager cache to use.'
+    required: false
+    default: ''
+  cache-dependency-path:
+    description: 'Dependency file path for cache key generation.'
+    required: false
+    default: ''
+runs:
+  using: 'composite'
+  steps:
+    - name: Setup Java attempt 1
+      id: setup-java-1
+      uses: actions/setup-java@v4
+      continue-on-error: true
+      with:
+        java-version: ${{ inputs.java-version }}
+        distribution: ${{ inputs.distribution }}
+        cache: ${{ inputs.cache }}
+        cache-dependency-path: ${{ inputs.cache-dependency-path }}
+    - name: Setup Java attempt 2
+      id: setup-java-2
+      if: steps.setup-java-1.outcome == 'failure'
+      uses: actions/setup-java@v4
+      continue-on-error: true
+      with:
+        java-version: ${{ inputs.java-version }}
+        distribution: ${{ inputs.distribution }}
+        cache: ${{ inputs.cache }}
+        cache-dependency-path: ${{ inputs.cache-dependency-path }}
+    - name: Setup Java attempt 3
+      if: steps.setup-java-1.outcome == 'failure' && 
steps.setup-java-2.outcome == 'failure'
+      uses: actions/setup-java@v4
+      with:
+        java-version: ${{ inputs.java-version }}
+        distribution: ${{ inputs.distribution }}
+        cache: ${{ inputs.cache }}
+        cache-dependency-path: ${{ inputs.cache-dependency-path }}
diff --git 
a/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service/k8s/shenyu-examples-dubbo.yml
 
b/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service/k8s/shenyu-examples-dubbo.yml
index fb838b5091..5a056ab7f7 100644
--- 
a/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service/k8s/shenyu-examples-dubbo.yml
+++ 
b/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service/k8s/shenyu-examples-dubbo.yml
@@ -48,7 +48,7 @@ spec:
               command:
                 - /bin/sh
                 - "-c"
-                - wget -q -O - http://localhost:8081/actuator/health | grep UP 
|| exit 1
+                - wget -q -O - http://localhost:8011/actuator/health | grep UP 
|| exit 1
           env:
             - name: shenyu.register.serverLists
               value: http://shenyu-admin:9095
diff --git a/shenyu-examples/shenyu-examples-grpc/k8s/shenyu-examples-grpc.yml 
b/shenyu-examples/shenyu-examples-grpc/k8s/shenyu-examples-grpc.yml
index 78733334ba..98c0c8e6dc 100644
--- a/shenyu-examples/shenyu-examples-grpc/k8s/shenyu-examples-grpc.yml
+++ b/shenyu-examples/shenyu-examples-grpc/k8s/shenyu-examples-grpc.yml
@@ -55,7 +55,7 @@ spec:
               command:
                 - /bin/sh
                 - "-c"
-                - wget -q -O - http://localhost:55290/actuator/health | grep 
UP || exit 1
+                - wget -q -O - http://localhost:55290/grpc/actuator/health | 
grep UP || exit 1
           env:
             - name: shenyu.register.serverLists
               value: http://shenyu-admin:9095
diff --git 
a/shenyu-examples/shenyu-examples-http/src/main/java/org/apache/shenyu/examples/http/controller/HttpTestController.java
 
b/shenyu-examples/shenyu-examples-http/src/main/java/org/apache/shenyu/examples/http/controller/HttpTestController.java
index 20e112e57f..8355132904 100644
--- 
a/shenyu-examples/shenyu-examples-http/src/main/java/org/apache/shenyu/examples/http/controller/HttpTestController.java
+++ 
b/shenyu-examples/shenyu-examples-http/src/main/java/org/apache/shenyu/examples/http/controller/HttpTestController.java
@@ -363,6 +363,17 @@ public class HttpTestController {
         return response;
     }
 
+    /**
+     * Return accepted code.
+     *
+     * @return response. result bean
+     */
+    @PostMapping("/request/accepted")
+    @ResponseStatus(HttpStatus.ACCEPTED)
+    public ResultBean postAccepted() {
+        return accepted();
+    }
+
     /**
      * Return bad request code.
      *
diff --git 
a/shenyu-examples/shenyu-examples-sofa/shenyu-examples-sofa-service/k8s/shenyu-examples-sofa.yml
 
b/shenyu-examples/shenyu-examples-sofa/shenyu-examples-sofa-service/k8s/shenyu-examples-sofa.yml
index f0ab46a6a2..7282c44cea 100644
--- 
a/shenyu-examples/shenyu-examples-sofa/shenyu-examples-sofa-service/k8s/shenyu-examples-sofa.yml
+++ 
b/shenyu-examples/shenyu-examples-sofa/shenyu-examples-sofa-service/k8s/shenyu-examples-sofa.yml
@@ -48,7 +48,7 @@ spec:
               command:
                 - /bin/sh
                 - "-c"
-                - wget -q -O - http://localhost:8081/actuator/health | grep UP 
|| exit 1
+                - wget -q -O - http://localhost:28011/actuator/health | grep 
UP || exit 1
           env:
             - name: shenyu.register.serverLists
               value: http://shenyu-admin:9095
diff --git 
a/shenyu-examples/shenyu-examples-websocket/shenyu-example-spring-annotation-websocket/k8s/shenyu-example-spring-annotation-websocket.yml
 
b/shenyu-examples/shenyu-examples-websocket/shenyu-example-spring-annotation-websocket/k8s/shenyu-example-spring-annotation-websocket.yml
index 0767297926..0ddbac62e5 100644
--- 
a/shenyu-examples/shenyu-examples-websocket/shenyu-example-spring-annotation-websocket/k8s/shenyu-example-spring-annotation-websocket.yml
+++ 
b/shenyu-examples/shenyu-examples-websocket/shenyu-example-spring-annotation-websocket/k8s/shenyu-example-spring-annotation-websocket.yml
@@ -37,8 +37,17 @@ spec:
       containers:
         - image: shenyu-example-spring-annotation-websocket
           name: shenyu-example-spring-annotation-websocket
+          startupProbe:
+            periodSeconds: 10
+            timeoutSeconds: 5
+            failureThreshold: 18
+            exec:
+              command:
+                - /bin/sh
+                - "-c"
+                - wget -q -O - http://localhost:8001/actuator/health | grep UP 
|| exit 1
           livenessProbe:
-            initialDelaySeconds: 30
+            initialDelaySeconds: 60
             periodSeconds: 10
             timeoutSeconds: 5
             successThreshold: 1
diff --git 
a/shenyu-examples/shenyu-examples-websocket/shenyu-example-spring-annotation-websocket/pom.xml
 
b/shenyu-examples/shenyu-examples-websocket/shenyu-example-spring-annotation-websocket/pom.xml
index c3af4bbe53..fe92b0f8d9 100644
--- 
a/shenyu-examples/shenyu-examples-websocket/shenyu-example-spring-annotation-websocket/pom.xml
+++ 
b/shenyu-examples/shenyu-examples-websocket/shenyu-example-spring-annotation-websocket/pom.xml
@@ -41,6 +41,10 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-websocket</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>org.projectlombok</groupId>
diff --git a/shenyu-integrated-test/k8s-ingress-healthcheck.sh 
b/shenyu-integrated-test/k8s-ingress-healthcheck.sh
new file mode 100644
index 0000000000..ca3030eb31
--- /dev/null
+++ b/shenyu-integrated-test/k8s-ingress-healthcheck.sh
@@ -0,0 +1,77 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+set -euo pipefail
+
+SERVICES_FILE=$1
+STARTUP_DELAY_SECONDS=${2:-3}
+MAX_RETRIES=${MAX_RETRIES:-30}
+CURL_CONNECT_TIMEOUT=${CURL_CONNECT_TIMEOUT:-5}
+CURL_MAX_TIME=${CURL_MAX_TIME:-10}
+K8S_WAIT_TIMEOUT=${K8S_WAIT_TIMEOUT:-10m}
+
+wait_deployments() {
+    local namespace=$1
+    local required=${2:-true}
+
+    echo "waiting for ${namespace} deployments to become available, timeout 
${K8S_WAIT_TIMEOUT}"
+    deployments=$(kubectl get deployment -n "${namespace}" -o name)
+    if [ -z "${deployments}" ]; then
+        echo "no deployments found in namespace ${namespace}"
+        if [ "${required}" != "true" ]; then
+            return 0
+        fi
+        exit 1
+    fi
+
+    for deployment in ${deployments}; do
+        kubectl wait --for=condition=Available "${deployment}" -n 
"${namespace}" --timeout="${K8S_WAIT_TIMEOUT}"
+    done
+}
+
+wait_deployments shenyu-ingress
+wait_deployments default false
+
+failed=0
+while IFS= read -r service || [ -n "$service" ]; do
+    if [[ -z "$service" || "$service" =~ ^# ]]; then
+        continue
+    fi
+
+    ready=0
+    for loop in $(seq 1 "${MAX_RETRIES}"); do
+        status=$(curl --connect-timeout "${CURL_CONNECT_TIMEOUT}" --max-time 
"${CURL_MAX_TIME}" -o /dev/null -s -w "%{http_code}" "$service" || true)
+        echo -e "curl $service response $status, attempt $loop/${MAX_RETRIES}"
+
+        if [ "$status" = "200" ]; then
+            ready=1
+            break
+        fi
+
+        sleep 2
+    done
+
+    if [ "$ready" -ne 1 ]; then
+        echo "service $service failed healthcheck after ${MAX_RETRIES} 
attempts"
+        failed=1
+    fi
+done < "${SERVICES_FILE}"
+
+sleep "${STARTUP_DELAY_SECONDS}"
+echo -e "\n-------------------"
+exit "${failed}"
diff --git 
a/shenyu-integrated-test/shenyu-integrated-test-http/src/test/java/org/apache/shenyu/integrated/test/http/combination/SentinelPluginTest.java
 
b/shenyu-integrated-test/shenyu-integrated-test-http/src/test/java/org/apache/shenyu/integrated/test/http/combination/SentinelPluginTest.java
index ffe81bf29f..210b3a2bc0 100644
--- 
a/shenyu-integrated-test/shenyu-integrated-test-http/src/test/java/org/apache/shenyu/integrated/test/http/combination/SentinelPluginTest.java
+++ 
b/shenyu-integrated-test/shenyu-integrated-test-http/src/test/java/org/apache/shenyu/integrated/test/http/combination/SentinelPluginTest.java
@@ -27,7 +27,7 @@ import org.apache.shenyu.common.enums.PluginEnum;
 import org.apache.shenyu.common.utils.JsonUtils;
 import org.apache.shenyu.integratedtest.common.AbstractPluginDataInit;
 import org.apache.shenyu.integratedtest.common.helper.HttpHelper;
-import org.apache.shenyu.integratedtest.common.result.ResultBean;
+import org.apache.shenyu.plugin.api.result.ShenyuResultEnum;
 import org.apache.shenyu.web.controller.LocalPluginController.RuleLocalData;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -48,7 +48,7 @@ public final class SentinelPluginTest extends 
AbstractPluginDataInit {
 
     private static final String TEST_SENTINEL_PATH = 
"/http/test/sentinel/pass";
 
-    private static final String TEST_SENTINEL_FALLBACK_PATH = 
"/http/test/request/accepted";
+    private static final String TEST_SENTINEL_FALLBACK_PATH = 
"fallback:/fallback/sentinel";
 
     @BeforeEach
     public void setup() throws IOException {
@@ -82,8 +82,9 @@ public final class SentinelPluginTest extends 
AbstractPluginDataInit {
         Map<String, Object> result = 
HttpHelper.INSTANCE.postGateway(TEST_SENTINEL_PATH, returnType);
         assertNotNull(result);
         assertEquals("pass", result.get("msg"));
-        ResultBean fallbackRet = 
HttpHelper.INSTANCE.postGateway(TEST_SENTINEL_PATH, ResultBean.class);
-        assertEquals(202, fallbackRet.getCode());
+        Map<String, Object> fallbackRet = 
HttpHelper.INSTANCE.postGateway(TEST_SENTINEL_PATH, returnType);
+        assertEquals(ShenyuResultEnum.SENTINEL_PLUGIN_FALLBACK.getCode(), 
((Number) fallbackRet.get("code")).intValue());
+        assertEquals(ShenyuResultEnum.SENTINEL_PLUGIN_FALLBACK.getMsg(), 
fallbackRet.get("message"));
     }
 
     private static List<ConditionData> buildSelectorConditionList() {
diff --git 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-apache-dubbo/deploy/kind-config.yaml
 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-apache-dubbo/deploy/kind-config.yaml
index 0dc0313af1..7cbb7298ac 100644
--- 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-apache-dubbo/deploy/kind-config.yaml
+++ 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-apache-dubbo/deploy/kind-config.yaml
@@ -34,3 +34,6 @@ nodes:
       - containerPort: 30095
         hostPort: 30095
         protocol: TCP
+      - containerPort: 31187
+        hostPort: 31187
+        protocol: TCP
diff --git 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-apache-dubbo/script/healthcheck.sh
 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-apache-dubbo/script/healthcheck.sh
index 22a76034ab..63a920d983 100644
--- 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-apache-dubbo/script/healthcheck.sh
+++ 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-apache-dubbo/script/healthcheck.sh
@@ -17,20 +17,4 @@
 #
 
 PRGDIR=`dirname "$0"`
-for service in `grep -v -E "^$|^#" ${PRGDIR}/services.list`
-do
-    for loop in `seq 1 30`
-    do
-        status=`curl -o /dev/null -s -w %{http_code} $service`
-        echo -e "curl $service response $status"
-
-        if [ $status -eq 200  ]; then
-            break
-        fi
-
-        sleep 2
-    done
-done
-
-sleep 3
-echo -e "\n-------------------"
+bash "${PRGDIR}/../../k8s-ingress-healthcheck.sh" "${PRGDIR}/services.list" 3
diff --git 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-grpc/deploy/kind-config.yaml
 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-grpc/deploy/kind-config.yaml
index 0dc0313af1..ee65b29b5f 100644
--- 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-grpc/deploy/kind-config.yaml
+++ 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-grpc/deploy/kind-config.yaml
@@ -34,3 +34,6 @@ nodes:
       - containerPort: 30095
         hostPort: 30095
         protocol: TCP
+      - containerPort: 31183
+        hostPort: 31183
+        protocol: TCP
diff --git 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-grpc/script/healthcheck.sh
 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-grpc/script/healthcheck.sh
index 22a76034ab..63a920d983 100644
--- 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-grpc/script/healthcheck.sh
+++ 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-grpc/script/healthcheck.sh
@@ -17,20 +17,4 @@
 #
 
 PRGDIR=`dirname "$0"`
-for service in `grep -v -E "^$|^#" ${PRGDIR}/services.list`
-do
-    for loop in `seq 1 30`
-    do
-        status=`curl -o /dev/null -s -w %{http_code} $service`
-        echo -e "curl $service response $status"
-
-        if [ $status -eq 200  ]; then
-            break
-        fi
-
-        sleep 2
-    done
-done
-
-sleep 3
-echo -e "\n-------------------"
+bash "${PRGDIR}/../../k8s-ingress-healthcheck.sh" "${PRGDIR}/services.list" 3
diff --git 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-grpc/script/services.list
 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-grpc/script/services.list
index ef0e742c04..722dc8fce2 100644
--- 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-grpc/script/services.list
+++ 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-grpc/script/services.list
@@ -15,4 +15,4 @@
 # limitations under the License.
 
 http://localhost:30095/actuator/health
-http://localhost:31184/actuator/health
+http://localhost:31183/grpc/actuator/health
diff --git 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-http/deploy/kind-config.yaml
 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-http/deploy/kind-config.yaml
index 0dc0313af1..05fc45360e 100644
--- 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-http/deploy/kind-config.yaml
+++ 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-http/deploy/kind-config.yaml
@@ -34,3 +34,6 @@ nodes:
       - containerPort: 30095
         hostPort: 30095
         protocol: TCP
+      - containerPort: 31189
+        hostPort: 31189
+        protocol: TCP
diff --git 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-http/script/healthcheck.sh
 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-http/script/healthcheck.sh
index 896e784144..a2e645436b 100644
--- 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-http/script/healthcheck.sh
+++ 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-http/script/healthcheck.sh
@@ -17,20 +17,4 @@
 #
 
 PRGDIR=`dirname "$0"`
-for service in `grep -v -E "^$|^#" ${PRGDIR}/services.list`
-do
-    for loop in `seq 1 30`
-    do
-        status=`curl -o /dev/null -s -w %{http_code} $service`
-        echo -e "curl $service response $status"
-
-        if [ $status -eq 200  ]; then
-            break
-        fi
-
-        sleep 2
-    done
-done
-
-sleep 20
-echo -e "\n-------------------"
+bash "${PRGDIR}/../../k8s-ingress-healthcheck.sh" "${PRGDIR}/services.list" 20
diff --git 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-sofa/deploy/kind-config.yaml
 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-sofa/deploy/kind-config.yaml
index 0dc0313af1..bdd2b447ea 100644
--- 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-sofa/deploy/kind-config.yaml
+++ 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-sofa/deploy/kind-config.yaml
@@ -34,3 +34,6 @@ nodes:
       - containerPort: 30095
         hostPort: 30095
         protocol: TCP
+      - containerPort: 31112
+        hostPort: 31112
+        protocol: TCP
diff --git 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-sofa/script/services.list
 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-sofa/script/services.list
index ef0e742c04..b539a8f5ac 100644
--- 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-sofa/script/services.list
+++ 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-sofa/script/services.list
@@ -15,4 +15,4 @@
 # limitations under the License.
 
 http://localhost:30095/actuator/health
-http://localhost:31184/actuator/health
+http://localhost:31112/actuator/health
diff --git 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-websocket/script/healthcheck.sh
 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-websocket/script/healthcheck.sh
index 896e784144..a2e645436b 100644
--- 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-websocket/script/healthcheck.sh
+++ 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-websocket/script/healthcheck.sh
@@ -17,20 +17,4 @@
 #
 
 PRGDIR=`dirname "$0"`
-for service in `grep -v -E "^$|^#" ${PRGDIR}/services.list`
-do
-    for loop in `seq 1 30`
-    do
-        status=`curl -o /dev/null -s -w %{http_code} $service`
-        echo -e "curl $service response $status"
-
-        if [ $status -eq 200  ]; then
-            break
-        fi
-
-        sleep 2
-    done
-done
-
-sleep 20
-echo -e "\n-------------------"
+bash "${PRGDIR}/../../k8s-ingress-healthcheck.sh" "${PRGDIR}/services.list" 20
diff --git 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-websocket/script/services.list
 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-websocket/script/services.list
index d6f04cdb1e..2852c6e404 100644
--- 
a/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-websocket/script/services.list
+++ 
b/shenyu-integrated-test/shenyu-integrated-test-k8s-ingress-websocket/script/services.list
@@ -14,4 +14,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-http://localhost:31188/new/feature/get/mapping/path
+http://localhost:30095/actuator/health
diff --git 
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/EndpointsReconciler.java
 
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/EndpointsReconciler.java
index b730336314..b02ccac54d 100644
--- 
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/EndpointsReconciler.java
+++ 
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/EndpointsReconciler.java
@@ -32,6 +32,7 @@ import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.shenyu.common.dto.SelectorData;
 import org.apache.shenyu.common.dto.convert.selector.DivideUpstream;
+import org.apache.shenyu.common.dto.convert.selector.WebSocketUpstream;
 import org.apache.shenyu.common.enums.PluginEnum;
 import org.apache.shenyu.common.exception.ShenyuException;
 import org.apache.shenyu.common.utils.GsonUtils;
@@ -42,6 +43,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -54,6 +56,8 @@ public class EndpointsReconciler implements Reconciler {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(EndpointsReconciler.class);
 
+    private static final Set<String> ENDPOINT_UPSTREAM_PLUGINS = new 
HashSet<>(Arrays.asList(PluginEnum.DIVIDE.getName(), 
PluginEnum.WEB_SOCKET.getName()));
+
     private final Lister<V1Ingress> ingressLister;
 
     private final Lister<V1Endpoints> endpointsLister;
@@ -101,17 +105,31 @@ public class EndpointsReconciler implements Reconciler {
             return new Result(false);
         }
 
-        // 1. Obtain upstream according to endpoints
-        List<DivideUpstream> upstreamList = 
getUpstreamFromEndpoints(v1Endpoints);
+        updateSelectors(ingressList, PluginEnum.DIVIDE.getName(), 
getDivideUpstreamFromEndpoints(v1Endpoints));
+        updateSelectors(ingressList, PluginEnum.WEB_SOCKET.getName(), 
getWebSocketUpstreamFromEndpoints(v1Endpoints));
+        LOG.info("Update selector for endpoint {}", request);
+
+        return new Result(false);
+    }
 
-        // 2. Update the handler of the selector
-        List<SelectorData> totalSelectors = 
shenyuCacheRepository.findSelectorDataList(PluginEnum.DIVIDE.getName());
+    private void updateSelectors(final List<Pair<String, String>> ingressList, 
final String pluginName, final String handle) {
+        if (!ENDPOINT_UPSTREAM_PLUGINS.contains(pluginName)) {
+            return;
+        }
+        List<SelectorData> totalSelectors = 
shenyuCacheRepository.findSelectorDataList(pluginName);
+        if (CollectionUtils.isEmpty(totalSelectors)) {
+            return;
+        }
         Set<String> needUpdateSelectorId = new HashSet<>();
-        //TODO Adaptation of other plugins
         ingressList.forEach(item -> {
-            List<String> selectorIdList = 
IngressSelectorCache.getInstance().get(item.getLeft(), item.getRight(), 
PluginEnum.DIVIDE.getName());
-            needUpdateSelectorId.addAll(selectorIdList);
+            List<String> selectorIdList = 
IngressSelectorCache.getInstance().get(item.getLeft(), item.getRight(), 
pluginName);
+            if (CollectionUtils.isNotEmpty(selectorIdList)) {
+                needUpdateSelectorId.addAll(selectorIdList);
+            }
         });
+        if (needUpdateSelectorId.isEmpty()) {
+            return;
+        }
         totalSelectors.forEach(selectorData -> {
             if (needUpdateSelectorId.contains(selectorData.getId())) {
                 SelectorData newSelectorData = 
SelectorData.builder().id(selectorData.getId())
@@ -124,19 +142,45 @@ public class EndpointsReconciler implements Reconciler {
                         .enabled(selectorData.getEnabled())
                         .logged(selectorData.getLogged())
                         .continued(selectorData.getContinued())
-                        .handle(GsonUtils.getInstance().toJson(upstreamList))
+                        .handle(handle)
                         .conditionList(selectorData.getConditionList())
                         .matchRestful(selectorData.getMatchRestful()).build();
                 
shenyuCacheRepository.saveOrUpdateSelectorData(newSelectorData);
             }
         });
-        LOG.info("Update selector for endpoint {}", request);
-
-        return new Result(false);
     }
 
-    private List<DivideUpstream> getUpstreamFromEndpoints(final V1Endpoints 
v1Endpoints) {
+    private String getDivideUpstreamFromEndpoints(final V1Endpoints 
v1Endpoints) {
         List<DivideUpstream> res = new ArrayList<>();
+        endpointAddresses(v1Endpoints).forEach(pair -> {
+            DivideUpstream upstream = new DivideUpstream();
+            upstream.setUpstreamUrl(pair.getLeft().getIp() + ":" + 
pair.getRight());
+            upstream.setWeight(100);
+            // TODO support config protocol in annotation
+            upstream.setProtocol("http://";);
+            upstream.setWarmup(0);
+            upstream.setStatus(true);
+            upstream.setUpstreamHost("");
+            res.add(upstream);
+        });
+        return GsonUtils.getInstance().toJson(res);
+    }
+
+    private String getWebSocketUpstreamFromEndpoints(final V1Endpoints 
v1Endpoints) {
+        List<WebSocketUpstream> res = new ArrayList<>();
+        endpointAddresses(v1Endpoints).forEach(pair -> 
res.add(WebSocketUpstream.builder()
+                .upstreamUrl(pair.getLeft().getIp() + ":" + pair.getRight())
+                .weight(100)
+                .protocol("ws://")
+                .warmup(0)
+                .status(true)
+                .host("")
+                .build()));
+        return GsonUtils.getInstance().toJson(res);
+    }
+
+    private List<Pair<V1EndpointAddress, String>> endpointAddresses(final 
V1Endpoints v1Endpoints) {
+        List<Pair<V1EndpointAddress, String>> res = new ArrayList<>();
         List<V1EndpointSubset> subsets = v1Endpoints.getSubsets();
         if (CollectionUtils.isNotEmpty(subsets)) {
             for (V1EndpointSubset subset : subsets) {
@@ -159,17 +203,8 @@ public class EndpointsReconciler implements Reconciler {
                     }
                 }
                 for (V1EndpointAddress address : addresses) {
-                    String ip = address.getIp();
-                    if (Objects.nonNull(ip)) {
-                        DivideUpstream upstream = new DivideUpstream();
-                        upstream.setUpstreamUrl(ip + ":" + port);
-                        upstream.setWeight(100);
-                        // TODO support config protocol in annotation
-                        upstream.setProtocol("http://";);
-                        upstream.setWarmup(0);
-                        upstream.setStatus(true);
-                        upstream.setUpstreamHost("");
-                        res.add(upstream);
+                    if (Objects.nonNull(address.getIp()) && 
Objects.nonNull(port)) {
+                        res.add(Pair.of(address, port));
                     }
                 }
             }
diff --git 
a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/EndpointsReconcilerTest.java
 
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/EndpointsReconcilerTest.java
new file mode 100644
index 0000000000..5d36eea3a9
--- /dev/null
+++ 
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/EndpointsReconcilerTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.k8s;
+
+import io.kubernetes.client.extended.controller.reconciler.Request;
+import io.kubernetes.client.extended.controller.reconciler.Result;
+import io.kubernetes.client.informer.SharedIndexInformer;
+import io.kubernetes.client.informer.cache.Indexer;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.models.CoreV1EndpointPort;
+import io.kubernetes.client.openapi.models.V1EndpointAddress;
+import io.kubernetes.client.openapi.models.V1EndpointSubsetBuilder;
+import io.kubernetes.client.openapi.models.V1Endpoints;
+import io.kubernetes.client.openapi.models.V1EndpointsBuilder;
+import io.kubernetes.client.openapi.models.V1Ingress;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.k8s.cache.IngressSelectorCache;
+import org.apache.shenyu.k8s.cache.ServiceIngressCache;
+import org.apache.shenyu.k8s.reconciler.EndpointsReconciler;
+import org.apache.shenyu.k8s.repository.ShenyuCacheRepository;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Collections;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Endpoints reconciler test.
+ */
+public final class EndpointsReconcilerTest {
+
+    /**
+     * test websocket selector update.
+     */
+    @Test
+    public void testUpdateWebSocketSelector() {
+        SharedIndexInformer<V1Ingress> ingressInformer = 
mock(SharedIndexInformer.class);
+        SharedIndexInformer<V1Endpoints> endpointsInformer = 
mock(SharedIndexInformer.class);
+        Indexer<V1Ingress> ingressIndexer = mock(Indexer.class);
+        Indexer<V1Endpoints> endpointsIndexer = mock(Indexer.class);
+        when(ingressInformer.getIndexer()).thenReturn(ingressIndexer);
+        when(endpointsInformer.getIndexer()).thenReturn(endpointsIndexer);
+
+        String namespace = "endpoint-websocket-ns";
+        String serviceName = "endpoint-websocket-service";
+        String ingressName = "endpoint-websocket-ingress";
+        String selectorId = "endpoint-websocket-selector";
+        V1Endpoints endpoints = new V1EndpointsBuilder().withKind("Endpoints")
+                
.withNewMetadata().withNamespace(namespace).withName(serviceName).endMetadata()
+                .withSubsets(new V1EndpointSubsetBuilder()
+                        .withAddresses(new V1EndpointAddress().ip("127.0.0.1"))
+                        .withPorts(new 
CoreV1EndpointPort().port(8001).protocol("TCP"))
+                        .build())
+                .build();
+        when(endpointsIndexer.getByKey(namespace + "/" + 
serviceName)).thenReturn(endpoints);
+
+        ServiceIngressCache.getInstance().putIngressName(namespace, 
serviceName, namespace, ingressName);
+        IngressSelectorCache.getInstance().put(namespace, ingressName, 
PluginEnum.WEB_SOCKET.getName(), selectorId);
+
+        ShenyuCacheRepository shenyuCacheRepository = 
mock(ShenyuCacheRepository.class);
+        SelectorData selectorData = SelectorData.builder()
+                .id(selectorId)
+                .pluginId(String.valueOf(PluginEnum.WEB_SOCKET.getCode()))
+                .pluginName(PluginEnum.WEB_SOCKET.getName())
+                .name("/**")
+                .handle("[]")
+                .enabled(true)
+                .build();
+        
when(shenyuCacheRepository.findSelectorDataList(PluginEnum.DIVIDE.getName())).thenReturn(Collections.emptyList());
+        
when(shenyuCacheRepository.findSelectorDataList(PluginEnum.WEB_SOCKET.getName())).thenReturn(Collections.singletonList(selectorData));
+
+        EndpointsReconciler endpointsReconciler = new 
EndpointsReconciler(ingressInformer, endpointsInformer, shenyuCacheRepository, 
mock(ApiClient.class));
+        Result result = endpointsReconciler.reconcile(new Request(namespace, 
serviceName));
+
+        Assertions.assertEquals(new Result(false), result);
+        ArgumentCaptor<SelectorData> selectorCaptor = 
ArgumentCaptor.forClass(SelectorData.class);
+        
verify(shenyuCacheRepository).saveOrUpdateSelectorData(selectorCaptor.capture());
+        SelectorData updatedSelector = selectorCaptor.getValue();
+        Assertions.assertEquals(PluginEnum.WEB_SOCKET.getName(), 
updatedSelector.getPluginName());
+        assertThat(updatedSelector.getHandle(), 
containsString("\"protocol\":\"ws://\""));
+        assertThat(updatedSelector.getHandle(), 
containsString("\"upstreamUrl\":\"127.0.0.1:8001\""));
+    }
+}
diff --git 
a/shenyu-web/src/main/java/org/apache/shenyu/web/fallback/DefaultFallbackController.java
 
b/shenyu-web/src/main/java/org/apache/shenyu/web/fallback/DefaultFallbackController.java
index 4b375f8158..b4a14e4dc1 100644
--- 
a/shenyu-web/src/main/java/org/apache/shenyu/web/fallback/DefaultFallbackController.java
+++ 
b/shenyu-web/src/main/java/org/apache/shenyu/web/fallback/DefaultFallbackController.java
@@ -19,12 +19,11 @@ package org.apache.shenyu.web.fallback;
 
 import org.apache.shenyu.plugin.api.result.ShenyuResultEnum;
 import org.apache.shenyu.plugin.api.result.ShenyuResultWrap;
-import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 /**
- * The default fallback for hystrix plugin and resilience4j plugin.
+ * The default fallback for fault tolerance plugins.
  */
 @RestController
 @RequestMapping("/fallback")
@@ -35,7 +34,7 @@ public class DefaultFallbackController {
      *
      * @return the shenyu result
      */
-    @GetMapping("/hystrix")
+    @RequestMapping("/hystrix")
     public Object hystrixPluginFallback() {
         return 
ShenyuResultWrap.error(ShenyuResultEnum.HYSTRIX_PLUGIN_FALLBACK, null);
     }
@@ -45,7 +44,7 @@ public class DefaultFallbackController {
      *
      * @return the shenyu result
      */
-    @GetMapping("/resilience4j")
+    @RequestMapping("/resilience4j")
     public Object resilience4jFallBack() {
         return 
ShenyuResultWrap.error(ShenyuResultEnum.RESILIENCE4J_PLUGIN_FALLBACK, null);
     }
@@ -55,7 +54,7 @@ public class DefaultFallbackController {
      *
      * @return the object
      */
-    @GetMapping("/sentinel")
+    @RequestMapping("/sentinel")
     public Object sentinelFallBack() {
         return 
ShenyuResultWrap.error(ShenyuResultEnum.SENTINEL_PLUGIN_FALLBACK, null);
     }
diff --git 
a/shenyu-web/src/test/java/org/apache/shenyu/web/fallback/DefaultFallbackControllerTest.java
 
b/shenyu-web/src/test/java/org/apache/shenyu/web/fallback/DefaultFallbackControllerTest.java
index aa5e608736..608b496a66 100644
--- 
a/shenyu-web/src/test/java/org/apache/shenyu/web/fallback/DefaultFallbackControllerTest.java
+++ 
b/shenyu-web/src/test/java/org/apache/shenyu/web/fallback/DefaultFallbackControllerTest.java
@@ -66,4 +66,11 @@ public final class DefaultFallbackControllerTest {
         assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
     }
 
+    @Test
+    public void sentinelFallbackSupportsOriginalRequestMethod() throws 
Exception {
+        final MockHttpServletResponse response = 
this.mockMvc.perform(MockMvcRequestBuilders.post("/fallback/sentinel"))
+                .andReturn().getResponse();
+        assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
+    }
+
 }

Reply via email to