This is an automated email from the ASF dual-hosted git repository.
Aias00 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 069f0d9157 fix: prevent NPE in gRPC, Dubbo and Sofa ingress parsers
(#6861) (#7057)
069f0d9157 is described below
commit 069f0d915731452f2227dfe69d929c8f3c61dc84
Author: Southern <[email protected]>
AuthorDate: Sat Sep 26 16:35:26 2026 +0800
fix: prevent NPE in gRPC, Dubbo and Sofa ingress parsers (#6861) (#7057)
Guard against null ingress labels before iterating over them, and skip
labels
that reference a missing Kubernetes Service. Add unit tests covering
existing
Services, missing Services, and ingress resources without labels.
Co-authored-by: aias00 <[email protected]>
---
.../shenyu/k8s/parser/DubboIngressParser.java | 9 +-
.../org/apache/shenyu/k8s/parser/GrpcParser.java | 9 +-
.../org/apache/shenyu/k8s/parser/SofaParser.java | 9 +-
.../k8s/parser/RpcIngressParserNullSafetyTest.java | 129 +++++++++++++++++++++
4 files changed, 153 insertions(+), 3 deletions(-)
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 72425a2b93..2dacf6de4b 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
@@ -238,8 +238,15 @@ public class DubboIngressParser implements
K8sResourceParser<V1Ingress> {
SelectorData selectorData = createSelectorData(pathPath,
conditionList, upstreamList);
List<RuleData> ruleDataList = new ArrayList<>();
List<MetaData> metaDataList = new ArrayList<>();
+ if (Objects.isNull(labels)) {
+ return res;
+ }
for (String label : labels.keySet()) {
- Map<String, String> metadataAnnotations =
serviceLister.namespace(namespace).get(labels.get(label)).getMetadata().getAnnotations();
+ V1Service service =
serviceLister.namespace(namespace).get(labels.get(label));
+ if (Objects.isNull(service)) {
+ continue;
+ }
+ Map<String, String> metadataAnnotations =
service.getMetadata().getAnnotations();
DubboRuleHandle ruleHandle =
createDubboRuleHandle(annotations);
List<ConditionData> ruleConditionList =
getRuleConditionList(metadataAnnotations);
RuleData ruleData =
createRuleData(metadataAnnotations, ruleHandle, ruleConditionList);
diff --git
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/GrpcParser.java
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/GrpcParser.java
index de0fb050ed..645b52af1c 100644
---
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/GrpcParser.java
+++
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/GrpcParser.java
@@ -260,8 +260,15 @@ public class GrpcParser implements
K8sResourceParser<V1Ingress> {
SelectorData selectorData = createSelectorData(pathPath,
conditionList, grpcUpstreamList);
List<RuleData> ruleDataList = new ArrayList<>();
List<MetaData> metaDataList = new ArrayList<>();
+ if (Objects.isNull(labels)) {
+ return res;
+ }
for (String label : labels.keySet()) {
- Map<String, String> metadataAnnotations =
serviceLister.namespace(namespace).get(labels.get(label)).getMetadata().getAnnotations();
+ V1Service service =
serviceLister.namespace(namespace).get(labels.get(label));
+ if (Objects.isNull(service)) {
+ continue;
+ }
+ Map<String, String> metadataAnnotations =
service.getMetadata().getAnnotations();
List<ConditionData> ruleConditionList =
getRuleConditionList(metadataAnnotations);
RuleData ruleData =
createRuleData(metadataAnnotations, ruleConditionList, annotations);
MetaData metaData = parseMetaData(metadataAnnotations);
diff --git
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/SofaParser.java
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/SofaParser.java
index aa7bf9c4cd..03a448daf3 100644
---
a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/SofaParser.java
+++
b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/SofaParser.java
@@ -169,8 +169,15 @@ public class SofaParser implements
K8sResourceParser<V1Ingress> {
SelectorData selectorData =
createSelectorData(path.getPath(), conditionList);
List<RuleData> ruleDataList = new ArrayList<>();
List<MetaData> metaDataList = new ArrayList<>();
+ if (Objects.isNull(labels)) {
+ return res;
+ }
for (String label : labels.keySet()) {
- Map<String, String> metadataAnnotations =
serviceLister.namespace(namespace).get(labels.get(label)).getMetadata().getAnnotations();
+ V1Service service =
serviceLister.namespace(namespace).get(labels.get(label));
+ if (Objects.isNull(service)) {
+ continue;
+ }
+ Map<String, String> metadataAnnotations =
service.getMetadata().getAnnotations();
SofaRuleHandle ruleHandle =
createSofaRuleHandle(annotations);
List<ConditionData> ruleConditionList =
getRuleConditionList(metadataAnnotations);
RuleData ruleData =
createRuleData(metadataAnnotations, ruleHandle, ruleConditionList);
diff --git
a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/RpcIngressParserNullSafetyTest.java
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/RpcIngressParserNullSafetyTest.java
new file mode 100644
index 0000000000..e361c4656f
--- /dev/null
+++
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/RpcIngressParserNullSafetyTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.parser;
+
+import io.kubernetes.client.informer.cache.Indexer;
+import io.kubernetes.client.informer.cache.Lister;
+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.V1HTTPIngressPathBuilder;
+import io.kubernetes.client.openapi.models.V1Ingress;
+import io.kubernetes.client.openapi.models.V1IngressBuilder;
+import io.kubernetes.client.openapi.models.V1IngressRuleBuilder;
+import io.kubernetes.client.openapi.models.V1Service;
+import io.kubernetes.client.openapi.models.V1ServiceBuilder;
+import org.apache.shenyu.k8s.common.IngressConstants;
+import org.apache.shenyu.k8s.common.ShenyuMemoryConfig;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Null safety tests for RPC ingress parsers.
+ */
+public final class RpcIngressParserNullSafetyTest {
+
+ private static final String NAMESPACE = "test-namespace";
+
+ private static final String BACKEND_SERVICE = "backend-service";
+
+ @Test
+ public void shouldParseIngressWithExistingService() {
+ Lister<V1Service> serviceLister = createServiceLister(new
V1ServiceBuilder().withNewMetadata().withName(BACKEND_SERVICE)
+
.withNamespace(NAMESPACE).withAnnotations(Collections.emptyMap()).endMetadata().build());
+ List<K8sResourceParser<V1Ingress>> parsers =
createParsers(serviceLister);
+
+ for (K8sResourceParser<V1Ingress> parser : parsers) {
+ ShenyuMemoryConfig config = Assertions.assertDoesNotThrow(() ->
parser.parse(createIngress(Collections.singletonMap(
+ "service-label", BACKEND_SERVICE)), null));
+ Assertions.assertEquals(1, config.getRouteConfigList().size());
+ Assertions.assertEquals(1,
config.getRouteConfigList().get(0).getRuleDataList().size());
+ }
+ }
+
+ @Test
+ public void shouldSkipMissingServiceReferencedByLabel() {
+ Lister<V1Service> serviceLister = createServiceLister(null);
+ List<K8sResourceParser<V1Ingress>> parsers =
createParsers(serviceLister);
+
+ for (K8sResourceParser<V1Ingress> parser : parsers) {
+ ShenyuMemoryConfig config = Assertions.assertDoesNotThrow(() ->
parser.parse(createIngress(Collections.singletonMap(
+ "missing-service-label", "missing-service")), null));
+ Assertions.assertEquals(1, config.getRouteConfigList().size());
+
Assertions.assertTrue(config.getRouteConfigList().get(0).getRuleDataList().isEmpty());
+
Assertions.assertTrue(config.getRouteConfigList().get(0).getMetaDataList().isEmpty());
+ }
+ }
+
+ @Test
+ public void shouldIgnoreNullIngressLabels() {
+ Lister<V1Service> serviceLister = createServiceLister(null);
+ List<K8sResourceParser<V1Ingress>> parsers =
createParsers(serviceLister);
+
+ for (K8sResourceParser<V1Ingress> parser : parsers) {
+ ShenyuMemoryConfig config = Assertions.assertDoesNotThrow(() ->
parser.parse(createIngress(null), null));
+ Assertions.assertNotNull(config.getRouteConfigList());
+ Assertions.assertTrue(config.getRouteConfigList().isEmpty());
+ }
+ }
+
+ private List<K8sResourceParser<V1Ingress>> createParsers(final
Lister<V1Service> serviceLister) {
+ Lister<V1Endpoints> endpointsLister = createEndpointsLister();
+ return Arrays.asList(new GrpcParser(serviceLister, endpointsLister),
+ new DubboIngressParser(serviceLister, endpointsLister), new
SofaParser(serviceLister, endpointsLister));
+ }
+
+ private Lister<V1Service> createServiceLister(final V1Service service) {
+ Indexer<V1Service> indexer = mock(Indexer.class);
+ when(indexer.getByKey(NAMESPACE + "/" +
BACKEND_SERVICE)).thenReturn(service);
+ return new Lister<>(indexer);
+ }
+
+ private Lister<V1Endpoints> createEndpointsLister() {
+ Indexer<V1Endpoints> indexer = mock(Indexer.class);
+ V1Endpoints endpoints = new
V1EndpointsBuilder().withNewMetadata().withName(BACKEND_SERVICE).withNamespace(NAMESPACE)
+ .endMetadata().withSubsets(new V1EndpointSubsetBuilder()
+ .withAddresses(new
V1EndpointAddress().ip("127.0.0.1")).build()).build();
+ when(indexer.getByKey(NAMESPACE + "/" +
BACKEND_SERVICE)).thenReturn(endpoints);
+ return new Lister<>(indexer);
+ }
+
+ private V1Ingress createIngress(final Map<String, String> labels) {
+ Map<String, String> annotations = new HashMap<>();
+ annotations.put(IngressConstants.UPSTREAMS_PROTOCOL_ANNOTATION_KEY,
"dubbo://,dubbo://");
+ V1Ingress ingress = new
V1IngressBuilder().withNewMetadata().withName("test-ingress").withNamespace(NAMESPACE)
+ .withAnnotations(annotations).endMetadata()
+ .withNewSpec().withRules(new
V1IngressRuleBuilder().withNewHttp().withPaths(
+ new
V1HTTPIngressPathBuilder().withPath("/test").withPathType("Prefix").withNewBackend()
+
.withNewService().withName(BACKEND_SERVICE).withNewPort().withNumber(8080)
+
.endPort().endService().endBackend().build()).endHttp().build()).endSpec()
+ .build();
+ ingress.getMetadata().setLabels(labels);
+ return ingress;
+ }
+}