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);