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

yu199195 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 0df09caa4e   fix: skip maven-remote-resources-plugin in CI to avoid 
lock contention (#6459) (#6460)
0df09caa4e is described below

commit 0df09caa4e6973da5a412193109eac2b30332470
Author: lll-peanut <[email protected]>
AuthorDate: Tue Jul 28 18:28:09 2026 +0800

      fix: skip maven-remote-resources-plugin in CI to avoid lock contention 
(#6459) (#6460)
    
    * fix: skip maven-remote-resources-plugin in CI to avoid lock contention 
(#6459)
    
      The maven-remote-resources-plugin:1.5 inherited from org.apache:apache:21
      parent POM causes intermittent 'Could not acquire lock(s)' failures when
      mvnd builds multiple modules in parallel. This is a known race condition
      in the plugin's file-based locking mechanism.
    
      Changes:
      - Add skipRemoteResources property (default false) in root pom.xml
      - Explicitly configure maven-remote-resources-plugin with skip controlled
        by the property, documenting the ability to skip it
      - Pass -DskipRemoteResources=true in CI builds (clean test only, does
        not affect release packaging)
    
      The plugin only injects LICENSE/NOTICE into META-INF during packaging,
      which is not needed for the CI 'clean test' phase. Release builds are
      unaffected as the property defaults to false.
    
    * fix: upgrade lombok to 1.18.34 for JDK 21 compatibility in e2e tests
    
      Lombok 1.18.24 throws NoSuchFieldError on JDK 21 when processing
      @ToString/@AllArgsConstructor annotations, causing e2e test compilation
      failure. Upgrade to 1.18.34 which supports JDK 21.
    
      Co-Authored-By: Claude Opus 4.8 <[email protected]>
    
    * fix: resolve E2E test compilation and K8s ingress websocket upstream race 
condition
    
      - Upgrade lombok from 1.18.24 to 1.18.34 for JDK 21 compatibility
        fixes NoSuchFieldError in e2e test annotation processing
    
      - Fix race condition in IngressReconciler where EndpointsReconciler
        could fire before ServiceIngressCache was populated during initial
        informer sync, leaving websocket selector handle as []
    
      - Add null checks for v1Endpoints in WebSocketParser and
        DivideIngressParser to prevent NPE during initial informer sync
    
      - Fix double-increment bug in 
WebSocketParser.parseDefaultWebSocketService()
    
    ---------
    
    Co-authored-by: aias00 <[email protected]>
---
 .github/workflows/ci.yml                           |   4 +-
 pom.xml                                            |   8 ++
 shenyu-e2e/pom.xml                                 |   2 +-
 .../shenyu/k8s/parser/DivideIngressParser.java     |   8 ++
 .../apache/shenyu/k8s/parser/WebSocketParser.java  |  65 ++++++----
 .../shenyu/k8s/reconciler/IngressReconciler.java   | 132 +++++++++++++++++++++
 6 files changed, 193 insertions(+), 26 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 347d9e8d02..209351a4f2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -130,10 +130,10 @@ jobs:
             ./mvnw.cmd -B clean test -Prelease
           elif mvnd --version > /dev/null 2>&1; then
             echo "Using mvnd for build"
-            mvnd -B clean test -Prelease
+            mvnd -B clean test -Prelease -DskipRemoteResources=true
           else
             echo "Falling back to maven wrapper"
-            ./mvnw -B clean test -Prelease
+            ./mvnw -B clean test -Prelease -DskipRemoteResources=true
           fi
       - uses: codecov/codecov-action@v1
         with:
diff --git a/pom.xml b/pom.xml
index e07aac9ecd..8800f9faa3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -84,6 +84,7 @@
         <java.version>17</java.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <skipTests>false</skipTests>
+        <skipRemoteResources>false</skipRemoteResources>
         <!-- maven plugin version start -->
         <exec-maven-plugin.version>1.6.0</exec-maven-plugin.version>
         <jacoco-maven-plugin.version>0.8.12</jacoco-maven-plugin.version>
@@ -670,6 +671,13 @@
 
     <build>
         <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-remote-resources-plugin</artifactId>
+                <configuration>
+                    <skip>${skipRemoteResources}</skip>
+                </configuration>
+            </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-source-plugin</artifactId>
diff --git a/shenyu-e2e/pom.xml b/shenyu-e2e/pom.xml
index e103e18385..637c3aa939 100644
--- a/shenyu-e2e/pom.xml
+++ b/shenyu-e2e/pom.xml
@@ -45,7 +45,7 @@
         <assertj.version>3.27.7</assertj.version>
         <hamcrest.version>1.3</hamcrest.version>
         <jsonassert.version>1.5.0</jsonassert.version>
-        <lombok.version>1.18.24</lombok.version>
+        <lombok.version>1.18.34</lombok.version>
         <guava.version>32.0.0-jre</guava.version>
         <log4j.version>2.18.0</log4j.version>
         <slf4j.version>1.7.35</slf4j.version>
diff --git 
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DivideIngressParser.java
 
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DivideIngressParser.java
index 31e258a3c3..ac5b287eac 100644
--- 
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DivideIngressParser.java
+++ 
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DivideIngressParser.java
@@ -154,6 +154,10 @@ public class DivideIngressParser implements 
K8sResourceParser<V1Ingress> {
             String serviceName = defaultBackend.getService().getName();
             // shenyu routes directly to the container
             V1Endpoints v1Endpoints = 
endpointsLister.namespace(namespace).get(serviceName);
+            if (Objects.isNull(v1Endpoints)) {
+                LOG.info("Endpoints {} not found for divide default backend", 
serviceName);
+                return defaultUpstreamList;
+            }
             List<V1EndpointSubset> subsets = v1Endpoints.getSubsets();
             if (Objects.isNull(subsets) || CollectionUtils.isEmpty(subsets)) {
                 LOG.info("Endpoints {} do not have subsets", serviceName);
@@ -293,6 +297,10 @@ public class DivideIngressParser implements 
K8sResourceParser<V1Ingress> {
             String serviceName = backend.getService().getName();
             // shenyu routes directly to the container
             V1Endpoints v1Endpoints = 
endpointsLister.namespace(namespace).get(serviceName);
+            if (Objects.isNull(v1Endpoints)) {
+                LOG.info("Endpoints {} not found for divide upstream", 
serviceName);
+                return upstreamList;
+            }
             List<V1EndpointSubset> subsets = v1Endpoints.getSubsets();
             String[] protocol = null;
             if (Objects.nonNull(annotations) && 
annotations.containsKey(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY)) {
diff --git 
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/WebSocketParser.java
 
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/WebSocketParser.java
index 178b8f0bf0..0ec848c092 100644
--- 
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/WebSocketParser.java
+++ 
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/WebSocketParser.java
@@ -153,32 +153,47 @@ public class WebSocketParser implements 
K8sResourceParser<V1Ingress> {
             String serviceName = defaultBackend.getService().getName();
             // shenyu routes directly to the container
             V1Endpoints v1Endpoints = 
endpointsLister.namespace(namespace).get(serviceName);
+            if (Objects.isNull(v1Endpoints)) {
+                LOG.info("Endpoints {} not found for websocket default 
backend", serviceName);
+                return webSocketUpstreamList;
+            }
             List<V1EndpointSubset> subsets = v1Endpoints.getSubsets();
-            V1Service v1Service = 
serviceLister.namespace(namespace).get(serviceName);
-            Map<String, String> annotations = 
v1Service.getMetadata().getAnnotations();
-            String[] protocols = 
annotations.get(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY).split(",");
             if (Objects.isNull(subsets) || CollectionUtils.isEmpty(subsets)) {
                 LOG.info("Endpoints {} do not have subsets", serviceName);
-            } else {
-                for (V1EndpointSubset subset : subsets) {
-                    List<V1EndpointAddress> addresses = subset.getAddresses();
-                    if (Objects.isNull(addresses) || 
CollectionUtils.isEmpty(addresses)) {
-                        continue;
-                    }
-                    int i = 0;
-                    for (V1EndpointAddress address : addresses) {
-                        String upstreamIp = address.getIp();
-                        String defaultPort = 
parsePort(defaultBackend.getService());
-                        if (Objects.nonNull(defaultPort)) {
-                            WebSocketUpstream upstream = 
WebSocketUpstream.builder()
-                                    .upstreamUrl(upstreamIp + ":" + 
defaultPort)
-                                    .weight(50)
-                                    .protocol(Objects.isNull(protocols[i++]) ? 
"ws://" : protocols[i++])
-                                    .warmup(0)
-                                    .status(true)
-                                    .host("").build();
-                            webSocketUpstreamList.add(upstream);
-                        }
+                return webSocketUpstreamList;
+            }
+            V1Service v1Service = 
serviceLister.namespace(namespace).get(serviceName);
+            if (Objects.isNull(v1Service)) {
+                LOG.info("Service {} not found for websocket default backend", 
serviceName);
+                return webSocketUpstreamList;
+            }
+            Map<String, String> annotations = 
v1Service.getMetadata().getAnnotations();
+            String[] protocols = {};
+            if (Objects.nonNull(annotations)) {
+                String protocolStr = 
annotations.get(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY);
+                if (Objects.nonNull(protocolStr)) {
+                    protocols = protocolStr.split(",");
+                }
+            }
+            for (V1EndpointSubset subset : subsets) {
+                List<V1EndpointAddress> addresses = subset.getAddresses();
+                if (Objects.isNull(addresses) || 
CollectionUtils.isEmpty(addresses)) {
+                    continue;
+                }
+                for (int i = 0; i < addresses.size(); i++) {
+                    V1EndpointAddress address = addresses.get(i);
+                    String upstreamIp = address.getIp();
+                    String defaultPort = 
parsePort(defaultBackend.getService());
+                    if (Objects.nonNull(defaultPort)) {
+                        String protocol = i < protocols.length ? protocols[i] 
: null;
+                        WebSocketUpstream upstream = 
WebSocketUpstream.builder()
+                                .upstreamUrl(upstreamIp + ":" + defaultPort)
+                                .weight(50)
+                                .protocol(Objects.isNull(protocol) ? "ws://" : 
protocol)
+                                .warmup(0)
+                                .status(true)
+                                .host("").build();
+                        webSocketUpstreamList.add(upstream);
                     }
                 }
             }
@@ -293,6 +308,10 @@ public class WebSocketParser implements 
K8sResourceParser<V1Ingress> {
             String serviceName = backend.getService().getName();
             // shenyu routes directly to the container
             V1Endpoints v1Endpoints = 
endpointsLister.namespace(namespace).get(serviceName);
+            if (Objects.isNull(v1Endpoints)) {
+                LOG.info("Endpoints {} not found for websocket upstream", 
serviceName);
+                return upstreamList;
+            }
             List<V1EndpointSubset> subsets = v1Endpoints.getSubsets();
             if (Objects.isNull(subsets) || CollectionUtils.isEmpty(subsets)) {
                 LOG.info("Endpoints {} do not have subsets", serviceName);
diff --git 
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/IngressReconciler.java
 
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/IngressReconciler.java
index 8014bc71dc..c47a93ef2e 100644
--- 
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/IngressReconciler.java
+++ 
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/IngressReconciler.java
@@ -24,6 +24,7 @@ import io.kubernetes.client.informer.SharedIndexInformer;
 import io.kubernetes.client.informer.cache.Lister;
 import io.kubernetes.client.openapi.ApiClient;
 import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.CoreV1EndpointPort;
 import io.kubernetes.client.openapi.models.V1EndpointAddress;
 import io.kubernetes.client.openapi.models.V1EndpointSubset;
 import io.kubernetes.client.openapi.models.V1Endpoints;
@@ -40,9 +41,12 @@ import org.apache.shenyu.common.dto.MetaData;
 import org.apache.shenyu.common.dto.PluginData;
 import org.apache.shenyu.common.dto.RuleData;
 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.enums.PluginRoleEnum;
 import org.apache.shenyu.common.exception.ShenyuException;
+import org.apache.shenyu.common.utils.GsonUtils;
 import org.apache.shenyu.common.utils.JsonUtils;
 import org.apache.shenyu.k8s.cache.IngressCache;
 import org.apache.shenyu.k8s.cache.IngressSecretCache;
@@ -187,6 +191,11 @@ public class IngressReconciler implements Reconciler {
             LOG.info("Add service cache {} for ingress {}", pair.getLeft() + 
"/" + pair.getRight(), request.getNamespace() + "/" + request.getName());
         });
 
+        // Ensure upstream handles are populated from endpoints
+        // This handles the race condition where EndpointsReconciler may have 
already
+        // fired before ServiceIngressCache was populated during initial 
informer sync
+        updateUpstreamFromEndpoints(v1Ingress);
+
         return new Result(false);
     }
 
@@ -551,6 +560,129 @@ public class IngressReconciler implements Reconciler {
         }
     }
 
+    private void updateUpstreamFromEndpoints(final V1Ingress v1Ingress) {
+        String pluginName = getPluginName(v1Ingress);
+        if (!PluginEnum.DIVIDE.getName().equals(pluginName) && 
!PluginEnum.WEB_SOCKET.getName().equals(pluginName)) {
+            return;
+        }
+        List<Pair<String, String>> serviceList = 
parseServiceFromIngress(v1Ingress);
+        if (CollectionUtils.isEmpty(serviceList)) {
+            return;
+        }
+        String namespace = 
Objects.requireNonNull(v1Ingress.getMetadata()).getNamespace();
+        String ingressName = v1Ingress.getMetadata().getName();
+        Lister<V1Endpoints> endpointsLister = 
ingressParser.getEndpointsLister();
+        for (Pair<String, String> service : serviceList) {
+            String serviceNamespace = service.getLeft();
+            String serviceName = service.getRight();
+            V1Endpoints v1Endpoints = 
endpointsLister.namespace(serviceNamespace).get(serviceName);
+            if (Objects.isNull(v1Endpoints)) {
+                LOG.info("Cannot find endpoints for service {}/{} when 
updating upstream", serviceNamespace, serviceName);
+                continue;
+            }
+            List<Pair<V1EndpointAddress, String>> addresses = 
endpointAddresses(v1Endpoints);
+            if (CollectionUtils.isEmpty(addresses)) {
+                continue;
+            }
+            String handle;
+            if (PluginEnum.WEB_SOCKET.getName().equals(pluginName)) {
+                handle = buildWebSocketUpstreamHandle(addresses);
+            } else {
+                handle = buildDivideUpstreamHandle(addresses);
+            }
+            List<String> selectorIdList = 
IngressSelectorCache.getInstance().get(namespace, ingressName, pluginName);
+            if (CollectionUtils.isEmpty(selectorIdList)) {
+                continue;
+            }
+            List<SelectorData> totalSelectors = 
shenyuCacheRepository.findSelectorDataList(pluginName);
+            if (CollectionUtils.isEmpty(totalSelectors)) {
+                continue;
+            }
+            for (SelectorData selectorData : totalSelectors) {
+                if (selectorIdList.contains(selectorData.getId())) {
+                    SelectorData newSelectorData = SelectorData.builder()
+                            .id(selectorData.getId())
+                            .pluginId(selectorData.getPluginId())
+                            .pluginName(selectorData.getPluginName())
+                            .name(selectorData.getName())
+                            .matchMode(selectorData.getMatchMode())
+                            .type(selectorData.getType())
+                            .sort(selectorData.getSort())
+                            .enabled(selectorData.getEnabled())
+                            .logged(selectorData.getLogged())
+                            .continued(selectorData.getContinued())
+                            .handle(handle)
+                            .conditionList(selectorData.getConditionList())
+                            
.matchRestful(selectorData.getMatchRestful()).build();
+                    
shenyuCacheRepository.saveOrUpdateSelectorData(newSelectorData);
+                    LOG.info("Updated upstream handle for selector {} of 
plugin {} from endpoints {}/{}",
+                            selectorData.getId(), pluginName, 
serviceNamespace, serviceName);
+                }
+            }
+        }
+    }
+
+    private String buildWebSocketUpstreamHandle(final 
List<Pair<V1EndpointAddress, String>> addresses) {
+        List<WebSocketUpstream> res = new ArrayList<>();
+        addresses.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 String buildDivideUpstreamHandle(final 
List<Pair<V1EndpointAddress, String>> addresses) {
+        List<DivideUpstream> res = new ArrayList<>();
+        addresses.forEach(pair -> {
+            DivideUpstream upstream = new DivideUpstream();
+            upstream.setUpstreamUrl(pair.getLeft().getIp() + ":" + 
pair.getRight());
+            upstream.setWeight(100);
+            upstream.setProtocol("http://";);
+            upstream.setWarmup(0);
+            upstream.setStatus(true);
+            upstream.setUpstreamHost("");
+            res.add(upstream);
+        });
+        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) {
+                List<CoreV1EndpointPort> ports = subset.getPorts();
+                List<V1EndpointAddress> addresses = subset.getAddresses();
+                if (CollectionUtils.isEmpty(ports) || 
CollectionUtils.isEmpty(addresses)) {
+                    continue;
+                }
+                CoreV1EndpointPort endpointPort = ports.stream()
+                        .filter(coreV1EndpointPort -> 
"TCP".equals(coreV1EndpointPort.getProtocol()))
+                        .findFirst()
+                        .orElse(null);
+                if (Objects.isNull(endpointPort)) {
+                    continue;
+                }
+                String port = endpointPort.getPort() > 0
+                        ? String.valueOf(endpointPort.getPort())
+                        : endpointPort.getName();
+                if (Objects.isNull(port)) {
+                    continue;
+                }
+                for (V1EndpointAddress address : addresses) {
+                    if (Objects.nonNull(address.getIp())) {
+                        res.add(Pair.of(address, port));
+                    }
+                }
+            }
+        }
+        return res;
+    }
+
     private String getPluginName(final V1Ingress ingress) {
         String pluginName;
         String pluginDubboEnabled = 
ingress.getMetadata().getAnnotations().get(IngressConstants.PLUGIN_DUBBO_ENABLED);

Reply via email to