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..d43575410028 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,16 @@ private List parseIngressRule(final V1IngressRule ingressR SelectorData selectorData = createSelectorData(pathPath, conditionList, upstreamList); List ruleDataList = new ArrayList<>(); List metaDataList = new ArrayList<>(); + if (Objects.isNull(labels) || labels.isEmpty()) { + continue; + } for (String label : labels.keySet()) { - Map metadataAnnotations = serviceLister.namespace(namespace).get(labels.get(label)).getMetadata().getAnnotations(); + V1Service service = serviceLister.namespace(namespace).get(labels.get(label)); + if (Objects.isNull(service) || Objects.isNull(service.getMetadata())) { + LOG.warn("Service {} not found in namespace {}", labels.get(label), namespace); + continue; + } + Map metadataAnnotations = service.getMetadata().getAnnotations(); DubboRuleHandle ruleHandle = createDubboRuleHandle(annotations); List 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 888a10b3de0a..9f65b8d8c952 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 @@ -252,8 +252,16 @@ private List parseIngressRule(final V1IngressRule ingressR SelectorData selectorData = createSelectorData(pathPath, conditionList, grpcUpstreamList); List ruleDataList = new ArrayList<>(); List metaDataList = new ArrayList<>(); + if (Objects.isNull(labels) || labels.isEmpty()) { + continue; + } for (String label : labels.keySet()) { - Map metadataAnnotations = serviceLister.namespace(namespace).get(labels.get(label)).getMetadata().getAnnotations(); + V1Service service = serviceLister.namespace(namespace).get(labels.get(label)); + if (Objects.isNull(service) || Objects.isNull(service.getMetadata())) { + LOG.warn("Service {} not found in namespace {}", labels.get(label), namespace); + continue; + } + Map metadataAnnotations = service.getMetadata().getAnnotations(); List 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 aa7bf9c4cd6f..8f4677112c43 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,16 @@ private List parseIngressRule(final V1IngressRule ingressR SelectorData selectorData = createSelectorData(path.getPath(), conditionList); List ruleDataList = new ArrayList<>(); List metaDataList = new ArrayList<>(); + if (Objects.isNull(labels) || labels.isEmpty()) { + continue; + } for (String label : labels.keySet()) { - Map metadataAnnotations = serviceLister.namespace(namespace).get(labels.get(label)).getMetadata().getAnnotations(); + V1Service service = serviceLister.namespace(namespace).get(labels.get(label)); + if (Objects.isNull(service) || Objects.isNull(service.getMetadata())) { + LOG.warn("Service {} not found in namespace {}", labels.get(label), namespace); + continue; + } + Map metadataAnnotations = service.getMetadata().getAnnotations(); SofaRuleHandle ruleHandle = createSofaRuleHandle(annotations); List ruleConditionList = getRuleConditionList(metadataAnnotations); RuleData ruleData = createRuleData(metadataAnnotations, ruleHandle, ruleConditionList); diff --git a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/DubboIngressParserTest.java b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/DubboIngressParserTest.java new file mode 100644 index 000000000000..0618a15ca0e6 --- /dev/null +++ b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/DubboIngressParserTest.java @@ -0,0 +1,182 @@ +/* + * 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.SharedIndexInformer; +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.V1IngressRule; +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.ShenyuMemoryConfig; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test for DubboIngressParser null-safety. + */ +public class DubboIngressParserTest { + + private static final String NAMESPACE = "testNamespace"; + + private static final String SERVICE_NAME = "testService"; + + private Lister newServiceLister() { + final SharedIndexInformer informer = mock(SharedIndexInformer.class); + final Indexer indexer = mock(Indexer.class); + when(informer.getIndexer()).thenReturn(indexer); + return new Lister<>(indexer); + } + + private Lister newEndpointsLister() { + final SharedIndexInformer informer = mock(SharedIndexInformer.class); + final Indexer indexer = mock(Indexer.class); + final V1Endpoints endpoints = new V1EndpointsBuilder() + .withNewMetadata().withName(SERVICE_NAME).withNamespace(NAMESPACE).endMetadata() + .withSubsets(new V1EndpointSubsetBuilder() + .withAddresses(new V1EndpointAddress().ip("127.0.0.1")).build()) + .build(); + when(indexer.getByKey(NAMESPACE + "/" + SERVICE_NAME)).thenReturn(endpoints); + when(informer.getIndexer()).thenReturn(indexer); + return new Lister<>(indexer); + } + + @Test + public void testParseWithNullLabels() { + final Map annotations = new HashMap<>(); + annotations.put("kubernetes.io/ingress.class", "shenyu"); + annotations.put("shenyu.apache.org/plugin-dubbo-enabled", "true"); + annotations.put("shenyu.apache.org/upstreams-protocol", "dubbo://,dubbo://"); + + final V1IngressRule rule = new V1IngressRuleBuilder() + .withNewHttp() + .withPaths(new V1HTTPIngressPathBuilder() + .withPath("/dubbo/findById") + .withNewBackend() + .withNewService().withName(SERVICE_NAME).withNewPort().withNumber(20888).endPort().endService() + .endBackend().build()) + .endHttp().build(); + + final V1Ingress ingress = new V1IngressBuilder() + .withNewMetadata().withName("testIngress").withNamespace(NAMESPACE) + .withAnnotations(annotations).withLabels(null).endMetadata() + .withNewSpec().withRules(rule).endSpec() + .build(); + + final DubboIngressParser parser = new DubboIngressParser(newServiceLister(), newEndpointsLister()); + final ShenyuMemoryConfig result = parser.parse(ingress, null); + Assertions.assertNotNull(result); + } + + @Test + public void testParseWithMissingService() { + final Map annotations = new HashMap<>(); + annotations.put("kubernetes.io/ingress.class", "shenyu"); + annotations.put("shenyu.apache.org/plugin-dubbo-enabled", "true"); + annotations.put("shenyu.apache.org/upstreams-protocol", "dubbo://,dubbo://"); + + final Map labels = new HashMap<>(); + labels.put("shenyu.apache.org/metadata-labels-1", "nonExistentService"); + + final V1IngressRule rule = new V1IngressRuleBuilder() + .withNewHttp() + .withPaths(new V1HTTPIngressPathBuilder() + .withPath("/dubbo/findById") + .withNewBackend() + .withNewService().withName(SERVICE_NAME).withNewPort().withNumber(20888).endPort().endService() + .endBackend().build()) + .endHttp().build(); + + final V1Ingress ingress = new V1IngressBuilder() + .withNewMetadata().withName("testIngress").withNamespace(NAMESPACE) + .withAnnotations(annotations).withLabels(labels).endMetadata() + .withNewSpec().withRules(rule).endSpec() + .build(); + + final DubboIngressParser parser = new DubboIngressParser(newServiceLister(), newEndpointsLister()); + final ShenyuMemoryConfig result = parser.parse(ingress, null); + Assertions.assertNotNull(result); + } + + @Test + public void testParseWithValidLabelsAndService() { + final SharedIndexInformer serviceInformer = mock(SharedIndexInformer.class); + final Indexer serviceIndexer = mock(Indexer.class); + when(serviceInformer.getIndexer()).thenReturn(serviceIndexer); + + final Map annotations = new HashMap<>(); + annotations.put("kubernetes.io/ingress.class", "shenyu"); + annotations.put("shenyu.apache.org/plugin-dubbo-enabled", "true"); + annotations.put("shenyu.apache.org/upstreams-protocol", "dubbo://,dubbo://"); + + final Map labels = new HashMap<>(); + labels.put("shenyu.apache.org/metadata-labels-1", "dubboFindIdService"); + + final Map serviceAnnotations = new HashMap<>(); + serviceAnnotations.put("shenyu.apache.org/plugin-dubbo-app-name", "dubbo"); + serviceAnnotations.put("shenyu.apache.org/plugin-dubbo-path", "/findById"); + serviceAnnotations.put("shenyu.apache.org/plugin-dubbo-rpc-type", "dubbo"); + serviceAnnotations.put("shenyu.apache.org/plugin-dubbo-service-name", + "org.apache.shenyu.examples.dubbo.api.service.DubboTestService"); + serviceAnnotations.put("shenyu.apache.org/plugin-dubbo-method-name", "findById"); + serviceAnnotations.put("shenyu.apache.org/plugin-dubbo-params-type", "java.lang.String"); + + final V1Service dubboService = new V1ServiceBuilder() + .withNewMetadata().withName("dubboFindIdService").withNamespace(NAMESPACE) + .withAnnotations(serviceAnnotations).endMetadata() + .build(); + when(serviceIndexer.getByKey(NAMESPACE + "/dubboFindIdService")).thenReturn(dubboService); + + final V1IngressRule rule = new V1IngressRuleBuilder() + .withNewHttp() + .withPaths(new V1HTTPIngressPathBuilder() + .withPath("/dubbo/findById") + .withNewBackend() + .withNewService().withName(SERVICE_NAME).withNewPort().withNumber(20888).endPort().endService() + .endBackend().build()) + .endHttp().build(); + + final V1Ingress ingress = new V1IngressBuilder() + .withNewMetadata().withName("testIngress").withNamespace(NAMESPACE) + .withAnnotations(annotations).withLabels(labels).endMetadata() + .withNewSpec().withRules(rule).endSpec() + .build(); + + final Lister serviceLister = new Lister<>(serviceIndexer); + final Lister endpointsLister = newEndpointsLister(); + final DubboIngressParser parser = new DubboIngressParser(serviceLister, endpointsLister); + final ShenyuMemoryConfig result = parser.parse(ingress, null); + Assertions.assertNotNull(result); + Assertions.assertNotNull(result.getRouteConfigList()); + Assertions.assertFalse(result.getRouteConfigList().isEmpty()); + } +} diff --git a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/GrpcParserTest.java b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/GrpcParserTest.java new file mode 100644 index 000000000000..395785dfd0d7 --- /dev/null +++ b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/GrpcParserTest.java @@ -0,0 +1,175 @@ +/* + * 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.SharedIndexInformer; +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.V1IngressRule; +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.ShenyuMemoryConfig; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test for GrpcParser null-safety. + */ +public class GrpcParserTest { + + private static final String NAMESPACE = "testNamespace"; + + private static final String SERVICE_NAME = "testService"; + + private Lister newServiceLister() { + final SharedIndexInformer informer = mock(SharedIndexInformer.class); + final Indexer indexer = mock(Indexer.class); + when(informer.getIndexer()).thenReturn(indexer); + return new Lister<>(indexer); + } + + private Lister newEndpointsLister() { + final SharedIndexInformer informer = mock(SharedIndexInformer.class); + final Indexer indexer = mock(Indexer.class); + final V1Endpoints endpoints = new V1EndpointsBuilder() + .withNewMetadata().withName(SERVICE_NAME).withNamespace(NAMESPACE).endMetadata() + .withSubsets(new V1EndpointSubsetBuilder() + .withAddresses(new V1EndpointAddress().ip("127.0.0.1")).build()) + .build(); + when(indexer.getByKey(NAMESPACE + "/" + SERVICE_NAME)).thenReturn(endpoints); + when(informer.getIndexer()).thenReturn(indexer); + return new Lister<>(indexer); + } + + @Test + public void testParseWithNullLabels() { + final Map annotations = new HashMap<>(); + annotations.put("kubernetes.io/ingress.class", "shenyu"); + + final V1IngressRule rule = new V1IngressRuleBuilder() + .withNewHttp() + .withPaths(new V1HTTPIngressPathBuilder() + .withPath("/grpc/hello") + .withNewBackend() + .withNewService().withName(SERVICE_NAME).withNewPort().withNumber(50051).endPort().endService() + .endBackend().build()) + .endHttp().build(); + + final V1Ingress ingress = new V1IngressBuilder() + .withNewMetadata().withName("testIngress").withNamespace(NAMESPACE) + .withAnnotations(annotations).withLabels(null).endMetadata() + .withNewSpec().withRules(rule).endSpec() + .build(); + + final GrpcParser parser = new GrpcParser(newServiceLister(), newEndpointsLister()); + final ShenyuMemoryConfig result = parser.parse(ingress, null); + Assertions.assertNotNull(result); + } + + @Test + public void testParseWithMissingService() { + final Map annotations = new HashMap<>(); + annotations.put("kubernetes.io/ingress.class", "shenyu"); + + final Map labels = new HashMap<>(); + labels.put("shenyu.apache.org/metadata-labels-1", "nonExistentService"); + + final V1IngressRule rule = new V1IngressRuleBuilder() + .withNewHttp() + .withPaths(new V1HTTPIngressPathBuilder() + .withPath("/grpc/hello") + .withNewBackend() + .withNewService().withName(SERVICE_NAME).withNewPort().withNumber(50051).endPort().endService() + .endBackend().build()) + .endHttp().build(); + + final V1Ingress ingress = new V1IngressBuilder() + .withNewMetadata().withName("testIngress").withNamespace(NAMESPACE) + .withAnnotations(annotations).withLabels(labels).endMetadata() + .withNewSpec().withRules(rule).endSpec() + .build(); + + final GrpcParser parser = new GrpcParser(newServiceLister(), newEndpointsLister()); + final ShenyuMemoryConfig result = parser.parse(ingress, null); + Assertions.assertNotNull(result); + } + + @Test + public void testParseWithValidLabelsAndService() { + final SharedIndexInformer serviceInformer = mock(SharedIndexInformer.class); + final Indexer serviceIndexer = mock(Indexer.class); + when(serviceInformer.getIndexer()).thenReturn(serviceIndexer); + + final Map annotations = new HashMap<>(); + annotations.put("kubernetes.io/ingress.class", "shenyu"); + + final Map labels = new HashMap<>(); + labels.put("shenyu.apache.org/metadata-labels-1", "grpcHelloService"); + + final Map serviceAnnotations = new HashMap<>(); + serviceAnnotations.put("shenyu.apache.org/plugin-grpc-app-name", "grpc"); + serviceAnnotations.put("shenyu.apache.org/plugin-grpc-path", "/grpc/hello"); + serviceAnnotations.put("shenyu.apache.org/plugin-grpc-rpc-type", "grpc"); + serviceAnnotations.put("shenyu.apache.org/plugin-grpc-service-name", "hello.HelloService"); + serviceAnnotations.put("shenyu.apache.org/plugin-grpc-method-name", "hello"); + serviceAnnotations.put("shenyu.apache.org/plugin-grpc-params-type", "hello.HelloRequest"); + + final V1Service grpcService = new V1ServiceBuilder() + .withNewMetadata().withName("grpcHelloService").withNamespace(NAMESPACE) + .withAnnotations(serviceAnnotations).endMetadata() + .build(); + when(serviceIndexer.getByKey(NAMESPACE + "/grpcHelloService")).thenReturn(grpcService); + + final V1IngressRule rule = new V1IngressRuleBuilder() + .withNewHttp() + .withPaths(new V1HTTPIngressPathBuilder() + .withPath("/grpc/hello") + .withNewBackend() + .withNewService().withName(SERVICE_NAME).withNewPort().withNumber(50051).endPort().endService() + .endBackend().build()) + .endHttp().build(); + + final V1Ingress ingress = new V1IngressBuilder() + .withNewMetadata().withName("testIngress").withNamespace(NAMESPACE) + .withAnnotations(annotations).withLabels(labels).endMetadata() + .withNewSpec().withRules(rule).endSpec() + .build(); + + final Lister serviceLister = new Lister<>(serviceIndexer); + final Lister endpointsLister = newEndpointsLister(); + final GrpcParser parser = new GrpcParser(serviceLister, endpointsLister); + final ShenyuMemoryConfig result = parser.parse(ingress, null); + Assertions.assertNotNull(result); + Assertions.assertNotNull(result.getRouteConfigList()); + Assertions.assertFalse(result.getRouteConfigList().isEmpty()); + } +} diff --git a/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/SofaParserTest.java b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/SofaParserTest.java new file mode 100644 index 000000000000..339b47ec9988 --- /dev/null +++ b/shenyu-kubernetes-controller/src/test/java/org/apache/shenyu/k8s/parser/SofaParserTest.java @@ -0,0 +1,167 @@ +/* + * 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.SharedIndexInformer; +import io.kubernetes.client.informer.cache.Indexer; +import io.kubernetes.client.informer.cache.Lister; +import io.kubernetes.client.openapi.models.V1Endpoints; +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.V1IngressRule; +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.ShenyuMemoryConfig; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test for SofaParser null-safety. + */ +public class SofaParserTest { + + private static final String NAMESPACE = "testNamespace"; + + private static final String SERVICE_NAME = "testService"; + + private Lister newServiceLister() { + final SharedIndexInformer informer = mock(SharedIndexInformer.class); + final Indexer indexer = mock(Indexer.class); + when(informer.getIndexer()).thenReturn(indexer); + return new Lister<>(indexer); + } + + private Lister newEndpointsLister() { + final SharedIndexInformer informer = mock(SharedIndexInformer.class); + final Indexer indexer = mock(Indexer.class); + when(informer.getIndexer()).thenReturn(indexer); + return new Lister<>(indexer); + } + + @Test + public void testParseWithNullLabels() { + final Map annotations = new HashMap<>(); + annotations.put("kubernetes.io/ingress.class", "shenyu"); + + final V1IngressRule rule = new V1IngressRuleBuilder() + .withNewHttp() + .withPaths(new V1HTTPIngressPathBuilder() + .withPath("/sofa/findById") + .withNewBackend() + .withNewService().withName(SERVICE_NAME).withNewPort().withNumber(20888).endPort().endService() + .endBackend().build()) + .endHttp().build(); + + final V1Ingress ingress = new V1IngressBuilder() + .withNewMetadata().withName("testIngress").withNamespace(NAMESPACE) + .withAnnotations(annotations).withLabels(null).endMetadata() + .withNewSpec().withRules(rule).endSpec() + .build(); + + final SofaParser parser = new SofaParser(newServiceLister(), newEndpointsLister()); + final ShenyuMemoryConfig result = parser.parse(ingress, null); + Assertions.assertNotNull(result); + } + + @Test + public void testParseWithMissingService() { + final Map annotations = new HashMap<>(); + annotations.put("kubernetes.io/ingress.class", "shenyu"); + + final Map labels = new HashMap<>(); + labels.put("shenyu.apache.org/metadata-labels-1", "nonExistentService"); + + final V1IngressRule rule = new V1IngressRuleBuilder() + .withNewHttp() + .withPaths(new V1HTTPIngressPathBuilder() + .withPath("/sofa/findById") + .withNewBackend() + .withNewService().withName(SERVICE_NAME).withNewPort().withNumber(20888).endPort().endService() + .endBackend().build()) + .endHttp().build(); + + final V1Ingress ingress = new V1IngressBuilder() + .withNewMetadata().withName("testIngress").withNamespace(NAMESPACE) + .withAnnotations(annotations).withLabels(labels).endMetadata() + .withNewSpec().withRules(rule).endSpec() + .build(); + + final SofaParser parser = new SofaParser(newServiceLister(), newEndpointsLister()); + final ShenyuMemoryConfig result = parser.parse(ingress, null); + Assertions.assertNotNull(result); + } + + @Test + public void testParseWithValidLabelsAndService() { + final SharedIndexInformer serviceInformer = mock(SharedIndexInformer.class); + final Indexer serviceIndexer = mock(Indexer.class); + when(serviceInformer.getIndexer()).thenReturn(serviceIndexer); + + final Map annotations = new HashMap<>(); + annotations.put("kubernetes.io/ingress.class", "shenyu"); + + final Map labels = new HashMap<>(); + labels.put("shenyu.apache.org/metadata-labels-1", "sofaFindByIdService"); + + final Map serviceAnnotations = new HashMap<>(); + serviceAnnotations.put("shenyu.apache.org/plugin-sofa-app-name", "sofa"); + serviceAnnotations.put("shenyu.apache.org/plugin-sofa-path", "/sofa/findById"); + serviceAnnotations.put("shenyu.apache.org/plugin-sofa-rpc-type", "sofa"); + serviceAnnotations.put("shenyu.apache.org/plugin-sofa-service-name", + "org.apache.shenyu.examples.sofa.api.service.SofaTestService"); + serviceAnnotations.put("shenyu.apache.org/plugin-sofa-method-name", "findById"); + serviceAnnotations.put("shenyu.apache.org/plugin-sofa-params-type", "java.lang.String"); + + final V1Service sofaService = new V1ServiceBuilder() + .withNewMetadata().withName("sofaFindByIdService").withNamespace(NAMESPACE) + .withAnnotations(serviceAnnotations).endMetadata() + .build(); + when(serviceIndexer.getByKey(NAMESPACE + "/sofaFindByIdService")).thenReturn(sofaService); + + final V1IngressRule rule = new V1IngressRuleBuilder() + .withNewHttp() + .withPaths(new V1HTTPIngressPathBuilder() + .withPath("/sofa/findById") + .withNewBackend() + .withNewService().withName(SERVICE_NAME).withNewPort().withNumber(20888).endPort().endService() + .endBackend().build()) + .endHttp().build(); + + final V1Ingress ingress = new V1IngressBuilder() + .withNewMetadata().withName("testIngress").withNamespace(NAMESPACE) + .withAnnotations(annotations).withLabels(labels).endMetadata() + .withNewSpec().withRules(rule).endSpec() + .build(); + + final Lister serviceLister = new Lister<>(serviceIndexer); + final Lister endpointsLister = newEndpointsLister(); + final SofaParser parser = new SofaParser(serviceLister, endpointsLister); + final ShenyuMemoryConfig result = parser.parse(ingress, null); + Assertions.assertNotNull(result); + Assertions.assertNotNull(result.getRouteConfigList()); + Assertions.assertFalse(result.getRouteConfigList().isEmpty()); + } +}