From 4fa4d4e5f66571bff88b266835359451376006ac Mon Sep 17 00:00:00 2001 From: Hemanth1205 Date: Sat, 14 Mar 2026 23:11:09 +0530 Subject: [PATCH 01/10] fix: update publishService/statusAdress logic in gateway an ingress controller files --- internal/controller/gateway_controller.go | 6 + internal/controller/ingress_controller.go | 31 ++- test/e2e/gatewayapi/gateway.go | 143 ++++++++++++ test/e2e/ingress/ingress.go | 256 ++++++++++++++++++++++ 4 files changed, 433 insertions(+), 3 deletions(-) diff --git a/internal/controller/gateway_controller.go b/internal/controller/gateway_controller.go index 147bdd1723..906517bd4d 100644 --- a/internal/controller/gateway_controller.go +++ b/internal/controller/gateway_controller.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "net" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -183,8 +184,13 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct if addr == "" { continue } + addrType := gatewayv1.IPAddressType + if net.ParseIP(addr) == nil { + addrType = gatewayv1.HostnameAddressType + } addrs = append(addrs, gatewayv1.GatewayStatusAddress{ + Type: &addrType, Value: addr, }, ) diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index f8447ee6c0..a4d8311705 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -20,6 +20,7 @@ package controller import ( "context" "fmt" + "net" "reflect" "github.com/go-logr/logr" @@ -694,9 +695,13 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.Tra if addr == "" { continue } - loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{ - IP: addr, - }) + ingress := networkingv1.IngressLoadBalancerIngress{} + if net.ParseIP(addr) != nil { + ingress.IP = addr + } else { + ingress.Hostname = addr + } + loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, ingress) } } else { // 2. if the IngressStatusAddress is not configured, try to use the PublishService @@ -731,6 +736,26 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.Tra }) } } + } else if svc.Spec.Type == corev1.ServiceTypeClusterIP { + // for ClusterIP services, find Ingresses that reference this service + // and collect hostnames from their load balancer status + // this is when we run the apisix in ClusterIP mode and enable Ingress + // when deploying in Cloud environments. + ingressList := &networkingv1.IngressList{} + if err := r.List(ctx, ingressList, client.MatchingFields{ + indexer.ServiceIndexRef: indexer.GenIndexKey(namespace, name), + }); err != nil { + return fmt.Errorf("failed to list ingresses for ClusterIP service %s/%s: %w", namespace, name, err) + } + for _, ing := range ingressList.Items { + for _, lb := range ing.Status.LoadBalancer.Ingress { + if lb.Hostname != "" { + loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{ + Hostname: lb.Hostname, + }) + } + } + } } } } diff --git a/test/e2e/gatewayapi/gateway.go b/test/e2e/gatewayapi/gateway.go index 8c5557efee..cb8d2959ae 100644 --- a/test/e2e/gatewayapi/gateway.go +++ b/test/e2e/gatewayapi/gateway.go @@ -539,4 +539,147 @@ spec: Expect(string(getListener.SupportedKinds[0].Kind)).To(Equal("UDPRoute"), "udp listener supported kind content") }) }) + + Context("Gateway Status Address", func() { + var gatewayProxyWithStatusAddressYaml = ` +apiVersion: apisix.apache.org/v1alpha1 +kind: GatewayProxy +metadata: + name: apisix-proxy-config + namespace: %s +spec: + statusAddress: + - %s + provider: + type: ControlPlane + controlPlane: + endpoints: + - %s + auth: + type: AdminKey + adminKey: + value: "%s" +` + var defaultGatewayClass = ` +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: %s +spec: + controllerName: "%s" +` + var defaultGateway = ` +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: %s +spec: + gatewayClassName: %s + listeners: + - name: http + protocol: HTTP + port: 80 + infrastructure: + parametersRef: + group: apisix.apache.org + kind: GatewayProxy + name: apisix-proxy-config +` + getGatewayAddresses := func(gatewayName string) ([]gatewayv1.GatewayStatusAddress, error) { + var gateway gatewayv1.Gateway + if err := s.GetKubeClient().Get(context.Background(), k8stypes.NamespacedName{ + Name: gatewayName, + Namespace: s.Namespace(), + }, &gateway); err != nil { + return nil, err + } + return gateway.Status.Addresses, nil + } + + It("sets IPAddress type when statusAddress is an IP", func() { + ipAddr := "192.168.1.100" + gatewayClassName := s.Namespace() + + By("create GatewayProxy with IP statusAddress") + gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, + s.Namespace(), ipAddr, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + Expect(s.CreateResourceFromString(gatewayProxy)).NotTo(HaveOccurred(), "creating GatewayProxy") + + By("create GatewayClass") + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(defaultGatewayClass, gatewayClassName, s.GetControllerName()), ""), + ).NotTo(HaveOccurred(), "creating GatewayClass") + + By("create Gateway") + gatewayName := s.Namespace() + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(defaultGateway, gatewayName, gatewayClassName), s.Namespace()), + ).NotTo(HaveOccurred(), "creating Gateway") + + By("check Gateway status addresses has IPAddress type") + s.RetryAssertion(func() error { + addrs, err := getGatewayAddresses(gatewayName) + if err != nil { + return err + } + if len(addrs) == 0 { + return fmt.Errorf("expected at least 1 status address, got 0") + } + addr := addrs[0] + if addr.Value != ipAddr { + return fmt.Errorf("expected address value %s, got %s", ipAddr, addr.Value) + } + if addr.Type == nil { + return fmt.Errorf("expected address type to be set, got nil") + } + if *addr.Type != gatewayv1.IPAddressType { + return fmt.Errorf("expected address type %s, got %s", gatewayv1.IPAddressType, *addr.Type) + } + return nil + }).ShouldNot(HaveOccurred(), "check Gateway IPAddress status address type") + }) + + It("sets Hostname type when statusAddress is a hostname", func() { + hostname := "mygateway.example.com" + gatewayClassName := s.Namespace() + + By("create GatewayProxy with hostname statusAddress") + gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, + s.Namespace(), hostname, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + Expect(s.CreateResourceFromString(gatewayProxy)).NotTo(HaveOccurred(), "creating GatewayProxy") + + By("create GatewayClass") + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(defaultGatewayClass, gatewayClassName, s.GetControllerName()), ""), + ).NotTo(HaveOccurred(), "creating GatewayClass") + + By("create Gateway") + gatewayName := s.Namespace() + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(defaultGateway, gatewayName, gatewayClassName), s.Namespace()), + ).NotTo(HaveOccurred(), "creating Gateway") + + By("check Gateway status addresses has Hostname type") + s.RetryAssertion(func() error { + addrs, err := getGatewayAddresses(gatewayName) + if err != nil { + return err + } + if len(addrs) == 0 { + return fmt.Errorf("expected at least 1 status address, got 0") + } + addr := addrs[0] + if addr.Value != hostname { + return fmt.Errorf("expected address value %s, got %s", hostname, addr.Value) + } + if addr.Type == nil { + return fmt.Errorf("expected address type to be set, got nil") + } + if *addr.Type != gatewayv1.HostnameAddressType { + return fmt.Errorf("expected address type %s, got %s", gatewayv1.HostnameAddressType, *addr.Type) + } + return nil + }).ShouldNot(HaveOccurred(), "check Gateway Hostname status address type") + }) + }) }) diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go index 6329b681f9..6383b38037 100644 --- a/test/e2e/ingress/ingress.go +++ b/test/e2e/ingress/ingress.go @@ -30,6 +30,8 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stretchr/testify/assert" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" @@ -1294,4 +1296,258 @@ spec: Should(Equal(http.StatusOK)) }) }) + + Context("Ingress Status Address", func() { + var gatewayProxyWithStatusAddressYaml = ` +apiVersion: apisix.apache.org/v1alpha1 +kind: GatewayProxy +metadata: + name: apisix-proxy-config + namespace: %s +spec: + statusAddress: + - %s + provider: + type: ControlPlane + controlPlane: + endpoints: + - %s + auth: + type: AdminKey + adminKey: + value: "%s" +` + var gatewayProxyWithPublishServiceYaml = ` +apiVersion: apisix.apache.org/v1alpha1 +kind: GatewayProxy +metadata: + name: apisix-proxy-config + namespace: %s +spec: + publishService: %s/%s + provider: + type: ControlPlane + controlPlane: + endpoints: + - %s + auth: + type: AdminKey + adminKey: + value: "%s" +` + var ingressClassYaml = ` +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: %s +spec: + controller: "%s" + parameters: + apiGroup: "apisix.apache.org" + kind: "GatewayProxy" + name: "apisix-proxy-config" + namespace: "%s" + scope: "Namespace" +` + var ingressYaml = ` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: %s +spec: + ingressClassName: %s + rules: + - host: status.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: %s + port: + number: 80 +` + getIngressLBStatus := func(ingressName string) ([]networkingv1.IngressLoadBalancerIngress, error) { + ing := &networkingv1.Ingress{} + if err := s.K8sClient.Get(context.Background(), types.NamespacedName{ + Name: ingressName, + Namespace: s.Namespace(), + }, ing); err != nil { + return nil, err + } + return ing.Status.LoadBalancer.Ingress, nil + } + + It("sets IP field when statusAddress is an IP", func() { + ipAddr := "192.168.1.200" + ingressClassName := s.Namespace() + + By("create GatewayProxy with IP statusAddress") + gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, + s.Namespace(), ipAddr, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + Expect(s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())).NotTo(HaveOccurred(), "creating GatewayProxy") + + By("create IngressClass") + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(ingressClassYaml, ingressClassName, s.GetControllerName(), s.Namespace()), ""), + ).NotTo(HaveOccurred(), "creating IngressClass") + + By("create Ingress") + ingressName := s.Namespace() + "-ip" + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(ingressYaml, ingressName, ingressClassName, "httpbin-service-e2e-test"), s.Namespace()), + ).NotTo(HaveOccurred(), "creating Ingress") + + By("check Ingress status has IP set and Hostname empty") + s.RetryAssertion(func() error { + lbs, err := getIngressLBStatus(ingressName) + if err != nil { + return err + } + if len(lbs) == 0 { + return fmt.Errorf("expected at least 1 load balancer ingress, got 0") + } + if lbs[0].IP != ipAddr { + return fmt.Errorf("expected IP %s, got %s", ipAddr, lbs[0].IP) + } + if lbs[0].Hostname != "" { + return fmt.Errorf("expected Hostname to be empty, got %s", lbs[0].Hostname) + } + return nil + }).ShouldNot(HaveOccurred(), "check Ingress IP load balancer status") + }) + + It("sets Hostname field when statusAddress is a hostname", func() { + hostname := "myingress.example.com" + ingressClassName := s.Namespace() + + By("create GatewayProxy with hostname statusAddress") + gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, + s.Namespace(), hostname, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + Expect(s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())).NotTo(HaveOccurred(), "creating GatewayProxy") + + By("create IngressClass") + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(ingressClassYaml, ingressClassName, s.GetControllerName(), s.Namespace()), ""), + ).NotTo(HaveOccurred(), "creating IngressClass") + + By("create Ingress") + ingressName := s.Namespace() + "-host" + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(ingressYaml, ingressName, ingressClassName, "httpbin-service-e2e-test"), s.Namespace()), + ).NotTo(HaveOccurred(), "creating Ingress") + + By("check Ingress status has Hostname set and IP empty") + s.RetryAssertion(func() error { + lbs, err := getIngressLBStatus(ingressName) + if err != nil { + return err + } + if len(lbs) == 0 { + return fmt.Errorf("expected at least 1 load balancer ingress, got 0") + } + if lbs[0].Hostname != hostname { + return fmt.Errorf("expected Hostname %s, got %s", hostname, lbs[0].Hostname) + } + if lbs[0].IP != "" { + return fmt.Errorf("expected IP to be empty, got %s", lbs[0].IP) + } + return nil + }).ShouldNot(HaveOccurred(), "check Ingress Hostname load balancer status") + }) + + It("propagates hostname from referencing Ingress when publishService is a ClusterIP", func() { + clusterIPSvcName := "apisix-clusterip-svc" + expectedHostname := "clusterip.example.com" + ingressClassName := s.Namespace() + + By("create ClusterIP service") + clusterIPSvc := fmt.Sprintf(` +apiVersion: v1 +kind: Service +metadata: + name: %s + namespace: %s +spec: + type: ClusterIP + selector: + app: httpbin + ports: + - port: 80 + targetPort: 80 +`, clusterIPSvcName, s.Namespace()) + Expect(s.CreateResourceFromStringWithNamespace(clusterIPSvc, s.Namespace())).NotTo(HaveOccurred(), "creating ClusterIP service") + + By("create source Ingress referencing the ClusterIP service") + sourceIngressName := s.Namespace() + "-source" + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(ingressYaml, sourceIngressName, ingressClassName, clusterIPSvcName), s.Namespace()), + ).NotTo(HaveOccurred(), "creating source Ingress") + + By("patch source Ingress status with hostname") + sourceIng := &networkingv1.Ingress{} + Expect(s.K8sClient.Get(context.Background(), types.NamespacedName{ + Name: sourceIngressName, Namespace: s.Namespace(), + }, sourceIng)).NotTo(HaveOccurred(), "getting source Ingress") + sourceIng.Status.LoadBalancer.Ingress = []networkingv1.IngressLoadBalancerIngress{ + {Hostname: expectedHostname}, + } + Expect(s.K8sClient.Status().Update(context.Background(), sourceIng)).NotTo(HaveOccurred(), "patching source Ingress status") + + By("create GatewayProxy with ClusterIP publishService") + gatewayProxy := fmt.Sprintf(gatewayProxyWithPublishServiceYaml, + s.Namespace(), s.Namespace(), clusterIPSvcName, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + Expect(s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())).NotTo(HaveOccurred(), "creating GatewayProxy") + + By("create IngressClass") + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(ingressClassYaml, ingressClassName, s.GetControllerName(), s.Namespace()), ""), + ).NotTo(HaveOccurred(), "creating IngressClass") + + By("create main Ingress") + mainIngressName := s.Namespace() + "-main" + mainIngress := fmt.Sprintf(` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: %s +spec: + ingressClassName: %s + rules: + - host: status.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: httpbin-service-e2e-test + port: + number: 80 +`, mainIngressName, ingressClassName) + Expect(s.CreateResourceFromStringWithNamespace(mainIngress, s.Namespace())).NotTo(HaveOccurred(), "creating main Ingress") + + By("check main Ingress status propagates hostname from source Ingress") + s.RetryAssertion(func() error { + lbs, err := getIngressLBStatus(mainIngressName) + if err != nil { + return err + } + if len(lbs) == 0 { + return fmt.Errorf("expected at least 1 load balancer ingress, got 0") + } + if lbs[0].Hostname != expectedHostname { + return fmt.Errorf("expected Hostname %s, got %s", expectedHostname, lbs[0].Hostname) + } + return nil + }).ShouldNot(HaveOccurred(), "check main Ingress load balancer status from ClusterIP") + + // cleanup: avoid leaking the cluster-scoped IngressClass + DeferCleanup(func() { + ic := &networkingv1.IngressClass{ObjectMeta: metav1.ObjectMeta{Name: ingressClassName}} + _ = s.K8sClient.Delete(context.Background(), ic) + }) + }) + }) }) From d01de6cb360155cc2084a9b3c159ba1ec099e88d Mon Sep 17 00:00:00 2001 From: Hemanth1205 Date: Sat, 14 Mar 2026 23:22:45 +0530 Subject: [PATCH 02/10] fix: update example.md to reflect new usage of publishService/statusAddress --- docs/en/latest/reference/example.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/en/latest/reference/example.md b/docs/en/latest/reference/example.md index 8c219f8c3b..a9d7a9aeb8 100644 --- a/docs/en/latest/reference/example.md +++ b/docs/en/latest/reference/example.md @@ -1092,6 +1092,8 @@ spec: - 10.24.87.13 ``` +Each entry in `statusAddress` can be an IP address or a hostname. The controller automatically sets the address type on the Gateway status — `IPAddress` for valid IPs and `Hostname` for everything else. + @@ -1116,6 +1118,8 @@ spec: - 10.24.87.13 ``` +Each entry in `statusAddress` can be an IP address or a hostname. The controller automatically sets the `IP` field for valid IPs and the `Hostname` field for everything else in the Ingress load balancer status. + To configure the `publishService`: ```yaml @@ -1133,7 +1137,10 @@ spec: publishService: apisix-gateway ``` -When using `publishService`, make sure your gateway Service is of `LoadBalancer` type the address can be populated. The controller will use the endpoint of this Service to update the status information of the Ingress resource. The format can be either `namespace/svc-name` or simply `svc-name` if the default namespace is correctly set. +When using `publishService`, the controller will use the endpoint of this Service to update the status information of the Ingress resource. The format can be either `namespace/svc-name` or simply `svc-name` if the default namespace is correctly set. + +- If the Service is of `LoadBalancer` type, the controller uses its external IP or hostname. +- If the Service is of `ClusterIP` type, the controller propagates the hostname from any Ingress resources that reference that Service. From 28849b1bcd336f5f6cd440310978fe25db85d0d5 Mon Sep 17 00:00:00 2001 From: Hemanth1205 Date: Mon, 16 Mar 2026 20:59:08 +0530 Subject: [PATCH 03/10] fix: resolve merge comments and lint issues --- internal/controller/ingress_controller.go | 12 ++++- test/e2e/gatewayapi/gateway.go | 64 +++++------------------ test/e2e/ingress/ingress.go | 62 ++++++---------------- 3 files changed, 39 insertions(+), 99 deletions(-) diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index a4d8311705..8babfc41c1 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -722,7 +722,8 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.Tra return fmt.Errorf("failed to get publish service %s: %w", publishService, err) } - if svc.Spec.Type == corev1.ServiceTypeLoadBalancer { + switch svc.Spec.Type { + case corev1.ServiceTypeLoadBalancer: // get the LoadBalancer IP and Hostname of the service for _, ip := range svc.Status.LoadBalancer.Ingress { if ip.IP != "" { @@ -736,7 +737,7 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.Tra }) } } - } else if svc.Spec.Type == corev1.ServiceTypeClusterIP { + case corev1.ServiceTypeClusterIP: // for ClusterIP services, find Ingresses that reference this service // and collect hostnames from their load balancer status // this is when we run the apisix in ClusterIP mode and enable Ingress @@ -748,6 +749,13 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.Tra return fmt.Errorf("failed to list ingresses for ClusterIP service %s/%s: %w", namespace, name, err) } for _, ing := range ingressList.Items { + // Skip the current Ingress being reconciled to avoid a + // self-referential loop: updating its own status would trigger + // a new reconcile, which would collect its own (just-written) + // hostname again and potentially repeat indefinitely. + if ing.Namespace == ingress.Namespace && ing.Name == ingress.Name { + continue + } for _, lb := range ing.Status.LoadBalancer.Ingress { if lb.Hostname != "" { loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{ diff --git a/test/e2e/gatewayapi/gateway.go b/test/e2e/gatewayapi/gateway.go index cb8d2959ae..5f09f37381 100644 --- a/test/e2e/gatewayapi/gateway.go +++ b/test/e2e/gatewayapi/gateway.go @@ -596,13 +596,12 @@ spec: return gateway.Status.Addresses, nil } - It("sets IPAddress type when statusAddress is an IP", func() { - ipAddr := "192.168.1.100" + checkGatewayStatusAddressType := func(addrValue string, expectedType gatewayv1.AddressType) { gatewayClassName := s.Namespace() - By("create GatewayProxy with IP statusAddress") + By("create GatewayProxy with statusAddress") gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, - s.Namespace(), ipAddr, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + s.Namespace(), addrValue, s.Deployer.GetAdminEndpoint(), s.AdminKey()) Expect(s.CreateResourceFromString(gatewayProxy)).NotTo(HaveOccurred(), "creating GatewayProxy") By("create GatewayClass") @@ -616,7 +615,7 @@ spec: fmt.Sprintf(defaultGateway, gatewayName, gatewayClassName), s.Namespace()), ).NotTo(HaveOccurred(), "creating Gateway") - By("check Gateway status addresses has IPAddress type") + By("check Gateway status address type") s.RetryAssertion(func() error { addrs, err := getGatewayAddresses(gatewayName) if err != nil { @@ -626,60 +625,25 @@ spec: return fmt.Errorf("expected at least 1 status address, got 0") } addr := addrs[0] - if addr.Value != ipAddr { - return fmt.Errorf("expected address value %s, got %s", ipAddr, addr.Value) + if addr.Value != addrValue { + return fmt.Errorf("expected address value %s, got %s", addrValue, addr.Value) } if addr.Type == nil { return fmt.Errorf("expected address type to be set, got nil") } - if *addr.Type != gatewayv1.IPAddressType { - return fmt.Errorf("expected address type %s, got %s", gatewayv1.IPAddressType, *addr.Type) + if *addr.Type != expectedType { + return fmt.Errorf("expected address type %s, got %s", expectedType, *addr.Type) } return nil - }).ShouldNot(HaveOccurred(), "check Gateway IPAddress status address type") + }).ShouldNot(HaveOccurred(), "check Gateway status address type") + } + + It("sets IPAddress type when statusAddress is an IP", func() { + checkGatewayStatusAddressType("192.168.1.100", gatewayv1.IPAddressType) }) It("sets Hostname type when statusAddress is a hostname", func() { - hostname := "mygateway.example.com" - gatewayClassName := s.Namespace() - - By("create GatewayProxy with hostname statusAddress") - gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, - s.Namespace(), hostname, s.Deployer.GetAdminEndpoint(), s.AdminKey()) - Expect(s.CreateResourceFromString(gatewayProxy)).NotTo(HaveOccurred(), "creating GatewayProxy") - - By("create GatewayClass") - Expect(s.CreateResourceFromStringWithNamespace( - fmt.Sprintf(defaultGatewayClass, gatewayClassName, s.GetControllerName()), ""), - ).NotTo(HaveOccurred(), "creating GatewayClass") - - By("create Gateway") - gatewayName := s.Namespace() - Expect(s.CreateResourceFromStringWithNamespace( - fmt.Sprintf(defaultGateway, gatewayName, gatewayClassName), s.Namespace()), - ).NotTo(HaveOccurred(), "creating Gateway") - - By("check Gateway status addresses has Hostname type") - s.RetryAssertion(func() error { - addrs, err := getGatewayAddresses(gatewayName) - if err != nil { - return err - } - if len(addrs) == 0 { - return fmt.Errorf("expected at least 1 status address, got 0") - } - addr := addrs[0] - if addr.Value != hostname { - return fmt.Errorf("expected address value %s, got %s", hostname, addr.Value) - } - if addr.Type == nil { - return fmt.Errorf("expected address type to be set, got nil") - } - if *addr.Type != gatewayv1.HostnameAddressType { - return fmt.Errorf("expected address type %s, got %s", gatewayv1.HostnameAddressType, *addr.Type) - } - return nil - }).ShouldNot(HaveOccurred(), "check Gateway Hostname status address type") + checkGatewayStatusAddressType("mygateway.example.com", gatewayv1.HostnameAddressType) }) }) }) diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go index 6383b38037..3bf541453a 100644 --- a/test/e2e/ingress/ingress.go +++ b/test/e2e/ingress/ingress.go @@ -1379,13 +1379,12 @@ spec: return ing.Status.LoadBalancer.Ingress, nil } - It("sets IP field when statusAddress is an IP", func() { - ipAddr := "192.168.1.200" + checkIngressStatusAddress := func(addrValue, ingressSuffix, expectedIP, expectedHostname string) { ingressClassName := s.Namespace() - By("create GatewayProxy with IP statusAddress") + By("create GatewayProxy with statusAddress") gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, - s.Namespace(), ipAddr, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + s.Namespace(), addrValue, s.Deployer.GetAdminEndpoint(), s.AdminKey()) Expect(s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())).NotTo(HaveOccurred(), "creating GatewayProxy") By("create IngressClass") @@ -1394,12 +1393,12 @@ spec: ).NotTo(HaveOccurred(), "creating IngressClass") By("create Ingress") - ingressName := s.Namespace() + "-ip" + ingressName := s.Namespace() + ingressSuffix Expect(s.CreateResourceFromStringWithNamespace( fmt.Sprintf(ingressYaml, ingressName, ingressClassName, "httpbin-service-e2e-test"), s.Namespace()), ).NotTo(HaveOccurred(), "creating Ingress") - By("check Ingress status has IP set and Hostname empty") + By("check Ingress load balancer status") s.RetryAssertion(func() error { lbs, err := getIngressLBStatus(ingressName) if err != nil { @@ -1408,53 +1407,22 @@ spec: if len(lbs) == 0 { return fmt.Errorf("expected at least 1 load balancer ingress, got 0") } - if lbs[0].IP != ipAddr { - return fmt.Errorf("expected IP %s, got %s", ipAddr, lbs[0].IP) + if lbs[0].IP != expectedIP { + return fmt.Errorf("expected IP %s, got %s", expectedIP, lbs[0].IP) } - if lbs[0].Hostname != "" { - return fmt.Errorf("expected Hostname to be empty, got %s", lbs[0].Hostname) + if lbs[0].Hostname != expectedHostname { + return fmt.Errorf("expected Hostname %s, got %s", expectedHostname, lbs[0].Hostname) } return nil - }).ShouldNot(HaveOccurred(), "check Ingress IP load balancer status") + }).ShouldNot(HaveOccurred(), "check Ingress load balancer status") + } + + It("sets IP field when statusAddress is an IP", func() { + checkIngressStatusAddress("192.168.1.200", "-ip", "192.168.1.200", "") }) It("sets Hostname field when statusAddress is a hostname", func() { - hostname := "myingress.example.com" - ingressClassName := s.Namespace() - - By("create GatewayProxy with hostname statusAddress") - gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, - s.Namespace(), hostname, s.Deployer.GetAdminEndpoint(), s.AdminKey()) - Expect(s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())).NotTo(HaveOccurred(), "creating GatewayProxy") - - By("create IngressClass") - Expect(s.CreateResourceFromStringWithNamespace( - fmt.Sprintf(ingressClassYaml, ingressClassName, s.GetControllerName(), s.Namespace()), ""), - ).NotTo(HaveOccurred(), "creating IngressClass") - - By("create Ingress") - ingressName := s.Namespace() + "-host" - Expect(s.CreateResourceFromStringWithNamespace( - fmt.Sprintf(ingressYaml, ingressName, ingressClassName, "httpbin-service-e2e-test"), s.Namespace()), - ).NotTo(HaveOccurred(), "creating Ingress") - - By("check Ingress status has Hostname set and IP empty") - s.RetryAssertion(func() error { - lbs, err := getIngressLBStatus(ingressName) - if err != nil { - return err - } - if len(lbs) == 0 { - return fmt.Errorf("expected at least 1 load balancer ingress, got 0") - } - if lbs[0].Hostname != hostname { - return fmt.Errorf("expected Hostname %s, got %s", hostname, lbs[0].Hostname) - } - if lbs[0].IP != "" { - return fmt.Errorf("expected IP to be empty, got %s", lbs[0].IP) - } - return nil - }).ShouldNot(HaveOccurred(), "check Ingress Hostname load balancer status") + checkIngressStatusAddress("myingress.example.com", "-host", "", "myingress.example.com") }) It("propagates hostname from referencing Ingress when publishService is a ClusterIP", func() { From 267471680265ce67ceab8a1be711b78a5538520c Mon Sep 17 00:00:00 2001 From: Hemanth1205 Date: Tue, 17 Mar 2026 15:39:52 +0530 Subject: [PATCH 04/10] fix: added logic to update IP address and resolve review comments from copilot --- internal/controller/ingress_controller.go | 13 +++++++++---- test/e2e/ingress/ingress.go | 10 +++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index 8babfc41c1..695716f016 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -695,13 +695,13 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.Tra if addr == "" { continue } - ingress := networkingv1.IngressLoadBalancerIngress{} + lbIngress := networkingv1.IngressLoadBalancerIngress{} if net.ParseIP(addr) != nil { - ingress.IP = addr + lbIngress.IP = addr } else { - ingress.Hostname = addr + lbIngress.Hostname = addr } - loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, ingress) + loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, lbIngress) } } else { // 2. if the IngressStatusAddress is not configured, try to use the PublishService @@ -757,6 +757,11 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.Tra continue } for _, lb := range ing.Status.LoadBalancer.Ingress { + if lb.IP != "" { + loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{ + IP: lb.IP, + }) + } if lb.Hostname != "" { loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{ Hostname: lb.Hostname, diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go index 3bf541453a..d8ea295607 100644 --- a/test/e2e/ingress/ingress.go +++ b/test/e2e/ingress/ingress.go @@ -1391,7 +1391,15 @@ spec: Expect(s.CreateResourceFromStringWithNamespace( fmt.Sprintf(ingressClassYaml, ingressClassName, s.GetControllerName(), s.Namespace()), ""), ).NotTo(HaveOccurred(), "creating IngressClass") - + DeferCleanup(func() { + ingressClass := &networkingv1.IngressClass{} + if err := s.K8sClient.Get(context.Background(), types.NamespacedName{ + Name: ingressClassName, + }, ingressClass); err == nil { + _ = s.K8sClient.Delete(context.Background(), ingressClass) + } + }) + By("create Ingress") ingressName := s.Namespace() + ingressSuffix Expect(s.CreateResourceFromStringWithNamespace( From 6799d30b06fb80a5f317a9d749809da37a58b7d5 Mon Sep 17 00:00:00 2001 From: Hemanth1205 Date: Thu, 19 Mar 2026 09:45:09 +0530 Subject: [PATCH 05/10] fix: update ingress test to fix formatting error --- test/e2e/ingress/ingress.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go index d8ea295607..ef4fa1edbe 100644 --- a/test/e2e/ingress/ingress.go +++ b/test/e2e/ingress/ingress.go @@ -1392,14 +1392,14 @@ spec: fmt.Sprintf(ingressClassYaml, ingressClassName, s.GetControllerName(), s.Namespace()), ""), ).NotTo(HaveOccurred(), "creating IngressClass") DeferCleanup(func() { - ingressClass := &networkingv1.IngressClass{} - if err := s.K8sClient.Get(context.Background(), types.NamespacedName{ - Name: ingressClassName, - }, ingressClass); err == nil { - _ = s.K8sClient.Delete(context.Background(), ingressClass) - } - }) - + ingressClass := &networkingv1.IngressClass{} + if err := s.K8sClient.Get(context.Background(), types.NamespacedName{ + Name: ingressClassName, + }, ingressClass); err == nil { + _ = s.K8sClient.Delete(context.Background(), ingressClass) + } + }) + By("create Ingress") ingressName := s.Namespace() + ingressSuffix Expect(s.CreateResourceFromStringWithNamespace( From 214c4df903edf5e29eedcc1d5ad00e7c260a8289 Mon Sep 17 00:00:00 2001 From: Hemanth1205 Date: Fri, 20 Mar 2026 12:42:46 +0530 Subject: [PATCH 06/10] fix: update code to fix the review comments and removed the clusterIP service logic and associated test --- docs/en/latest/reference/example.md | 5 +- internal/controller/gateway_controller.go | 34 +++---- internal/controller/ingress_controller.go | 35 +------ test/e2e/ingress/ingress.go | 111 ---------------------- 4 files changed, 19 insertions(+), 166 deletions(-) diff --git a/docs/en/latest/reference/example.md b/docs/en/latest/reference/example.md index a9d7a9aeb8..fecbc7002c 100644 --- a/docs/en/latest/reference/example.md +++ b/docs/en/latest/reference/example.md @@ -1137,10 +1137,7 @@ spec: publishService: apisix-gateway ``` -When using `publishService`, the controller will use the endpoint of this Service to update the status information of the Ingress resource. The format can be either `namespace/svc-name` or simply `svc-name` if the default namespace is correctly set. - -- If the Service is of `LoadBalancer` type, the controller uses its external IP or hostname. -- If the Service is of `ClusterIP` type, the controller propagates the hostname from any Ingress resources that reference that Service. +When using `publishService`, the controller will use the endpoint of this Service to update the status information of the Ingress resource. The format can be either `namespace/svc-name` or simply `svc-name` if the default namespace is correctly set. Only `LoadBalancer` type services are supported - the controller uses their external IP or hostname to populate the Ingress load balancer status. diff --git a/internal/controller/gateway_controller.go b/internal/controller/gateway_controller.go index 906517bd4d..98796820ac 100644 --- a/internal/controller/gateway_controller.go +++ b/internal/controller/gateway_controller.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "net" + "reflect" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -179,22 +180,20 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct msg: "gateway proxy not found", } } else { - if len(gateway.Status.Addresses) != len(gatewayProxy.Spec.StatusAddress) { - for _, addr := range gatewayProxy.Spec.StatusAddress { - if addr == "" { - continue - } - addrType := gatewayv1.IPAddressType - if net.ParseIP(addr) == nil { - addrType = gatewayv1.HostnameAddressType - } - addrs = append(addrs, - gatewayv1.GatewayStatusAddress{ - Type: &addrType, - Value: addr, - }, - ) + for _, addr := range gatewayProxy.Spec.StatusAddress { + if addr == "" { + continue + } + addrType := gatewayv1.IPAddressType + if net.ParseIP(addr) == nil { + addrType = gatewayv1.HostnameAddressType } + addrs = append(addrs, + gatewayv1.GatewayStatusAddress{ + Type: &addrType, + Value: addr, + }, + ) } } @@ -213,8 +212,9 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct accepted := SetGatewayConditionAccepted(gateway, acceptStatus.status, acceptStatus.msg) programmed := SetGatewayConditionProgrammed(gateway, conditionProgrammedStatus, conditionProgrammedMsg) - if accepted || programmed || len(addrs) > 0 || len(listenerStatuses) > 0 { - if len(addrs) > 0 { + addressesChanged := !reflect.DeepEqual(gateway.Status.Addresses, addrs) + if accepted || programmed || addressesChanged || len(listenerStatuses) > 0 { + if addressesChanged { gateway.Status.Addresses = addrs } if len(listenerStatuses) > 0 { diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index 695716f016..47e1a06781 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -722,8 +722,7 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.Tra return fmt.Errorf("failed to get publish service %s: %w", publishService, err) } - switch svc.Spec.Type { - case corev1.ServiceTypeLoadBalancer: + if svc.Spec.Type == corev1.ServiceTypeLoadBalancer { // get the LoadBalancer IP and Hostname of the service for _, ip := range svc.Status.LoadBalancer.Ingress { if ip.IP != "" { @@ -737,38 +736,6 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.Tra }) } } - case corev1.ServiceTypeClusterIP: - // for ClusterIP services, find Ingresses that reference this service - // and collect hostnames from their load balancer status - // this is when we run the apisix in ClusterIP mode and enable Ingress - // when deploying in Cloud environments. - ingressList := &networkingv1.IngressList{} - if err := r.List(ctx, ingressList, client.MatchingFields{ - indexer.ServiceIndexRef: indexer.GenIndexKey(namespace, name), - }); err != nil { - return fmt.Errorf("failed to list ingresses for ClusterIP service %s/%s: %w", namespace, name, err) - } - for _, ing := range ingressList.Items { - // Skip the current Ingress being reconciled to avoid a - // self-referential loop: updating its own status would trigger - // a new reconcile, which would collect its own (just-written) - // hostname again and potentially repeat indefinitely. - if ing.Namespace == ingress.Namespace && ing.Name == ingress.Name { - continue - } - for _, lb := range ing.Status.LoadBalancer.Ingress { - if lb.IP != "" { - loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{ - IP: lb.IP, - }) - } - if lb.Hostname != "" { - loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{ - Hostname: lb.Hostname, - }) - } - } - } } } } diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go index ef4fa1edbe..55bf53fed0 100644 --- a/test/e2e/ingress/ingress.go +++ b/test/e2e/ingress/ingress.go @@ -31,7 +31,6 @@ import ( . "github.com/onsi/gomega" "github.com/stretchr/testify/assert" networkingv1 "k8s.io/api/networking/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" @@ -1316,24 +1315,6 @@ spec: type: AdminKey adminKey: value: "%s" -` - var gatewayProxyWithPublishServiceYaml = ` -apiVersion: apisix.apache.org/v1alpha1 -kind: GatewayProxy -metadata: - name: apisix-proxy-config - namespace: %s -spec: - publishService: %s/%s - provider: - type: ControlPlane - controlPlane: - endpoints: - - %s - auth: - type: AdminKey - adminKey: - value: "%s" ` var ingressClassYaml = ` apiVersion: networking.k8s.io/v1 @@ -1433,97 +1414,5 @@ spec: checkIngressStatusAddress("myingress.example.com", "-host", "", "myingress.example.com") }) - It("propagates hostname from referencing Ingress when publishService is a ClusterIP", func() { - clusterIPSvcName := "apisix-clusterip-svc" - expectedHostname := "clusterip.example.com" - ingressClassName := s.Namespace() - - By("create ClusterIP service") - clusterIPSvc := fmt.Sprintf(` -apiVersion: v1 -kind: Service -metadata: - name: %s - namespace: %s -spec: - type: ClusterIP - selector: - app: httpbin - ports: - - port: 80 - targetPort: 80 -`, clusterIPSvcName, s.Namespace()) - Expect(s.CreateResourceFromStringWithNamespace(clusterIPSvc, s.Namespace())).NotTo(HaveOccurred(), "creating ClusterIP service") - - By("create source Ingress referencing the ClusterIP service") - sourceIngressName := s.Namespace() + "-source" - Expect(s.CreateResourceFromStringWithNamespace( - fmt.Sprintf(ingressYaml, sourceIngressName, ingressClassName, clusterIPSvcName), s.Namespace()), - ).NotTo(HaveOccurred(), "creating source Ingress") - - By("patch source Ingress status with hostname") - sourceIng := &networkingv1.Ingress{} - Expect(s.K8sClient.Get(context.Background(), types.NamespacedName{ - Name: sourceIngressName, Namespace: s.Namespace(), - }, sourceIng)).NotTo(HaveOccurred(), "getting source Ingress") - sourceIng.Status.LoadBalancer.Ingress = []networkingv1.IngressLoadBalancerIngress{ - {Hostname: expectedHostname}, - } - Expect(s.K8sClient.Status().Update(context.Background(), sourceIng)).NotTo(HaveOccurred(), "patching source Ingress status") - - By("create GatewayProxy with ClusterIP publishService") - gatewayProxy := fmt.Sprintf(gatewayProxyWithPublishServiceYaml, - s.Namespace(), s.Namespace(), clusterIPSvcName, s.Deployer.GetAdminEndpoint(), s.AdminKey()) - Expect(s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())).NotTo(HaveOccurred(), "creating GatewayProxy") - - By("create IngressClass") - Expect(s.CreateResourceFromStringWithNamespace( - fmt.Sprintf(ingressClassYaml, ingressClassName, s.GetControllerName(), s.Namespace()), ""), - ).NotTo(HaveOccurred(), "creating IngressClass") - - By("create main Ingress") - mainIngressName := s.Namespace() + "-main" - mainIngress := fmt.Sprintf(` -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: %s -spec: - ingressClassName: %s - rules: - - host: status.example.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: httpbin-service-e2e-test - port: - number: 80 -`, mainIngressName, ingressClassName) - Expect(s.CreateResourceFromStringWithNamespace(mainIngress, s.Namespace())).NotTo(HaveOccurred(), "creating main Ingress") - - By("check main Ingress status propagates hostname from source Ingress") - s.RetryAssertion(func() error { - lbs, err := getIngressLBStatus(mainIngressName) - if err != nil { - return err - } - if len(lbs) == 0 { - return fmt.Errorf("expected at least 1 load balancer ingress, got 0") - } - if lbs[0].Hostname != expectedHostname { - return fmt.Errorf("expected Hostname %s, got %s", expectedHostname, lbs[0].Hostname) - } - return nil - }).ShouldNot(HaveOccurred(), "check main Ingress load balancer status from ClusterIP") - - // cleanup: avoid leaking the cluster-scoped IngressClass - DeferCleanup(func() { - ic := &networkingv1.IngressClass{ObjectMeta: metav1.ObjectMeta{Name: ingressClassName}} - _ = s.K8sClient.Delete(context.Background(), ic) - }) - }) }) }) From 8cbe4c9e4f40750c9210b4610a90401d52d9273c Mon Sep 17 00:00:00 2001 From: Hemanth1205 Date: Fri, 20 Mar 2026 13:06:41 +0530 Subject: [PATCH 07/10] fix: updated gateway tests and created a helper method to remove the duplicated assertion logic --- test/e2e/gatewayapi/gateway.go | 77 +++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/test/e2e/gatewayapi/gateway.go b/test/e2e/gatewayapi/gateway.go index 5f09f37381..b58044980b 100644 --- a/test/e2e/gatewayapi/gateway.go +++ b/test/e2e/gatewayapi/gateway.go @@ -596,6 +596,29 @@ spec: return gateway.Status.Addresses, nil } + assertGatewayAddress := func(gatewayName, expectedValue string, expectedType gatewayv1.AddressType) { + s.RetryAssertion(func() error { + addrs, err := getGatewayAddresses(gatewayName) + if err != nil { + return err + } + if len(addrs) == 0 { + return fmt.Errorf("expected at least 1 status address, got 0") + } + addr := addrs[0] + if addr.Value != expectedValue { + return fmt.Errorf("expected address value %s, got %s", expectedValue, addr.Value) + } + if addr.Type == nil { + return fmt.Errorf("expected address type to be set, got nil") + } + if *addr.Type != expectedType { + return fmt.Errorf("expected address type %s, got %s", expectedType, *addr.Type) + } + return nil + }).ShouldNot(HaveOccurred(), "check Gateway status address") + } + checkGatewayStatusAddressType := func(addrValue string, expectedType gatewayv1.AddressType) { gatewayClassName := s.Namespace() @@ -616,26 +639,7 @@ spec: ).NotTo(HaveOccurred(), "creating Gateway") By("check Gateway status address type") - s.RetryAssertion(func() error { - addrs, err := getGatewayAddresses(gatewayName) - if err != nil { - return err - } - if len(addrs) == 0 { - return fmt.Errorf("expected at least 1 status address, got 0") - } - addr := addrs[0] - if addr.Value != addrValue { - return fmt.Errorf("expected address value %s, got %s", addrValue, addr.Value) - } - if addr.Type == nil { - return fmt.Errorf("expected address type to be set, got nil") - } - if *addr.Type != expectedType { - return fmt.Errorf("expected address type %s, got %s", expectedType, *addr.Type) - } - return nil - }).ShouldNot(HaveOccurred(), "check Gateway status address type") + assertGatewayAddress(gatewayName, addrValue, expectedType) } It("sets IPAddress type when statusAddress is an IP", func() { @@ -645,5 +649,38 @@ spec: It("sets Hostname type when statusAddress is a hostname", func() { checkGatewayStatusAddressType("mygateway.example.com", gatewayv1.HostnameAddressType) }) + + It("updates status when statusAddress value changes without count change", func() { + gatewayClassName := s.Namespace() + gatewayName := s.Namespace() + initialAddr := "192.168.1.100" + updatedAddr := "updated.example.com" + + By("create GatewayProxy with initial statusAddress") + gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, + s.Namespace(), initialAddr, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + Expect(s.CreateResourceFromString(gatewayProxy)).NotTo(HaveOccurred(), "creating GatewayProxy") + + By("create GatewayClass") + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(defaultGatewayClass, gatewayClassName, s.GetControllerName()), ""), + ).NotTo(HaveOccurred(), "creating GatewayClass") + + By("create Gateway") + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(defaultGateway, gatewayName, gatewayClassName), s.Namespace()), + ).NotTo(HaveOccurred(), "creating Gateway") + + By("verify initial status address is set") + assertGatewayAddress(gatewayName, initialAddr, gatewayv1.IPAddressType) + + By("update GatewayProxy with different statusAddress (same count)") + updatedGatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, + s.Namespace(), updatedAddr, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + Expect(s.CreateResourceFromString(updatedGatewayProxy)).NotTo(HaveOccurred(), "updating GatewayProxy") + + By("verify status address is updated to new value and type") + assertGatewayAddress(gatewayName, updatedAddr, gatewayv1.HostnameAddressType) + }) }) }) From 8bfebc048be7500bd5f9ee531dc1c1c9b44af203 Mon Sep 17 00:00:00 2001 From: Hemanth1205 Date: Fri, 20 Mar 2026 15:00:53 +0530 Subject: [PATCH 08/10] fix: added logic to support ClusterIP svc and checks to remove duplicates from the statusAddress passed from gatewayproxy --- docs/en/latest/reference/example.md | 5 +- internal/controller/gateway_controller.go | 11 ++ internal/controller/ingress_controller.go | 54 ++++++- test/e2e/gatewayapi/gateway.go | 78 +++++++--- test/e2e/ingress/ingress.go | 173 +++++++++++++++++++++- 5 files changed, 292 insertions(+), 29 deletions(-) diff --git a/docs/en/latest/reference/example.md b/docs/en/latest/reference/example.md index fecbc7002c..a9d7a9aeb8 100644 --- a/docs/en/latest/reference/example.md +++ b/docs/en/latest/reference/example.md @@ -1137,7 +1137,10 @@ spec: publishService: apisix-gateway ``` -When using `publishService`, the controller will use the endpoint of this Service to update the status information of the Ingress resource. The format can be either `namespace/svc-name` or simply `svc-name` if the default namespace is correctly set. Only `LoadBalancer` type services are supported - the controller uses their external IP or hostname to populate the Ingress load balancer status. +When using `publishService`, the controller will use the endpoint of this Service to update the status information of the Ingress resource. The format can be either `namespace/svc-name` or simply `svc-name` if the default namespace is correctly set. + +- If the Service is of `LoadBalancer` type, the controller uses its external IP or hostname. +- If the Service is of `ClusterIP` type, the controller propagates the hostname from any Ingress resources that reference that Service. diff --git a/internal/controller/gateway_controller.go b/internal/controller/gateway_controller.go index 98796820ac..b717b9450e 100644 --- a/internal/controller/gateway_controller.go +++ b/internal/controller/gateway_controller.go @@ -197,6 +197,17 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } } + // deduplicate in case statusAddress contains repeated values + seen := make(map[string]bool) + deduped := addrs[:0] + for _, addr := range addrs { + if !seen[addr.Value] { + seen[addr.Value] = true + deduped = append(deduped, addr) + } + } + addrs = deduped + listenerStatuses, err := getListenerStatus(ctx, r.Client, gateway) if err != nil { r.Log.Error(err, "failed to get listener status", "gateway", req.NamespacedName) diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index 47e1a06781..4f4a3cbbf7 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -722,7 +722,8 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.Tra return fmt.Errorf("failed to get publish service %s: %w", publishService, err) } - if svc.Spec.Type == corev1.ServiceTypeLoadBalancer { + switch svc.Spec.Type { + case corev1.ServiceTypeLoadBalancer: // get the LoadBalancer IP and Hostname of the service for _, ip := range svc.Status.LoadBalancer.Ingress { if ip.IP != "" { @@ -736,12 +737,61 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.Tra }) } } + case corev1.ServiceTypeClusterIP: + // For ClusterIP services, propagate load balancer status from any other + // Ingress that lists this service as a backend (e.g. a cloud LB Ingress + // fronting the APISIX ClusterIP Service). Uses ServiceIndexRef, which + // indexes Ingresses by spec.rules[].http.paths[].backend.service.name. + ingressList := &networkingv1.IngressList{} + if err := r.List(ctx, ingressList, client.MatchingFields{ + indexer.ServiceIndexRef: indexer.GenIndexKey(namespace, name), + }); err != nil { + return fmt.Errorf("failed to list ingresses for ClusterIP service %s/%s: %w", namespace, name, err) + } + if len(ingressList.Items) == 0 { + r.Log.V(1).Info("no Ingress found with this ClusterIP service as a backend; status will not be propagated", + "service", namespace+"/"+name) + } + for _, ing := range ingressList.Items { + // Skip the current Ingress being reconciled to avoid a + // self-referential loop: updating its own status would trigger + // a new reconcile, which would collect its own (just-written) + // hostname again and potentially repeat indefinitely. + if ing.Namespace == ingress.Namespace && ing.Name == ingress.Name { + continue + } + for _, lb := range ing.Status.LoadBalancer.Ingress { + if lb.IP != "" { + loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{ + IP: lb.IP, + }) + } + if lb.Hostname != "" { + loadBalancerStatus.Ingress = append(loadBalancerStatus.Ingress, networkingv1.IngressLoadBalancerIngress{ + Hostname: lb.Hostname, + }) + } + } + } } } } + // deduplicate load balancer ingress entries that may arise when multiple + // source Ingresses carry the same address (ClusterIP case) or when + // statusAddress contains repeated values. + seen := make(map[networkingv1.IngressLoadBalancerIngress]bool) + deduped := loadBalancerStatus.Ingress[:0] + for _, lb := range loadBalancerStatus.Ingress { + if !seen[lb] { + seen[lb] = true + deduped = append(deduped, lb) + } + } + loadBalancerStatus.Ingress = deduped + // update the load balancer status - if len(loadBalancerStatus.Ingress) > 0 && !reflect.DeepEqual(ingress.Status.LoadBalancer, loadBalancerStatus) { + if !reflect.DeepEqual(ingress.Status.LoadBalancer, loadBalancerStatus) { ingress.Status.LoadBalancer = loadBalancerStatus r.Updater.Update(status.Update{ NamespacedName: utils.NamespacedName(ingress), diff --git a/test/e2e/gatewayapi/gateway.go b/test/e2e/gatewayapi/gateway.go index b58044980b..23a27cd914 100644 --- a/test/e2e/gatewayapi/gateway.go +++ b/test/e2e/gatewayapi/gateway.go @@ -619,24 +619,28 @@ spec: }).ShouldNot(HaveOccurred(), "check Gateway status address") } - checkGatewayStatusAddressType := func(addrValue string, expectedType gatewayv1.AddressType) { - gatewayClassName := s.Namespace() - - By("create GatewayProxy with statusAddress") - gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, - s.Namespace(), addrValue, s.Deployer.GetAdminEndpoint(), s.AdminKey()) - Expect(s.CreateResourceFromString(gatewayProxy)).NotTo(HaveOccurred(), "creating GatewayProxy") - + createGatewayClassAndGateway := func(gatewayClassName, gatewayName string) { By("create GatewayClass") Expect(s.CreateResourceFromStringWithNamespace( fmt.Sprintf(defaultGatewayClass, gatewayClassName, s.GetControllerName()), ""), ).NotTo(HaveOccurred(), "creating GatewayClass") By("create Gateway") - gatewayName := s.Namespace() Expect(s.CreateResourceFromStringWithNamespace( fmt.Sprintf(defaultGateway, gatewayName, gatewayClassName), s.Namespace()), ).NotTo(HaveOccurred(), "creating Gateway") + } + + checkGatewayStatusAddressType := func(addrValue string, expectedType gatewayv1.AddressType) { + gatewayClassName := s.Namespace() + + By("create GatewayProxy with statusAddress") + gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, + s.Namespace(), addrValue, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + Expect(s.CreateResourceFromString(gatewayProxy)).NotTo(HaveOccurred(), "creating GatewayProxy") + + gatewayName := s.Namespace() + createGatewayClassAndGateway(gatewayClassName, gatewayName) By("check Gateway status address type") assertGatewayAddress(gatewayName, addrValue, expectedType) @@ -650,6 +654,52 @@ spec: checkGatewayStatusAddressType("mygateway.example.com", gatewayv1.HostnameAddressType) }) + It("deduplicates repeated statusAddress entries", func() { + gatewayClassName := s.Namespace() + gatewayName := s.Namespace() + addr := "192.168.1.100" + + By("create GatewayProxy with the same IP listed twice in statusAddress") + gatewayProxy := fmt.Sprintf(` +apiVersion: apisix.apache.org/v1alpha1 +kind: GatewayProxy +metadata: + name: apisix-proxy-config + namespace: %s +spec: + statusAddress: + - %s + - %s + provider: + type: ControlPlane + controlPlane: + endpoints: + - %s + auth: + type: AdminKey + adminKey: + value: "%s" +`, s.Namespace(), addr, addr, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + Expect(s.CreateResourceFromString(gatewayProxy)).NotTo(HaveOccurred(), "creating GatewayProxy") + + createGatewayClassAndGateway(gatewayClassName, gatewayName) + + By("verify only one address appears in Gateway status despite duplicate input") + s.RetryAssertion(func() error { + addrs, err := getGatewayAddresses(gatewayName) + if err != nil { + return err + } + if len(addrs) != 1 { + return fmt.Errorf("expected exactly 1 status address after dedup, got %d", len(addrs)) + } + if addrs[0].Value != addr { + return fmt.Errorf("expected address value %s, got %s", addr, addrs[0].Value) + } + return nil + }).ShouldNot(HaveOccurred(), "check Gateway status address deduplication") + }) + It("updates status when statusAddress value changes without count change", func() { gatewayClassName := s.Namespace() gatewayName := s.Namespace() @@ -661,15 +711,7 @@ spec: s.Namespace(), initialAddr, s.Deployer.GetAdminEndpoint(), s.AdminKey()) Expect(s.CreateResourceFromString(gatewayProxy)).NotTo(HaveOccurred(), "creating GatewayProxy") - By("create GatewayClass") - Expect(s.CreateResourceFromStringWithNamespace( - fmt.Sprintf(defaultGatewayClass, gatewayClassName, s.GetControllerName()), ""), - ).NotTo(HaveOccurred(), "creating GatewayClass") - - By("create Gateway") - Expect(s.CreateResourceFromStringWithNamespace( - fmt.Sprintf(defaultGateway, gatewayName, gatewayClassName), s.Namespace()), - ).NotTo(HaveOccurred(), "creating Gateway") + createGatewayClassAndGateway(gatewayClassName, gatewayName) By("verify initial status address is set") assertGatewayAddress(gatewayName, initialAddr, gatewayv1.IPAddressType) diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go index 55bf53fed0..79720f3a53 100644 --- a/test/e2e/ingress/ingress.go +++ b/test/e2e/ingress/ingress.go @@ -1297,6 +1297,24 @@ spec: }) Context("Ingress Status Address", func() { + var gatewayProxyWithPublishServiceYaml = ` +apiVersion: apisix.apache.org/v1alpha1 +kind: GatewayProxy +metadata: + name: apisix-proxy-config + namespace: %s +spec: + publishService: %s/%s + provider: + type: ControlPlane + controlPlane: + endpoints: + - %s + auth: + type: AdminKey + adminKey: + value: "%s" +` var gatewayProxyWithStatusAddressYaml = ` apiVersion: apisix.apache.org/v1alpha1 kind: GatewayProxy @@ -1360,14 +1378,7 @@ spec: return ing.Status.LoadBalancer.Ingress, nil } - checkIngressStatusAddress := func(addrValue, ingressSuffix, expectedIP, expectedHostname string) { - ingressClassName := s.Namespace() - - By("create GatewayProxy with statusAddress") - gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, - s.Namespace(), addrValue, s.Deployer.GetAdminEndpoint(), s.AdminKey()) - Expect(s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())).NotTo(HaveOccurred(), "creating GatewayProxy") - + createIngressClass := func(ingressClassName string) { By("create IngressClass") Expect(s.CreateResourceFromStringWithNamespace( fmt.Sprintf(ingressClassYaml, ingressClassName, s.GetControllerName(), s.Namespace()), ""), @@ -1380,6 +1391,17 @@ spec: _ = s.K8sClient.Delete(context.Background(), ingressClass) } }) + } + + checkIngressStatusAddress := func(addrValue, ingressSuffix, expectedIP, expectedHostname string) { + ingressClassName := s.Namespace() + + By("create GatewayProxy with statusAddress") + gatewayProxy := fmt.Sprintf(gatewayProxyWithStatusAddressYaml, + s.Namespace(), addrValue, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + Expect(s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())).NotTo(HaveOccurred(), "creating GatewayProxy") + + createIngressClass(ingressClassName) By("create Ingress") ingressName := s.Namespace() + ingressSuffix @@ -1414,5 +1436,140 @@ spec: checkIngressStatusAddress("myingress.example.com", "-host", "", "myingress.example.com") }) + It("deduplicates repeated statusAddress entries", func() { + addr := "192.168.1.200" + ingressClassName := s.Namespace() + + By("create GatewayProxy with the same IP listed twice in statusAddress") + gatewayProxy := fmt.Sprintf(` +apiVersion: apisix.apache.org/v1alpha1 +kind: GatewayProxy +metadata: + name: apisix-proxy-config + namespace: %s +spec: + statusAddress: + - %s + - %s + provider: + type: ControlPlane + controlPlane: + endpoints: + - %s + auth: + type: AdminKey + adminKey: + value: "%s" +`, s.Namespace(), addr, addr, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + Expect(s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())).NotTo(HaveOccurred(), "creating GatewayProxy") + + createIngressClass(ingressClassName) + + By("create Ingress") + ingressName := s.Namespace() + "-dedup" + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(ingressYaml, ingressName, ingressClassName, "httpbin-service-e2e-test"), s.Namespace()), + ).NotTo(HaveOccurred(), "creating Ingress") + + By("verify only one address appears in Ingress status despite duplicate input") + s.RetryAssertion(func() error { + lbs, err := getIngressLBStatus(ingressName) + if err != nil { + return err + } + if len(lbs) != 1 { + return fmt.Errorf("expected exactly 1 load balancer ingress after dedup, got %d", len(lbs)) + } + if lbs[0].IP != addr { + return fmt.Errorf("expected IP %s, got %s", addr, lbs[0].IP) + } + return nil + }).ShouldNot(HaveOccurred(), "check Ingress load balancer status deduplication") + }) + + It("propagates hostname from referencing Ingress when publishService is a ClusterIP", func() { + clusterIPSvcName := "apisix-clusterip-svc" + expectedHostname := "clusterip.example.com" + ingressClassName := s.Namespace() + + By("create ClusterIP service") + clusterIPSvc := fmt.Sprintf(` +apiVersion: v1 +kind: Service +metadata: + name: %s + namespace: %s +spec: + type: ClusterIP + selector: + app: httpbin + ports: + - port: 80 + targetPort: 80 +`, clusterIPSvcName, s.Namespace()) + Expect(s.CreateResourceFromStringWithNamespace(clusterIPSvc, s.Namespace())).NotTo(HaveOccurred(), "creating ClusterIP service") + + By("create source Ingress referencing the ClusterIP service") + sourceIngressName := s.Namespace() + "-source" + Expect(s.CreateResourceFromStringWithNamespace( + fmt.Sprintf(ingressYaml, sourceIngressName, ingressClassName, clusterIPSvcName), s.Namespace()), + ).NotTo(HaveOccurred(), "creating source Ingress") + + By("patch source Ingress status with hostname") + sourceIng := &networkingv1.Ingress{} + Expect(s.K8sClient.Get(context.Background(), types.NamespacedName{ + Name: sourceIngressName, Namespace: s.Namespace(), + }, sourceIng)).NotTo(HaveOccurred(), "getting source Ingress") + sourceIng.Status.LoadBalancer.Ingress = []networkingv1.IngressLoadBalancerIngress{ + {Hostname: expectedHostname}, + } + Expect(s.K8sClient.Status().Update(context.Background(), sourceIng)).NotTo(HaveOccurred(), "patching source Ingress status") + + By("create GatewayProxy with ClusterIP publishService") + gatewayProxy := fmt.Sprintf(gatewayProxyWithPublishServiceYaml, + s.Namespace(), s.Namespace(), clusterIPSvcName, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + Expect(s.CreateResourceFromStringWithNamespace(gatewayProxy, s.Namespace())).NotTo(HaveOccurred(), "creating GatewayProxy") + + createIngressClass(ingressClassName) + + By("create main Ingress") + mainIngressName := s.Namespace() + "-main" + mainIngress := fmt.Sprintf(` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: %s +spec: + ingressClassName: %s + rules: + - host: status.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: httpbin-service-e2e-test + port: + number: 80 +`, mainIngressName, ingressClassName) + Expect(s.CreateResourceFromStringWithNamespace(mainIngress, s.Namespace())).NotTo(HaveOccurred(), "creating main Ingress") + + By("check main Ingress status propagates hostname from source Ingress") + s.RetryAssertion(func() error { + lbs, err := getIngressLBStatus(mainIngressName) + if err != nil { + return err + } + if len(lbs) == 0 { + return fmt.Errorf("expected at least 1 load balancer ingress, got 0") + } + if lbs[0].Hostname != expectedHostname { + return fmt.Errorf("expected Hostname %s, got %s", expectedHostname, lbs[0].Hostname) + } + return nil + }).ShouldNot(HaveOccurred(), "check main Ingress load balancer status from ClusterIP") + }) + }) }) From 06704d6325b5ede5ff2b3bfa3913abbd0c38d1de Mon Sep 17 00:00:00 2001 From: Hemanth1205 Date: Mon, 23 Mar 2026 15:09:32 +0530 Subject: [PATCH 09/10] fix: updated ingress_controller code to fix the pipeline issues --- internal/controller/gateway_controller.go | 10 +-------- internal/controller/ingress_controller.go | 10 +-------- internal/controller/utils.go | 25 +++++++++++++++++++++++ 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/internal/controller/gateway_controller.go b/internal/controller/gateway_controller.go index b717b9450e..748bfe37ed 100644 --- a/internal/controller/gateway_controller.go +++ b/internal/controller/gateway_controller.go @@ -198,15 +198,7 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } // deduplicate in case statusAddress contains repeated values - seen := make(map[string]bool) - deduped := addrs[:0] - for _, addr := range addrs { - if !seen[addr.Value] { - seen[addr.Value] = true - deduped = append(deduped, addr) - } - } - addrs = deduped + addrs = deduplicateGatewayStatusAddresses(addrs) listenerStatuses, err := getListenerStatus(ctx, r.Client, gateway) if err != nil { diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go index 4f4a3cbbf7..e36923731a 100644 --- a/internal/controller/ingress_controller.go +++ b/internal/controller/ingress_controller.go @@ -780,15 +780,7 @@ func (r *IngressReconciler) updateStatus(ctx context.Context, tctx *provider.Tra // deduplicate load balancer ingress entries that may arise when multiple // source Ingresses carry the same address (ClusterIP case) or when // statusAddress contains repeated values. - seen := make(map[networkingv1.IngressLoadBalancerIngress]bool) - deduped := loadBalancerStatus.Ingress[:0] - for _, lb := range loadBalancerStatus.Ingress { - if !seen[lb] { - seen[lb] = true - deduped = append(deduped, lb) - } - } - loadBalancerStatus.Ingress = deduped + loadBalancerStatus.Ingress = deduplicateLoadBalancerIngress(loadBalancerStatus.Ingress) // update the load balancer status if !reflect.DeepEqual(ingress.Status.LoadBalancer, loadBalancerStatus) { diff --git a/internal/controller/utils.go b/internal/controller/utils.go index a00ef9165b..e8a31ab6ef 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -1621,3 +1621,28 @@ func ExtractIngressClass(obj client.Object) string { panic(fmt.Errorf("unhandled object type %T for extracting ingress class", obj)) } } + +// deduplicateLoadBalancerIngress removes duplicate IngressLoadBalancerIngress entries in-place, +// comparing by IP and Hostname (Ports are ignored for dedup purposes). +func deduplicateLoadBalancerIngress(entries []networkingv1.IngressLoadBalancerIngress) []networkingv1.IngressLoadBalancerIngress { + slices.SortFunc(entries, func(a, b networkingv1.IngressLoadBalancerIngress) int { + if c := strings.Compare(a.IP, b.IP); c != 0 { + return c + } + return strings.Compare(a.Hostname, b.Hostname) + }) + return slices.CompactFunc(entries, func(a, b networkingv1.IngressLoadBalancerIngress) bool { + return a.IP == b.IP && a.Hostname == b.Hostname + }) +} + +// deduplicateGatewayStatusAddresses removes duplicate GatewayStatusAddress entries in-place, +// comparing by Value field (AddressType is a pointer so cannot be used as map key). +func deduplicateGatewayStatusAddresses(addrs []gatewayv1.GatewayStatusAddress) []gatewayv1.GatewayStatusAddress { + slices.SortFunc(addrs, func(a, b gatewayv1.GatewayStatusAddress) int { + return strings.Compare(a.Value, b.Value) + }) + return slices.CompactFunc(addrs, func(a, b gatewayv1.GatewayStatusAddress) bool { + return a.Value == b.Value + }) +} From fa1505bf33f42aa51ed4af7bebb6072c0e3650fe Mon Sep 17 00:00:00 2001 From: Hemanth1205 Date: Wed, 25 Mar 2026 00:31:32 +0530 Subject: [PATCH 10/10] fix: updated the failing ingress e2e testcase for clusterIP --- test/e2e/ingress/ingress.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go index 79720f3a53..5e56456de0 100644 --- a/test/e2e/ingress/ingress.go +++ b/test/e2e/ingress/ingress.go @@ -1511,9 +1511,28 @@ spec: By("create source Ingress referencing the ClusterIP service") sourceIngressName := s.Namespace() + "-source" - Expect(s.CreateResourceFromStringWithNamespace( - fmt.Sprintf(ingressYaml, sourceIngressName, ingressClassName, clusterIPSvcName), s.Namespace()), - ).NotTo(HaveOccurred(), "creating source Ingress") + // The source Ingress intentionally has no ingressClassName — it simulates a + // cloud ALB Ingress managed by a different controller. Without ingressClassName + // our controller will not reconcile it and will not overwrite its status. + sourceIngressYaml := fmt.Sprintf(` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: %s +spec: + rules: + - host: clusterip.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: %s + port: + number: 80 +`, sourceIngressName, clusterIPSvcName) + Expect(s.CreateResourceFromStringWithNamespace(sourceIngressYaml, s.Namespace())).NotTo(HaveOccurred(), "creating source Ingress") By("patch source Ingress status with hostname") sourceIng := &networkingv1.Ingress{}