This is an automated email from the ASF dual-hosted git repository.

baoyuan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new 878e5015 fix: update publishService/statusAdress logic in gateway and 
ingress controller files (#2730) (#2732)
878e5015 is described below

commit 878e5015bda978cb2f4231511c835420d8769a2f
Author: Hemanth Chebrolu <[email protected]>
AuthorDate: Mon Apr 13 15:49:14 2026 +0530

    fix: update publishService/statusAdress logic in gateway and ingress 
controller files (#2730) (#2732)
---
 docs/en/latest/reference/example.md       |   9 +-
 internal/controller/gateway_controller.go |  33 ++--
 internal/controller/ingress_controller.go |  57 +++++-
 internal/controller/utils.go              |  25 +++
 test/e2e/gatewayapi/gateway.go            | 186 +++++++++++++++++++
 test/e2e/ingress/ingress.go               | 297 ++++++++++++++++++++++++++++++
 6 files changed, 589 insertions(+), 18 deletions(-)

diff --git a/docs/en/latest/reference/example.md 
b/docs/en/latest/reference/example.md
index 8c219f8c..a9d7a9ae 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.
+
 </TabItem>
 
 <TabItem value="ingress">
@@ -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.
 
 </TabItem>
 
diff --git a/internal/controller/gateway_controller.go 
b/internal/controller/gateway_controller.go
index 147bdd17..748bfe37 100644
--- a/internal/controller/gateway_controller.go
+++ b/internal/controller/gateway_controller.go
@@ -21,6 +21,8 @@ import (
        "context"
        "errors"
        "fmt"
+       "net"
+       "reflect"
 
        "github.com/go-logr/logr"
        corev1 "k8s.io/api/core/v1"
@@ -178,20 +180,26 @@ 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
-                               }
-                               addrs = append(addrs,
-                                       gatewayv1.GatewayStatusAddress{
-                                               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,
+                               },
+                       )
                }
        }
 
+       // deduplicate in case statusAddress contains repeated values
+       addrs = deduplicateGatewayStatusAddresses(addrs)
+
        listenerStatuses, err := getListenerStatus(ctx, r.Client, gateway)
        if err != nil {
                r.Log.Error(err, "failed to get listener status", "gateway", 
req.NamespacedName)
@@ -207,8 +215,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 f8447ee6..e3692373 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,
-                       })
+                       lbIngress := networkingv1.IngressLoadBalancerIngress{}
+                       if net.ParseIP(addr) != nil {
+                               lbIngress.IP = addr
+                       } else {
+                               lbIngress.Hostname = addr
+                       }
+                       loadBalancerStatus.Ingress = 
append(loadBalancerStatus.Ingress, lbIngress)
                }
        } else {
                // 2. if the IngressStatusAddress is not configured, try to use 
the PublishService
@@ -717,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 != "" {
@@ -731,12 +737,53 @@ 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.
+       loadBalancerStatus.Ingress = 
deduplicateLoadBalancerIngress(loadBalancerStatus.Ingress)
+
        // 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/internal/controller/utils.go b/internal/controller/utils.go
index a00ef916..e8a31ab6 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
+       })
+}
diff --git a/test/e2e/gatewayapi/gateway.go b/test/e2e/gatewayapi/gateway.go
index 8c5557ef..23a27cd9 100644
--- a/test/e2e/gatewayapi/gateway.go
+++ b/test/e2e/gatewayapi/gateway.go
@@ -539,4 +539,190 @@ 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
+               }
+
+               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")
+               }
+
+               createGatewayClassAndGateway := func(gatewayClassName, 
gatewayName string) {
+                       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")
+               }
+
+               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)
+               }
+
+               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() {
+                       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()
+                       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")
+
+                       createGatewayClassAndGateway(gatewayClassName, 
gatewayName)
+
+                       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)
+               })
+       })
 })
diff --git a/test/e2e/ingress/ingress.go b/test/e2e/ingress/ingress.go
index 6329b681..5e56456d 100644
--- a/test/e2e/ingress/ingress.go
+++ b/test/e2e/ingress/ingress.go
@@ -30,6 +30,7 @@ import (
        . "github.com/onsi/ginkgo/v2"
        . "github.com/onsi/gomega"
        "github.com/stretchr/testify/assert"
+       networkingv1 "k8s.io/api/networking/v1"
        "k8s.io/apimachinery/pkg/types"
        "k8s.io/utils/ptr"
 
@@ -1294,4 +1295,300 @@ spec:
                                Should(Equal(http.StatusOK))
                })
        })
+
+       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
+metadata:
+  name: apisix-proxy-config
+  namespace: %s
+spec:
+  statusAddress:
+  - %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
+               }
+
+               createIngressClass := func(ingressClassName string) {
+                       By("create IngressClass")
+                       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)
+                               }
+                       })
+               }
+
+               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
+                       Expect(s.CreateResourceFromStringWithNamespace(
+                               fmt.Sprintf(ingressYaml, ingressName, 
ingressClassName, "httpbin-service-e2e-test"), s.Namespace()),
+                       ).NotTo(HaveOccurred(), "creating Ingress")
+
+                       By("check Ingress load balancer status")
+                       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 != expectedIP {
+                                       return fmt.Errorf("expected IP %s, got 
%s", expectedIP, lbs[0].IP)
+                               }
+                               if lbs[0].Hostname != expectedHostname {
+                                       return fmt.Errorf("expected Hostname 
%s, got %s", expectedHostname, lbs[0].Hostname)
+                               }
+                               return nil
+                       }).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() {
+                       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"
+                       // 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{}
+                       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")
+               })
+
+       })
 })

Reply via email to