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

liuhongyu 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 71ff9d26e0 [ISSUE #6235] Set upstream check for each upstream. (#6272)
71ff9d26e0 is described below

commit 71ff9d26e01e233fc571eac02e55f5ee86a65bb0
Author: jonasHanhan <[email protected]>
AuthorDate: Fri Jan 16 11:06:56 2026 +0800

    [ISSUE #6235] Set upstream check for each upstream. (#6272)
    
    * fix common : support per-upstream health check.
    
    * fix admin : sanitize upstream url parsing.
    
    * fix common : align health check flag handling.
    
    ---------
    
    Co-authored-by: aias00 <[email protected]>
---
 .../admin/service/impl/UpstreamCheckService.java   | 24 +++++++
 .../shenyu/admin/transfer/DiscoveryTransfer.java   | 40 +++++++++--
 .../shenyu/admin/utils/CommonUpstreamUtils.java    | 10 ++-
 .../admin/service/UpstreamCheckServiceTest.java    | 82 ++++++++++++++++++++++
 .../dto/convert/selector/CommonUpstream.java       | 30 +++++++-
 .../dto/convert/selector/DivideUpstream.java       | 17 +++++
 .../common/dto/convert/selector/GrpcUpstream.java  | 17 +++++
 .../loadbalancer/cache/UpstreamCacheManager.java   | 10 +++
 .../loadbalancer/cache/UpstreamCheckTask.java      |  7 ++
 .../shenyu/loadbalancer/entity/Upstream.java       | 40 +++++++++++
 .../cache/UpstreamCacheManagerTest.java            | 29 +++++++-
 .../loadbalancer/cache/UpstreamCheckTaskTest.java  | 41 +++++++++++
 .../divide/handler/DivideUpstreamDataHandler.java  |  1 +
 .../dubbo/proxy/ApacheDubboProxyService.java       |  1 +
 .../handler/AbstractDubboPluginDataHandler.java    |  1 +
 .../handler/GrpcDiscoveryUpstreamDataHandler.java  | 20 ++++--
 .../grpc/loadbalance/picker/ShenyuPicker.java      |  1 +
 .../handler/WebSocketUpstreamDataHandler.java      |  1 +
 .../web/controller/LocalPluginController.java      |  1 +
 19 files changed, 355 insertions(+), 18 deletions(-)

diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/UpstreamCheckService.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/UpstreamCheckService.java
index 5e559ebddd..7fcbee74fe 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/UpstreamCheckService.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/UpstreamCheckService.java
@@ -222,6 +222,8 @@ public class UpstreamCheckService {
             if (!exists.isPresent()) {
                 upstreams.add(commonUpstream);
             } else {
+                CommonUpstream existUpstream = exists.get();
+                
existUpstream.setHealthCheckEnabled(commonUpstream.isHealthCheckEnabled());
                 LOG.info("upstream host {} is exists.", 
commonUpstream.getUpstreamHost());
             }
             PENDING_SYNC.add(commonUpstream.hashCode());
@@ -251,6 +253,12 @@ public class UpstreamCheckService {
         if (!REGISTER_TYPE_HTTP.equalsIgnoreCase(registerType) || !checked) {
             return false;
         }
+        if (!commonUpstream.isHealthCheckEnabled()) {
+            commonUpstream.setStatus(true);
+            commonUpstream.setTimestamp(System.currentTimeMillis());
+            this.submit(selectorId, commonUpstream);
+            return false;
+        }
         final boolean pass = 
UpstreamCheckUtils.checkUrl(commonUpstream.getUpstreamUrl());
         if (pass) {
             this.submit(selectorId, commonUpstream);
@@ -310,6 +318,14 @@ public class UpstreamCheckService {
         ZOMBIE_SET.remove(zombieUpstream);
         String selectorId = zombieUpstream.getSelectorId();
         CommonUpstream commonUpstream = zombieUpstream.getCommonUpstream();
+        if (!commonUpstream.isHealthCheckEnabled()) {
+            commonUpstream.setTimestamp(System.currentTimeMillis());
+            commonUpstream.setStatus(true);
+            List<CommonUpstream> old = 
ListUtils.unmodifiableList(UPSTREAM_MAP.getOrDefault(selectorId, 
Collections.emptyList()));
+            this.submitJust(selectorId, commonUpstream);
+            updateHandler(selectorId, old, UPSTREAM_MAP.get(selectorId));
+            return;
+        }
         final boolean pass = 
UpstreamCheckUtils.checkUrl(commonUpstream.getUpstreamUrl());
         if (pass) {
             commonUpstream.setTimestamp(System.currentTimeMillis());
@@ -332,6 +348,14 @@ public class UpstreamCheckService {
         final List<CompletableFuture<CommonUpstream>> checkFutures = new 
ArrayList<>(upstreamList.size());
         for (CommonUpstream commonUpstream : upstreamList) {
             checkFutures.add(CompletableFuture.supplyAsync(() -> {
+                if (!commonUpstream.isHealthCheckEnabled()) {
+                    if (!commonUpstream.isStatus()) {
+                        
commonUpstream.setTimestamp(System.currentTimeMillis());
+                        commonUpstream.setStatus(true);
+                        PENDING_SYNC.add(commonUpstream.hashCode());
+                    }
+                    return commonUpstream;
+                }
                 final boolean pass = 
UpstreamCheckUtils.checkUrl(commonUpstream.getUpstreamUrl());
                 if (pass) {
                     if (!commonUpstream.isStatus()) {
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/transfer/DiscoveryTransfer.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/transfer/DiscoveryTransfer.java
index ff24cc9935..91f854cd5f 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/transfer/DiscoveryTransfer.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/transfer/DiscoveryTransfer.java
@@ -78,7 +78,14 @@ public enum DiscoveryTransfer {
     public CommonUpstream mapToCommonUpstream(DiscoveryUpstreamData 
discoveryUpstreamData) {
         return Optional.ofNullable(discoveryUpstreamData).map(data -> {
             String url = data.getUrl();
-            return new CommonUpstream(data.getProtocol(), url.split(":")[0], 
url, false, data.getDateCreated().getTime());
+            CommonUpstream commonUpstream = new 
CommonUpstream(data.getProtocol(), url.split(":")[0], url, false,
+                    data.getDateCreated().getTime());
+            Properties properties = Optional.ofNullable(data.getProps())
+                    .map(props -> GsonUtils.getInstance().fromJson(props, 
Properties.class))
+                    .orElse(new Properties());
+            commonUpstream
+                    
.setHealthCheckEnabled(Boolean.parseBoolean(properties.getProperty("healthCheckEnabled",
 "true")));
+            return commonUpstream;
         }).orElse(null);
     }
 
@@ -103,7 +110,6 @@ public enum DiscoveryTransfer {
         }).orElse(null);
     }
 
-
     public DiscoveryRelVO mapToVo(DiscoveryRelDO discoveryRelDO) {
         return Optional.ofNullable(discoveryRelDO).map(data -> {
             DiscoveryRelVO discoveryRelVO = new DiscoveryRelVO();
@@ -116,7 +122,6 @@ public enum DiscoveryTransfer {
         }).orElse(null);
     }
 
-
     public DiscoveryRelDO mapToDO(DiscoveryRelDTO discoveryRelDTO) {
         return Optional.ofNullable(discoveryRelDTO).map(data -> {
             DiscoveryRelDO discoveryRelDO = new DiscoveryRelDO();
@@ -335,13 +340,38 @@ public enum DiscoveryTransfer {
             return discoveryUpstreamDTO;
         }).orElse(null);
     }
-    
+
     /**
      * mapToDiscoveryUpstreamData.
+     *
      * @param commonUpstream commonUpstream
      * @return DiscoveryUpstreamData
      */
     public DiscoveryUpstreamData mapToDiscoveryUpstreamData(CommonUpstream 
commonUpstream) {
-        return 
mapToData(CommonUpstreamUtils.buildDefaultDiscoveryUpstreamDTO(commonUpstream.getUpstreamUrl().split(":")[0],
 Integer.valueOf(commonUpstream.getUpstreamUrl().split(":")[1]), 
commonUpstream.getProtocol(),commonUpstream.getNamespaceId()));
+        String upstreamUrl = commonUpstream.getUpstreamUrl();
+        String[] parts = Optional.ofNullable(upstreamUrl)
+                .map(url -> url.split(":", 2))
+                .orElseThrow(() -> new IllegalArgumentException("Upstream URL 
must not be null"));
+        if (parts.length < 2) {
+            throw new IllegalArgumentException("Invalid upstream URL, expected 
'host:port' format but was: " + upstreamUrl);
+        }
+        String host = parts[0];
+        int port;
+        try {
+            port = Integer.parseInt(parts[1]);
+        } catch (NumberFormatException ex) {
+            throw new IllegalArgumentException("Invalid port in upstream URL: 
" + upstreamUrl, ex);
+        }
+        DiscoveryUpstreamDTO discoveryUpstreamDTO = 
CommonUpstreamUtils.buildDefaultDiscoveryUpstreamDTO(
+                host,
+                port,
+                commonUpstream.getProtocol(),
+                commonUpstream.getNamespaceId());
+        Properties properties = 
Optional.ofNullable(discoveryUpstreamDTO.getProps())
+                .map(props -> GsonUtils.getInstance().fromJson(props, 
Properties.class))
+                .orElse(new Properties());
+        properties.setProperty("healthCheckEnabled", 
String.valueOf(commonUpstream.isHealthCheckEnabled()));
+        
discoveryUpstreamDTO.setProps(GsonUtils.getInstance().toJson(properties));
+        return mapToData(discoveryUpstreamDTO);
     }
 }
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/CommonUpstreamUtils.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/CommonUpstreamUtils.java
index a6df53b953..6a6cdc5c2e 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/CommonUpstreamUtils.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/CommonUpstreamUtils.java
@@ -246,9 +246,13 @@ public class CommonUpstreamUtils {
         return Optional.ofNullable(upstreamList)
                 .orElse(Collections.emptyList())
                 .stream()
-                .map(upstream -> new CommonUpstream(upstream.getProtocol(),
-                        upstream.getUpstreamHost(), upstream.getUpstreamUrl(),
-                        upstream.isStatus(), upstream.getTimestamp()))
+                .map(upstream -> {
+                    CommonUpstream commonUpstream = new 
CommonUpstream(upstream.getProtocol(),
+                            upstream.getUpstreamHost(), 
upstream.getUpstreamUrl(),
+                            upstream.isStatus(), upstream.getTimestamp());
+                    
commonUpstream.setHealthCheckEnabled(upstream.isHealthCheckEnabled());
+                    return commonUpstream;
+                })
                 .collect(Collectors.toList());
     }
 
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/UpstreamCheckServiceTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/UpstreamCheckServiceTest.java
index 04de274923..45875affdd 100644
--- 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/UpstreamCheckServiceTest.java
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/UpstreamCheckServiceTest.java
@@ -313,4 +313,86 @@ public final class UpstreamCheckServiceTest {
         upstreamMap.put("UrlReachableAnother", 
Collections.singletonList(divideUpstream1));
         upstreamMap.put("UrlErrorAnother", 
Collections.singletonList(divideUpstream2));
     }
+
+    @Test
+    public void testCheckAndSubmitWithHealthCheckDisabled() {
+        ScheduledThreadPoolExecutor executor = new 
ScheduledThreadPoolExecutor(1,
+                ShenyuThreadFactory.create("scheduled-upstream-task", false));
+        ReflectionTestUtils.setField(upstreamCheckService, "executor", 
executor);
+
+        final DivideUpstream divideUpstream = DivideUpstream.builder()
+                .upstreamUrl("unreachable-url:8080")
+                .upstreamHost("unreachable-host")
+                .healthCheckEnabled(false)
+                .status(false)
+                .build();
+
+        boolean result = upstreamCheckService.checkAndSubmit("testSelector", 
divideUpstream);
+
+        assertFalse(result);
+        assertTrue(divideUpstream.isStatus());
+        assertTrue(upstreamMap.containsKey("testSelector"));
+    }
+
+    @Test
+    public void testSubmitWithHealthCheckDisabled() {
+        ScheduledThreadPoolExecutor executor = new 
ScheduledThreadPoolExecutor(1,
+                ShenyuThreadFactory.create("scheduled-upstream-task", false));
+        ReflectionTestUtils.setField(upstreamCheckService, "executor", 
executor);
+
+        final DivideUpstream divideUpstream = DivideUpstream.builder()
+                .upstreamUrl("any-url:8080")
+                .upstreamHost("any-host")
+                .healthCheckEnabled(false)
+                .status(true)
+                .build();
+
+        upstreamCheckService.submit("testSelectorDisabled", divideUpstream);
+
+        assertTrue(upstreamMap.containsKey("testSelectorDisabled"));
+        assertEquals(1, upstreamMap.get("testSelectorDisabled").size());
+    }
+
+    @Test
+    public void testHealthCheckEnabledDefaultsToTrue() {
+        final DivideUpstream divideUpstream = DivideUpstream.builder()
+                .upstreamUrl("test-url:8080")
+                .upstreamHost("test-host")
+                .build();
+
+        assertTrue(divideUpstream.isHealthCheckEnabled());
+    }
+
+    @Test
+    public void testSubmitSyncsHealthCheckEnabledForExistingUpstream() {
+        ScheduledThreadPoolExecutor executor = new 
ScheduledThreadPoolExecutor(1,
+                ShenyuThreadFactory.create("scheduled-upstream-task", false));
+        ReflectionTestUtils.setField(upstreamCheckService, "executor", 
executor);
+
+        final String selectorId = "testSyncSelector";
+
+        final DivideUpstream upstream1 = DivideUpstream.builder()
+                .upstreamUrl("sync-url:8080")
+                .upstreamHost("sync-host")
+                .healthCheckEnabled(true)
+                .status(true)
+                .build();
+        upstreamCheckService.submit(selectorId, upstream1);
+
+        assertTrue(upstreamMap.containsKey(selectorId));
+        assertEquals(1, upstreamMap.get(selectorId).size());
+        assertTrue(upstreamMap.get(selectorId).get(0).isHealthCheckEnabled());
+
+        final DivideUpstream upstream2 = DivideUpstream.builder()
+                .upstreamUrl("sync-url:8080")
+                .upstreamHost("sync-host")
+                .healthCheckEnabled(false)
+                .status(true)
+                .build();
+        upstreamCheckService.submit(selectorId, upstream2);
+
+        assertEquals(1, upstreamMap.get(selectorId).size());
+        assertFalse(upstreamMap.get(selectorId).get(0).isHealthCheckEnabled());
+        assertTrue(upstreamMap.get(selectorId).get(0).isStatus());
+    }
 }
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/CommonUpstream.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/CommonUpstream.java
index e9dd8c3a51..13f10e7b1f 100644
--- 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/CommonUpstream.java
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/CommonUpstream.java
@@ -59,6 +59,11 @@ public class CommonUpstream {
      */
     private boolean gray;
 
+    /**
+     * health check enabled.
+     */
+    private boolean healthCheckEnabled = true;
+
     /**
      * Instantiates a new Common upstream.
      */
@@ -209,6 +214,24 @@ public class CommonUpstream {
         this.gray = gray;
     }
 
+    /**
+     * get healthCheckEnabled.
+     *
+     * @return healthCheckEnabled
+     */
+    public boolean isHealthCheckEnabled() {
+        return healthCheckEnabled;
+    }
+
+    /**
+     * set healthCheckEnabled.
+     *
+     * @param healthCheckEnabled healthCheckEnabled
+     */
+    public void setHealthCheckEnabled(final boolean healthCheckEnabled) {
+        this.healthCheckEnabled = healthCheckEnabled;
+    }
+
     /**
      * set namespaceId.
      *
@@ -231,12 +254,13 @@ public class CommonUpstream {
                 && Objects.equals(protocol, that.protocol)
                 && Objects.equals(gray, that.gray)
                 && Objects.equals(upstreamUrl, that.upstreamUrl)
-                && Objects.equals(namespaceId, that.namespaceId);
+                && Objects.equals(namespaceId, that.namespaceId)
+                && Objects.equals(healthCheckEnabled, that.healthCheckEnabled);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(upstreamHost, protocol, upstreamUrl, namespaceId, 
gray);
+        return Objects.hash(upstreamHost, protocol, upstreamUrl, namespaceId, 
gray, healthCheckEnabled);
     }
 
     @Override
@@ -259,6 +283,8 @@ public class CommonUpstream {
                 + namespaceId
                 + ", gray="
                 + gray
+                + ", healthCheckEnabled="
+                + healthCheckEnabled
                 + '}';
     }
 }
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/DivideUpstream.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/DivideUpstream.java
index 7f972cf170..ce1667138e 100644
--- 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/DivideUpstream.java
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/DivideUpstream.java
@@ -58,6 +58,7 @@ public class DivideUpstream extends CommonUpstream {
         setTimestamp(builder.timestamp);
         this.warmup = builder.warmup;
         setNamespaceId(builder.namespaceId);
+        setHealthCheckEnabled(builder.healthCheckEnabled);
     }
 
     /**
@@ -200,6 +201,11 @@ public class DivideUpstream extends CommonUpstream {
          */
         private String namespaceId;
 
+        /**
+         * healthCheckEnabled.
+         */
+        private boolean healthCheckEnabled = true;
+
         /**
          * no args constructor.
          */
@@ -303,5 +309,16 @@ public class DivideUpstream extends CommonUpstream {
             this.namespaceId = namespaceId;
             return this;
         }
+
+        /**
+         * build healthCheckEnabled.
+         *
+         * @param healthCheckEnabled healthCheckEnabled
+         * @return this
+         */
+        public Builder healthCheckEnabled(final boolean healthCheckEnabled) {
+            this.healthCheckEnabled = healthCheckEnabled;
+            return this;
+        }
     }
 }
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/GrpcUpstream.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/GrpcUpstream.java
index 21eb75cf73..8379e719e8 100644
--- 
a/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/GrpcUpstream.java
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/dto/convert/selector/GrpcUpstream.java
@@ -46,6 +46,7 @@ public final class GrpcUpstream extends CommonUpstream {
         setStatus(statusValue);
         setTimestamp(builder.timestamp);
         setNamespaceId(builder.namespaceId);
+        setHealthCheckEnabled(builder.healthCheckEnabled);
     }
 
     /**
@@ -163,6 +164,11 @@ public final class GrpcUpstream extends CommonUpstream {
          */
         private String namespaceId;
 
+        /**
+         * health check enabled.
+         */
+        private boolean healthCheckEnabled = true;
+
         /**
          * no args constructor.
          */
@@ -255,5 +261,16 @@ public final class GrpcUpstream extends CommonUpstream {
             this.namespaceId = namespaceId;
             return this;
         }
+
+        /**
+         * build healthCheckEnabled.
+         *
+         * @param healthCheckEnabled healthCheckEnabled
+         * @return this
+         */
+        public Builder healthCheckEnabled(final boolean healthCheckEnabled) {
+            this.healthCheckEnabled = healthCheckEnabled;
+            return this;
+        }
     }
 }
diff --git 
a/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/cache/UpstreamCacheManager.java
 
b/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/cache/UpstreamCacheManager.java
index 30a76ed410..eeff3c8195 100644
--- 
a/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/cache/UpstreamCacheManager.java
+++ 
b/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/cache/UpstreamCacheManager.java
@@ -146,6 +146,12 @@ public final class UpstreamCacheManager {
      */
     public void submit(final String selectorId, final List<Upstream> 
upstreamList) {
         List<Upstream> actualUpstreamList = Objects.isNull(upstreamList) ? 
Lists.newArrayList() : upstreamList;
+        actualUpstreamList.forEach(upstream -> {
+            if (!upstream.isHealthCheckEnabled()) {
+                upstream.setStatus(true);
+                upstream.setHealthy(true);
+            }
+        });
         Map<Boolean, List<Upstream>> partitionedUpstreams = 
actualUpstreamList.stream()
                 .collect(Collectors.partitioningBy(Upstream::isStatus));
         List<Upstream> validUpstreamList = partitionedUpstreams.get(true);
@@ -173,6 +179,10 @@ public final class UpstreamCacheManager {
                 Upstream matchedExistUp = existUpstreamMap.get(key);
                 if (Objects.nonNull(matchedExistUp)) {
                     matchedExistUp.setWeight(validUp.getWeight());
+                    
matchedExistUp.setHealthCheckEnabled(validUp.isHealthCheckEnabled());
+                    if (!matchedExistUp.isHealthCheckEnabled()) {
+                        matchedExistUp.setHealthy(true);
+                    }
                 }
             });
 
diff --git 
a/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/cache/UpstreamCheckTask.java
 
b/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/cache/UpstreamCheckTask.java
index 359aa06253..eef5cc47c5 100644
--- 
a/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/cache/UpstreamCheckTask.java
+++ 
b/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/cache/UpstreamCheckTask.java
@@ -190,6 +190,13 @@ public final class UpstreamCheckTask implements Runnable {
     }
 
     private UpstreamWithSelectorId check(final String selectorId, final 
Upstream upstream) {
+        if (!upstream.isHealthCheckEnabled()) {
+            if (!upstream.isHealthy()) {
+                upstream.setHealthy(true);
+                upstream.setLastHealthTimestamp(System.currentTimeMillis());
+            }
+            return new UpstreamWithSelectorId(selectorId, upstream);
+        }
         boolean pass = UpstreamCheckUtils.checkUrl(upstream.getUrl(), 
checkTimeout);
         if (pass) {
             if (upstream.isHealthy()) {
diff --git 
a/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/entity/Upstream.java
 
b/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/entity/Upstream.java
index c815d67e28..36656e3493 100644
--- 
a/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/entity/Upstream.java
+++ 
b/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/entity/Upstream.java
@@ -104,6 +104,11 @@ public final class Upstream {
      * this is gray.
      */
     private boolean gray;
+
+    /**
+     * health check enabled.
+     */
+    private boolean healthCheckEnabled = true;
     
     private Map<String, String> metadata = new ConcurrentHashMap<>();
     
@@ -127,6 +132,7 @@ public final class Upstream {
         this.group = builder.group;
         this.version = builder.version;
         this.gray = builder.gray;
+        this.healthCheckEnabled = builder.healthCheckEnabled;
     }
     
     /**
@@ -227,6 +233,24 @@ public final class Upstream {
     public void setHealthy(final boolean healthy) {
         this.healthy = healthy;
     }
+
+    /**
+     * Is health check enabled.
+     *
+     * @return the boolean
+     */
+    public boolean isHealthCheckEnabled() {
+        return healthCheckEnabled;
+    }
+
+    /**
+     * Sets health check enabled.
+     *
+     * @param healthCheckEnabled the health check enabled
+     */
+    public void setHealthCheckEnabled(final boolean healthCheckEnabled) {
+        this.healthCheckEnabled = healthCheckEnabled;
+    }
     
     /**
      * Gets last health timestamp.
@@ -552,6 +576,11 @@ public final class Upstream {
          */
         private Boolean gray = false;
 
+        /**
+         * health check enabled.
+         */
+        private boolean healthCheckEnabled = true;
+
         /**
          * no args constructor.
          */
@@ -667,5 +696,16 @@ public final class Upstream {
             return this;
         }
 
+        /**
+         * build healthCheckEnabled.
+         *
+         * @param healthCheckEnabled healthCheckEnabled
+         * @return this builder
+         */
+        public Builder healthCheckEnabled(final boolean healthCheckEnabled) {
+            this.healthCheckEnabled = healthCheckEnabled;
+            return this;
+        }
+
     }
 }
diff --git 
a/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/cache/UpstreamCacheManagerTest.java
 
b/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/cache/UpstreamCacheManagerTest.java
index 051f3b773b..323535f230 100644
--- 
a/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/cache/UpstreamCacheManagerTest.java
+++ 
b/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/cache/UpstreamCacheManagerTest.java
@@ -29,7 +29,6 @@ import org.junit.jupiter.api.Assertions;
 import java.util.ArrayList;
 import java.util.List;
 
-
 /**
  * The type UpstreamCacheManager check task test.
  */
@@ -79,4 +78,32 @@ public class UpstreamCacheManagerTest {
         final UpstreamCacheManager upstreamCacheManager = 
UpstreamCacheManager.getInstance();
         
Assertions.assertNull(upstreamCacheManager.findUpstreamListBySelectorId(SELECTOR_ID));
     }
+
+    @Test
+    @Order(5)
+    public void testSubmitSyncsHealthCheckEnabled() {
+        final UpstreamCacheManager upstreamCacheManager = 
UpstreamCacheManager.getInstance();
+        final String testSelectorId = "HEALTH_CHECK_SYNC_TEST";
+
+        // First submit with healthCheckEnabled = true (default)
+        List<Upstream> upstreamList = new ArrayList<>(1);
+        upstreamList.add(Upstream.builder()
+                .url("health-check-url:8080")
+                .status(true)
+                .healthCheckEnabled(true)
+                .build());
+        upstreamCacheManager.submit(testSelectorId, upstreamList);
+
+        // Now submit with the same URL but healthCheckEnabled = false
+        List<Upstream> updatedList = new ArrayList<>(1);
+        updatedList.add(Upstream.builder()
+                .url("health-check-url:8080")
+                .status(true)
+                .healthCheckEnabled(false)
+                .build());
+        upstreamCacheManager.submit(testSelectorId, updatedList);
+
+        // Clean up
+        upstreamCacheManager.removeByKey(testSelectorId);
+    }
 }
diff --git 
a/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/cache/UpstreamCheckTaskTest.java
 
b/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/cache/UpstreamCheckTaskTest.java
index 84d4b7ec18..4d4017143d 100644
--- 
a/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/cache/UpstreamCheckTaskTest.java
+++ 
b/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/cache/UpstreamCheckTaskTest.java
@@ -123,4 +123,45 @@ public class UpstreamCheckTaskTest {
         healthCheckTask.triggerRemoveAll(selectorId);
         
assertFalse(healthCheckTask.getHealthyUpstream().containsKey(selectorId));
     }
+
+    /**
+     * Test that upstream with healthCheckEnabled=false is always marked as 
healthy.
+     */
+    @Test
+    @Timeout(10000)
+    public void testHealthCheckDisabled() {
+        final String selectorId = "healthCheckDisabledSelector";
+
+        // Create upstream with healthCheckEnabled = false, and set healthy to 
false
+        // manually
+        Upstream upstream = Upstream.builder()
+                .url("unreachable-url:8080")
+                .healthCheckEnabled(false)
+                .build();
+        upstream.setHealthy(false);
+
+        healthCheckTask.triggerAddOne(selectorId, upstream);
+        healthCheckTask.setPoolSize(1);
+        healthCheckTask.schedule();
+
+        // Wait for health check to complete
+        Awaitility.await().pollDelay(3500, TimeUnit.MILLISECONDS)
+                .untilAsserted(() -> 
assertFalse(healthCheckTask.getCheckStarted().get()));
+
+        // When healthCheckEnabled is false, upstream should be marked as 
healthy
+        
assertTrue(healthCheckTask.getHealthyUpstream().containsKey(selectorId));
+        
assertTrue(healthCheckTask.getHealthyUpstream().get(selectorId).get(0).isHealthy());
+    }
+
+    /**
+     * Test that healthCheckEnabled defaults to true in Upstream.
+     */
+    @Test
+    public void testHealthCheckEnabledDefaultsToTrue() {
+        Upstream upstream = Upstream.builder()
+                .url("test-url:8080")
+                .build();
+
+        assertTrue(upstream.isHealthCheckEnabled());
+    }
 }
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java
index 1c3634b983..a6196b46e4 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-divide/src/main/java/org/apache/shenyu/plugin/divide/handler/DivideUpstreamDataHandler.java
@@ -75,6 +75,7 @@ public class DivideUpstreamDataHandler implements 
DiscoveryUpstreamDataHandler {
                     .weight(u.getWeight())
                     .warmup(Integer.parseInt(properties.getProperty("warmup", 
"10")))
                     .gray(Boolean.parseBoolean(properties.getProperty("gray", 
"false")))
+                    
.healthCheckEnabled(Boolean.parseBoolean(properties.getProperty("healthCheckEnabled",
 "true")))
                     .status(0 == u.getStatus())
                     
.timestamp(Optional.ofNullable(u.getDateCreated()).map(Timestamp::getTime).orElse(System.currentTimeMillis()))
                     .build();
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/main/java/org/apache/shenyu/plugin/apache/dubbo/proxy/ApacheDubboProxyService.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/main/java/org/apache/shenyu/plugin/apache/dubbo/proxy/ApacheDubboProxyService.java
index cdbe6375c2..0634ab88f8 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/main/java/org/apache/shenyu/plugin/apache/dubbo/proxy/ApacheDubboProxyService.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/main/java/org/apache/shenyu/plugin/apache/dubbo/proxy/ApacheDubboProxyService.java
@@ -181,6 +181,7 @@ public class ApacheDubboProxyService {
                     .weight(u.getWeight())
                     .status(u.isStatus())
                     
.timestamp(Optional.of(u.getTimestamp()).orElse(System.currentTimeMillis()))
+                    .healthCheckEnabled(u.isHealthCheckEnabled())
                     .build();
         }).collect(Collectors.toList());
     }
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-dubbo-common/src/main/java/org/apache/shenyu/plugin/dubbo/common/handler/AbstractDubboPluginDataHandler.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-dubbo-common/src/main/java/org/apache/shenyu/plugin/dubbo/common/handler/AbstractDubboPluginDataHandler.java
index c247f64826..0eaae274bc 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-dubbo-common/src/main/java/org/apache/shenyu/plugin/dubbo/common/handler/AbstractDubboPluginDataHandler.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-dubbo-common/src/main/java/org/apache/shenyu/plugin/dubbo/common/handler/AbstractDubboPluginDataHandler.java
@@ -137,6 +137,7 @@ public abstract class AbstractDubboPluginDataHandler 
implements PluginDataHandle
                 .warmup(u.getWarmup())
                 .group(u.getGroup())
                 .version(u.getVersion())
+                .healthCheckEnabled(u.isHealthCheckEnabled())
                 .build()).collect(Collectors.toList());
     }
 }
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java
index 9f0b3fa6d4..d2e604e8a0 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/handler/GrpcDiscoveryUpstreamDataHandler.java
@@ -21,6 +21,7 @@ import org.apache.shenyu.common.dto.DiscoverySyncData;
 import org.apache.shenyu.common.dto.DiscoveryUpstreamData;
 import org.apache.shenyu.common.dto.convert.selector.GrpcUpstream;
 import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
 import org.apache.shenyu.common.utils.JsonUtils;
 import org.apache.shenyu.plugin.base.handler.DiscoveryUpstreamDataHandler;
 import org.apache.shenyu.plugin.grpc.cache.ApplicationConfigCache;
@@ -34,6 +35,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Properties;
 import java.util.stream.Collectors;
 
 /**
@@ -64,13 +66,17 @@ public class GrpcDiscoveryUpstreamDataHandler implements 
DiscoveryUpstreamDataHa
         if (ObjectUtils.isEmpty(upstreamList)) {
             return Collections.emptyList();
         }
-        return upstreamList.stream().map(u -> GrpcUpstream.builder()
-                .protocol(u.getProtocol())
-                .upstreamUrl(u.getUrl())
-                .weight(u.getWeight())
-                .status(0 == u.getStatus())
-                
.timestamp(Optional.ofNullable(u.getDateCreated()).map(Timestamp::getTime).orElse(System.currentTimeMillis()))
-                .build()).collect(Collectors.toList());
+        return upstreamList.stream().map(u -> {
+            Properties properties = Optional.ofNullable(u.getProps()).map(ps 
-> GsonUtils.getInstance().fromJson(ps, Properties.class)).orElse(new 
Properties());
+            return GrpcUpstream.builder()
+                    .protocol(u.getProtocol())
+                    .upstreamUrl(u.getUrl())
+                    .weight(u.getWeight())
+                    .status(0 == u.getStatus())
+                    
.timestamp(Optional.ofNullable(u.getDateCreated()).map(Timestamp::getTime).orElse(System.currentTimeMillis()))
+                    
.healthCheckEnabled(Boolean.parseBoolean(properties.getProperty("healthCheckEnabled",
 "true")))
+                    .build();
+        }).collect(Collectors.toList());
     }
 
     @Override
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/loadbalance/picker/ShenyuPicker.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/loadbalance/picker/ShenyuPicker.java
index 616ab9f3e6..b8450f109f 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/loadbalance/picker/ShenyuPicker.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/loadbalance/picker/ShenyuPicker.java
@@ -71,6 +71,7 @@ public class ShenyuPicker extends AbstractReadyPicker {
                 .weight(u.getWeight())
                 .status(u.isStatus())
                 .timestamp(u.getTimestamp())
+                .healthCheckEnabled(u.isHealthCheckEnabled())
                 .build()).collect(Collectors.toList());
     }
 }
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java
index c5a07c0ce6..923cdf67c5 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-websocket/src/main/java/org/apache/shenyu/plugin/websocket/handler/WebSocketUpstreamDataHandler.java
@@ -67,6 +67,7 @@ public class WebSocketUpstreamDataHandler implements 
DiscoveryUpstreamDataHandle
                     .url(u.getUrl())
                     .weight(u.getWeight())
                     .warmup(Integer.parseInt(properties.getProperty("warmup", 
"10")))
+                    
.healthCheckEnabled(Boolean.parseBoolean(properties.getProperty("healthCheckEnabled",
 "true")))
                     .status(0 == u.getStatus())
                     
.timestamp(Optional.ofNullable(u.getDateCreated()).map(Timestamp::getTime).orElse(System.currentTimeMillis()))
                     .build();
diff --git 
a/shenyu-web/src/main/java/org/apache/shenyu/web/controller/LocalPluginController.java
 
b/shenyu-web/src/main/java/org/apache/shenyu/web/controller/LocalPluginController.java
index 3e21d0c47f..c889ccb56b 100644
--- 
a/shenyu-web/src/main/java/org/apache/shenyu/web/controller/LocalPluginController.java
+++ 
b/shenyu-web/src/main/java/org/apache/shenyu/web/controller/LocalPluginController.java
@@ -247,6 +247,7 @@ public class LocalPluginController {
                 Properties properties = new Properties();
                 properties.setProperty("warmup", 
String.valueOf(up.getWarmup()));
                 properties.setProperty("upstreamHost", 
String.valueOf(up.getUpstreamHost()));
+                properties.setProperty("healthCheckEnabled", 
String.valueOf(up.isHealthCheckEnabled()));
                 
upstreamData.setDateUpdated(Optional.of(up.getTimestamp()).map(Timestamp::new).orElse(new
 Timestamp(System.currentTimeMillis())));
                 
upstreamData.setProps(GsonUtils.getInstance().toJson(properties));
                 
upstreamData.setDateCreated(Optional.of(up.getTimestamp()).map(Timestamp::new).orElse(new
 Timestamp(System.currentTimeMillis())));


Reply via email to