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")
+ })
+
+ })
})