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 261efac753 fix: tolerate missing endpoints when parsing a grpc ingress
(#6490) (#7288)
261efac753 is described below
commit 261efac75314a3bfcdef97b494fb597bbb19fd19
Author: wy471x <[email protected]>
AuthorDate: Sat Sep 26 11:14:47 2026 +0800
fix: tolerate missing endpoints when parsing a grpc ingress (#6490) (#7288)
---
.../org/apache/shenyu/k8s/parser/GrpcParser.java | 8 ++
.../k8s/parser/GrpcParserMissingEndpointsTest.java | 155 +++++++++++++++++++++
2 files changed, 163 insertions(+)
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 888a10b3de..de0fb050ed 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
@@ -153,6 +153,10 @@ public class GrpcParser 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 grpc 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);
@@ -184,6 +188,10 @@ public class GrpcParser 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 grpc upstream",
serviceName);
+ return upstreamList;
+ }
List<V1EndpointSubset> subsets = v1Endpoints.getSubsets();
if (Objects.isNull(subsets) || CollectionUtils.isEmpty(subsets)) {
diff --git
a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/GrpcParserMissingEndpointsTest.java
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/GrpcParserMissingEndpointsTest.java
new file mode 100644
index 0000000000..d6f522462f
--- /dev/null
+++
b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/GrpcParserMissingEndpointsTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.CoreV1EndpointPort;
+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.V1IngressBackend;
+import io.kubernetes.client.openapi.models.V1IngressBackendBuilder;
+import io.kubernetes.client.openapi.models.V1IngressBuilder;
+import io.kubernetes.client.openapi.models.V1IngressRule;
+import io.kubernetes.client.openapi.models.V1IngressRuleBuilder;
+import io.kubernetes.client.openapi.models.V1IngressSpec;
+import io.kubernetes.client.openapi.models.V1IngressSpecBuilder;
+import io.kubernetes.client.openapi.models.V1Service;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.shenyu.k8s.common.IngressConfiguration;
+import org.apache.shenyu.k8s.common.ShenyuMemoryConfig;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.Objects;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test for parsing a grpc ingress whose backend service has no endpoints yet.
+ */
+public final class GrpcParserMissingEndpointsTest {
+
+ private static final String NAMESPACE = "grpc-missing-endpoints-ns";
+
+ private static final String SERVICE_NAME =
"grpc-missing-endpoints-service";
+
+ private static final String INGRESS_NAME =
"grpc-missing-endpoints-ingress";
+
+ private static final int SERVICE_PORT = 38080;
+
+ private Indexer<V1Endpoints> endpointsIndexer;
+
+ private GrpcParser grpcParser;
+
+ @BeforeEach
+ public void init() {
+ endpointsIndexer = mock(Indexer.class);
+ Indexer<V1Service> serviceIndexer = mock(Indexer.class);
+ grpcParser = new GrpcParser(new Lister<>(serviceIndexer), new
Lister<>(endpointsIndexer));
+ }
+
+ /**
+ * test parse the ingress default backend when the endpoints of the
service are absent.
+ */
+ @Test
+ public void testParseDefaultBackendWithoutEndpoints() {
+ V1Ingress ingress = buildIngress(backend(), null);
+
+ ShenyuMemoryConfig config = Assertions.assertDoesNotThrow(
+ () -> grpcParser.parse(ingress, mock(CoreV1Api.class)));
+
+ Pair<Pair<String, String>, IngressConfiguration> globalDefaultBackend
= config.getGlobalDefaultBackend();
+ Assertions.assertNotNull(globalDefaultBackend);
+ Assertions.assertEquals("[]",
globalDefaultBackend.getRight().getSelectorData().getHandle());
+ }
+
+ /**
+ * test parse the ingress rule backend when the endpoints of the service
are absent.
+ */
+ @Test
+ public void testParseRuleBackendWithoutEndpoints() {
+ V1Ingress ingress = buildIngress(null, rule(backend()));
+
+ ShenyuMemoryConfig config = Assertions.assertDoesNotThrow(
+ () -> grpcParser.parse(ingress, mock(CoreV1Api.class)));
+
+ Assertions.assertEquals(1, config.getRouteConfigList().size());
+ Assertions.assertEquals("[]",
config.getRouteConfigList().get(0).getSelectorData().getHandle());
+ }
+
+ /**
+ * test parse the ingress rule backend when the endpoints of the service
are present.
+ */
+ @Test
+ public void testParseRuleBackendWithEndpoints() {
+ mockEndpoints();
+ V1Ingress ingress = buildIngress(null, rule(backend()));
+
+ ShenyuMemoryConfig config = grpcParser.parse(ingress,
mock(CoreV1Api.class));
+
+ Assertions.assertEquals(1, config.getRouteConfigList().size());
+
assertThat(config.getRouteConfigList().get(0).getSelectorData().getHandle(),
+ containsString("127.0.0.1:" + SERVICE_PORT));
+ }
+
+ private void mockEndpoints() {
+ V1Endpoints endpoints = new V1EndpointsBuilder().withKind("Endpoints")
+
.withNewMetadata().withNamespace(NAMESPACE).withName(SERVICE_NAME).endMetadata()
+ .withSubsets(new V1EndpointSubsetBuilder()
+ .withAddresses(new V1EndpointAddress().ip("127.0.0.1"))
+ .withPorts(new
CoreV1EndpointPort().port(SERVICE_PORT).protocol("TCP"))
+ .build())
+ .build();
+ when(endpointsIndexer.getByKey(NAMESPACE + "/" +
SERVICE_NAME)).thenReturn(endpoints);
+ }
+
+ private V1IngressBackend backend() {
+ return new V1IngressBackendBuilder()
+
.withNewService().withName(SERVICE_NAME).withNewPort().withNumber(SERVICE_PORT).endPort().endService()
+ .build();
+ }
+
+ private V1IngressRule rule(final V1IngressBackend backend) {
+ return new V1IngressRuleBuilder().withNewHttp().withPaths(
+ new
V1HTTPIngressPathBuilder().withPath("/grpc/").withPathType("Prefix").withBackend(backend).build())
+ .endHttp().build();
+ }
+
+ private V1Ingress buildIngress(final V1IngressBackend defaultBackend,
final V1IngressRule rule) {
+ V1IngressSpec spec = new V1IngressSpecBuilder()
+ .withDefaultBackend(defaultBackend)
+ .withRules(Objects.isNull(rule) ? null :
Collections.singletonList(rule))
+ .build();
+ return new V1IngressBuilder().withNewMetadata()
+ .withName(INGRESS_NAME).withNamespace(NAMESPACE)
+
.withAnnotations(Collections.emptyMap()).withLabels(Collections.emptyMap()).endMetadata()
+ .withSpec(spec)
+ .withKind("Ingress").build();
+ }
+}