From ce1e2c5071b406919dc2e261c1ad1f5b90e837c5 Mon Sep 17 00:00:00 2001 From: juicew <491852417@qq.com> Date: Sat, 22 Aug 2026 01:47:39 +0800 Subject: [PATCH 1/2] fix: guard ingress upstream protocol index Prevent Divide and Dubbo ingress parsers from throwing ArrayIndexOutOfBoundsException when fewer protocols than endpoints are configured. Fall back to http:// and dubbo:// respectively. --- .../k8s/parser/DivideIngressParser.java | 2 +- .../shenyu/k8s/parser/DubboIngressParser.java | 2 +- .../k8s/DivideIngressReconcilerTest.java | 20 +++++++++++++++++++ .../shenyu/k8s/DubboReconcilerTest.java | 20 +++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) 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 ac5b287eac77..b8e0c29ad679 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 @@ -322,7 +322,7 @@ private List parseUpstream(final V1IngressBackend backend, final DivideUpstream upstream = new DivideUpstream(); upstream.setUpstreamUrl(upstreamIp + ":" + defaultPort); upstream.setWeight(100); - upstream.setProtocol(Objects.isNull(protocol) ? "http://" : protocol[i++]); + upstream.setProtocol(Objects.isNull(protocol) || i >= protocol.length ? "http://" : protocol[i++]); upstream.setWarmup(0); upstream.setStatus(true); upstream.setUpstreamHost(""); diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DubboIngressParser.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DubboIngressParser.java index 7c76d9c9397a..0f29aade2007 100644 --- a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DubboIngressParser.java +++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/DubboIngressParser.java @@ -353,7 +353,7 @@ private List parseUpstream(final V1HTTPIngressPath path, final St DubboUpstream upstream = DubboUpstream.builder() .upstreamUrl(upstreamIp + ":" + defaultPort) .weight(100) - .protocol(Objects.isNull(protocols[i++]) ? "dubbo://" : protocols[i++]) + .protocol(i >= protocols.length ? "dubbo://" : protocols[i++]) .warmup(0) .status(true) .upstreamHost("") diff --git a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/DivideIngressReconcilerTest.java b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/DivideIngressReconcilerTest.java index e23e92037eb1..ce5795e766f0 100644 --- a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/DivideIngressReconcilerTest.java +++ b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/DivideIngressReconcilerTest.java @@ -34,12 +34,14 @@ import io.kubernetes.client.openapi.models.V1EndpointSubsetBuilder; import io.kubernetes.client.openapi.models.V1EndpointAddress; import org.apache.shenyu.common.config.ssl.ShenyuSniAsyncMapping; +import org.apache.shenyu.common.dto.SelectorData; import org.apache.shenyu.k8s.parser.IngressParser; import org.apache.shenyu.k8s.reconciler.IngressReconciler; import org.apache.shenyu.k8s.repository.ShenyuCacheRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import java.util.HashMap; import java.util.Map; @@ -119,4 +121,22 @@ public void testReconcile() { verify(shenyuCacheRepository).saveOrUpdateSelectorData(any()); verify(shenyuCacheRepository).saveOrUpdateRuleData(any()); } + + /** + * test reconcile with fewer protocols than endpoints. + */ + @Test + public void testReconcileWithFewerProtocolsThanEndpoints() { + V1Ingress ingress = ingressInformer.getIndexer().getByKey("mockedNamespace/mockedIngress"); + Map annotations = ingress.getMetadata().getAnnotations(); + annotations.put("shenyu.apache.org/upstreams-protocol", "https://"); + V1Endpoints endpoints = endpointsInformer.getIndexer().getByKey("mockedNamespace/testService"); + endpoints.getSubsets().get(0).setAddresses(java.util.Arrays.asList( + new V1EndpointAddress().ip("127.0.0.1"), new V1EndpointAddress().ip("127.0.0.2"))); + ingressReconciler.reconcile(new Request("mockedNamespace", "mockedIngress")); + ArgumentCaptor selectorCaptor = ArgumentCaptor.forClass(SelectorData.class); + verify(shenyuCacheRepository).saveOrUpdateSelectorData(selectorCaptor.capture()); + Assertions.assertTrue(selectorCaptor.getValue().getHandle().contains("https://")); + Assertions.assertTrue(selectorCaptor.getValue().getHandle().contains("http://")); + } } diff --git a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/DubboReconcilerTest.java b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/DubboReconcilerTest.java index a4cac6599759..66ab9c680a46 100644 --- a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/DubboReconcilerTest.java +++ b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/DubboReconcilerTest.java @@ -35,12 +35,14 @@ import io.kubernetes.client.openapi.models.V1Service; import io.kubernetes.client.openapi.models.V1ServiceBuilder; import org.apache.shenyu.common.config.ssl.ShenyuSniAsyncMapping; +import org.apache.shenyu.common.dto.SelectorData; import org.apache.shenyu.k8s.parser.IngressParser; import org.apache.shenyu.k8s.reconciler.IngressReconciler; import org.apache.shenyu.k8s.repository.ShenyuCacheRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import java.util.HashMap; import java.util.Map; @@ -150,4 +152,22 @@ public void testReconcile() { verify(shenyuCacheRepository).saveOrUpdateRuleData(any()); verify(shenyuCacheRepository).saveOrUpdateMetaData(any()); } + + /** + * test reconcile with fewer protocols than endpoints. + */ + @Test + public void testReconcileWithFewerProtocolsThanEndpoints() { + V1Ingress ingress = ingressInformer.getIndexer().getByKey("mockedNamespace/mockedIngress"); + Map annotations = ingress.getMetadata().getAnnotations(); + annotations.put("shenyu.apache.org/upstreams-protocol", "dubbo+ssl://"); + V1Endpoints endpoints = endpointsInformer.getIndexer().getByKey("mockedNamespace/testService"); + endpoints.getSubsets().get(0).setAddresses(java.util.Arrays.asList( + new V1EndpointAddress().ip("127.0.0.1"), new V1EndpointAddress().ip("127.0.0.2"))); + ingressReconciler.reconcile(new Request("mockedNamespace", "mockedIngress")); + ArgumentCaptor selectorCaptor = ArgumentCaptor.forClass(SelectorData.class); + verify(shenyuCacheRepository).saveOrUpdateSelectorData(selectorCaptor.capture()); + Assertions.assertTrue(selectorCaptor.getValue().getHandle().contains("dubbo+ssl://")); + Assertions.assertTrue(selectorCaptor.getValue().getHandle().contains("dubbo://")); + } } From 61f58f88f7401ab61b958c3a90a516e76856ebfc Mon Sep 17 00:00:00 2001 From: juicew <491852417@qq.com> Date: Sat, 5 Sep 2026 17:16:05 +0800 Subject: [PATCH 2/2] test: isolate ingress reconciler cache between test cases Clear singleton ingress-related caches before each test to prevent selectors from previous test cases causing duplicate deletion invocations. --- .../apache/shenyu/k8s/DivideIngressReconcilerTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/DivideIngressReconcilerTest.java b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/DivideIngressReconcilerTest.java index 00821eba7686..185ad79436bf 100644 --- a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/DivideIngressReconcilerTest.java +++ b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/DivideIngressReconcilerTest.java @@ -79,6 +79,11 @@ public final class DivideIngressReconcilerTest { @BeforeEach public void init() { + // These caches are process-wide singletons; isolate each test case. + IngressCache.getInstance().remove("mockedNamespace", "mockedIngress"); + IngressSelectorCache.getInstance().remove("mockedNamespace", "mockedIngress", PluginEnum.DIVIDE.getName()); + ServiceIngressCache.getInstance().removeSpecifiedIngressName( + "mockedNamespace", "testService", "mockedNamespace", "mockedIngress"); ingressInformer = mock(SharedIndexInformer.class); secretInformer = mock(SharedIndexInformer.class); shenyuCacheRepository = mock(ShenyuCacheRepository.class); @@ -145,7 +150,8 @@ public void testReconcileWithFewerProtocolsThanEndpoints() { verify(shenyuCacheRepository).saveOrUpdateSelectorData(selectorCaptor.capture()); Assertions.assertTrue(selectorCaptor.getValue().getHandle().contains("https://")); Assertions.assertTrue(selectorCaptor.getValue().getHandle().contains("http://")); - } + } + /** * Test reconcile after ingress deletion. */