Repository: kubernetes-sigs/aws-load-balancer-controller
Pull request #4189: [feat gw api] Implement Gateway Status + Some bug fixes for Gateway
Author: login: zac-nixon
Labels: name: lgtm
description: "Looks good to me", indicates that a PR is ready to be merged.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: size/XXL
description: Denotes a PR that changes 1000+ lines, ignoring generated files.
name: cncf-cla: yes
description: Indicates the PR's author has signed the CNCF CLA.
Created: 2025-05-20T01:49:35Z | Merged: 2025-05-29T16:42:18Z

### Description

Implements Gateway conditions for both Programmed and Accepted. I had to refactor the route util in order to propagate the exact problem back to the gateway controller in order to properly implement the various failure causes on the Accepted condition.

I had to refactor the deployer to get back the LB status to correctly implement requeuing the Gateway in order to update the programmed condition when the LB finishes provisioning. 

Added support infrastructure annotations, they get propagated to the TGB created by the Gateway.


### Tests

- Verified Accepted / Program conditions:
```
nixozach@80a997300bd5 aws-load-balancer-controller % kubectl -n gateway-nlb get gateway test-gw-nlb -o yaml
	apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  annotations:
    foo: baz
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"gateway.networking.k8s.io/v1","kind":"Gateway","metadata":{"annotations":{},"name":"test-gw-nlb","namespace":"gateway-nlb"},"spec":{"gatewayClassName":"nlb-gateway","listeners":[{"allowedRoutes":{"namespaces":{"from":"Same"}},"name":"test-2024","port":80,"protocol":"TCP"}]}}
  creationTimestamp: "2025-04-17T05:38:04Z"
  finalizers:
  - gateway.k8s.aws/nlb
  generation: 3
  name: test-gw-nlb
  namespace: gateway-nlb
  resourceVersion: "26639358"
  uid: 823cdc65-4ea0-4dfc-b8b5-48b3a7e86516
spec:
  gatewayClassName: nlb-gateway
  infrastructure:
    parametersRef:
      group: gateway.k8s.aws
      kind: LoadBalancerConfiguration
      name: local-nlb
  listeners:
  - allowedRoutes:
      namespaces:
        from: Same
    name: test-2024
    port: 80
    protocol: TCP
status:
  addresses:
  - type: Hostname
    value: k8s-gatewayn-testgwnl-3797607738-fbef7aa6d15b46aa.elb.us-east-1.amazonaws.com
  conditions:
  - lastTransitionTime: "2025-05-15T22:13:13Z"
    message: ""
    observedGeneration: 3
    reason: Accepted
    status: "True"
    type: Accepted
  - lastTransitionTime: "2025-05-16T23:03:00Z"
    message: arn:aws:elasticloadbalancing:us-east-1:565768096483:loadbalancer/net/k8s-gatewayn-testgwnl-3797607738/fbef7aa6d15b46aa
    observedGeneration: 3
    reason: Programmed
    status: "True"
    type: Programmed
```

- Verified labels / annotations are applied from Gateway infrastructure parameter to TGB. (also backfilled tests here)

### Checklist
- [x] Added tests that cover your change (if possible)
- [ ] Added/modified documentation as required (such as the `README.md`, or the `docs` directory)
- [x] Manually tested
- [x] Made sure the title of the PR is a good description that can go into the release notes

### BONUS POINTS checklist: complete for good vibes and maybe prizes?! :exploding_head:
- [x] Backfilled missing tests for code in same general area :tada:
- [x] Refactored something and made the world a better place :star2:



Diff:
diff --git a/controllers/gateway/config_resolver.go b/controllers/gateway/config_resolver.go
index c706e6e8c9..ea7da57b23 100644
--- a/controllers/gateway/config_resolver.go
+++ b/controllers/gateway/config_resolver.go
@@ -29,10 +29,10 @@ func newGatewayConfigResolver() gatewayConfigResolver {
 func (resolver *gatewayConfigResolverImpl) getLoadBalancerConfigForGateway(ctx context.Context, k8sClient client.Client, gw *gwv1.Gateway, gwClass *gwv1.GatewayClass) (elbv2gw.LoadBalancerConfiguration, error) {
 
 	// If the Gateway Class isn't accepted, we shouldn't try to reconcile this Gateway.
-	derivedStatus, _ := deriveGatewayClassAcceptedStatus(gwClass)
+	derivedStatusIndx, ok := deriveAcceptedConditionIndex(gwClass)
 
-	if derivedStatus != metav1.ConditionTrue {
-		return elbv2gw.LoadBalancerConfiguration{}, errors.Errorf("Unable to materialize gateway when gateway class [%s] is not accepted. GatewayClass status is %s", gwClass.Name, derivedStatus)
+	if !ok || gwClass.Status.Conditions[derivedStatusIndx].Status != metav1.ConditionTrue {
+		return elbv2gw.LoadBalancerConfiguration{}, errors.Errorf("Unable to materialize gateway when gateway class [%s] is not accepted", gwClass.Name)
 	}
 
 	gatewayClassLBConfig, err := resolver.configResolverFn(ctx, k8sClient, gwClass.Spec.ParametersRef)
diff --git a/controllers/gateway/gateway_controller.go b/controllers/gateway/gateway_controller.go
index f74a37ee37..866de8257d 100644
--- a/controllers/gateway/gateway_controller.go
+++ b/controllers/gateway/gateway_controller.go
@@ -3,9 +3,11 @@ package gateway
 import (
 	"context"
 	"fmt"
+	elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
 	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/tools/record"
@@ -35,6 +37,12 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/source"
 	gwv1 "sigs.k8s.io/gateway-api/apis/v1"
 	gwalpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+	"time"
+)
+
+const (
+	requeueMessage          = "Monitoring provisioning state"
+	statusUpdateRequeueTime = 2 * time.Minute
 )
 
 var _ Reconciler = &gatewayReconciler{}
@@ -86,6 +94,7 @@ func newGatewayReconciler(controllerName string, lbType elbv2model.LoadBalancerT
 		reconcileTracker:        reconcileTracker,
 		cfgResolver:             cfgResolver,
 		routeReconciler:         routeReconciler,
+		gatewayConditionUpdater: prepareGatewayConditionUpdate,
 	}
 }
 
@@ -107,6 +116,7 @@ type gatewayReconciler struct {
 	logger                  logr.Logger
 	metricsCollector        lbcmetrics.MetricCollector
 	reconcileTracker        func(namespaceName types.NamespacedName)
+	gatewayConditionUpdater func(gw *gwv1.Gateway, targetConditionType string, newStatus metav1.ConditionStatus, reason string, message string) bool
 
 	cfgResolver     gatewayConfigResolver
 	routeReconciler routeutils.RouteReconciler
@@ -186,12 +196,23 @@ func (r *gatewayReconciler) reconcileHelper(ctx context.Context, req reconcile.R
 	mergedLbConfig, err := r.cfgResolver.getLoadBalancerConfigForGateway(ctx, r.k8sClient, gw, gwClass)
 
 	if err != nil {
+		statusErr := r.updateGatewayStatusFailure(ctx, gw, gwv1.GatewayReasonInvalid, err)
+		if statusErr != nil {
+			r.logger.Error(statusErr, "Unable to update gateway status on failure to retrieve attached config")
+		}
 		return err
 	}
 
 	allRoutes, err := r.gatewayLoader.LoadRoutesForGateway(ctx, *gw, r.routeFilter, r.routeReconciler)
 
 	if err != nil {
+		var loaderErr routeutils.LoaderError
+		if errors.As(err, &loaderErr) {
+			statusErr := r.updateGatewayStatusFailure(ctx, gw, loaderErr.GetGatewayReason(), loaderErr)
+			if statusErr != nil {
+				r.logger.Error(statusErr, "Unable to update gateway status on failure to build routes")
+			}
+		}
 		return err
 	}
 
@@ -248,10 +269,6 @@ func (r *gatewayReconciler) reconcileUpdate(ctx context.Context, gw *gwv1.Gatewa
 	if err != nil {
 		return err
 	}
-	lbDNS, err := lb.DNSName().Resolve(ctx)
-	if err != nil {
-		return err
-	}
 
 	if !backendSGRequired {
 		if err := r.backendSGProvider.Release(ctx, networking.ResourceTypeGateway, []types.NamespacedName{k8s.NamespacedName(gw)}); err != nil {
@@ -259,7 +276,7 @@ func (r *gatewayReconciler) reconcileUpdate(ctx context.Context, gw *gwv1.Gatewa
 		}
 	}
 
-	if err = r.updateGatewayStatus(ctx, lbDNS, gw); err != nil {
+	if err = r.updateGatewayStatusSuccess(ctx, lb.Status, gw); err != nil {
 		r.eventRecorder.Event(gw, corev1.EventTypeWarning, k8s.GatewayEventReasonFailedUpdateStatus, fmt.Sprintf("Failed update status due to %v", err))
 		return err
 	}
@@ -295,28 +312,58 @@ func (r *gatewayReconciler) buildModel(ctx context.Context, gw *gwv1.Gateway, cf
 	return stack, lb, backendSGRequired, nil
 }
 
-func (r *gatewayReconciler) updateGatewayStatus(ctx context.Context, lbDNS string, gw *gwv1.Gateway) error {
-	// TODO Consider LB ARN.
+func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbStatus *elbv2model.LoadBalancerStatus, gw *gwv1.Gateway) error {
+	// LB Status should always be set, if it's not, we need to prevent NPE
+	if lbStatus == nil {
+		r.logger.Info("Unable to update Gateway Status due to null LB status")
+		return nil
+	}
+	gwOld := gw.DeepCopy()
+
+	var needPatch bool
+	var requeueNeeded bool
+	if isGatewayProgrammed(*lbStatus) {
+		needPatch = r.gatewayConditionUpdater(gw, string(gwv1.GatewayConditionProgrammed), metav1.ConditionTrue, string(gwv1.GatewayConditionProgrammed), lbStatus.LoadBalancerARN)
+	} else {
+		requeueNeeded = true
+	}
 
-	// Gateway Address Status
+	needPatch = r.gatewayConditionUpdater(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionTrue, string(gwv1.GatewayConditionAccepted), "") || needPatch
 	if len(gw.Status.Addresses) != 1 ||
-		gw.Status.Addresses[0].Value != "" ||
-		gw.Status.Addresses[0].Value != lbDNS {
-		gwOld := gw.DeepCopy()
+		gw.Status.Addresses[0].Value != lbStatus.DNSName {
 		ipAddressType := gwv1.HostnameAddressType
 		gw.Status.Addresses = []gwv1.GatewayStatusAddress{
 			{
 				Type:  &ipAddressType,
-				Value: lbDNS,
+				Value: lbStatus.DNSName,
 			},
 		}
+		needPatch = true
+	}
+
+	if needPatch {
 		if err := r.k8sClient.Status().Patch(ctx, gw, client.MergeFrom(gwOld)); err != nil {
 			return errors.Wrapf(err, "failed to update gw status: %v", k8s.NamespacedName(gw))
 		}
 	}
 
-	// TODO: Listener status ListenerStatus
-	// https://github.com/aws/aws-application-networking-k8s/blob/main/pkg/controllers/gateway_controller.go#L350
+	if requeueNeeded {
+		return runtime.NewRequeueNeededAfter(requeueMessage, statusUpdateRequeueTime)
+	}
+
+	return nil
+}
+
+func (r *gatewayReconciler) updateGatewayStatusFailure(ctx context.Context, gw *gwv1.Gateway, reason gwv1.GatewayConditionReason, err error) error {
+	gwOld := gw.DeepCopy()
+
+	needPatch := r.gatewayConditionUpdater(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionFalse, string(reason), err.Error())
+
+	if needPatch {
+		if err := r.k8sClient.Status().Patch(ctx, gw, client.MergeFrom(gwOld)); err != nil {
+			return errors.Wrapf(err, "failed to update gw status: %v", k8s.NamespacedName(gw))
+		}
+	}
 
 	return nil
 }
@@ -475,3 +522,12 @@ func (r *gatewayReconciler) setupNLBGatewayControllerWatches(ctrl controller.Con
 	return nil
 
 }
+
+func isGatewayProgrammed(lbStatus elbv2model.LoadBalancerStatus) bool {
+	if lbStatus.ProvisioningState == nil {
+		return false
+	}
+
+	return lbStatus.ProvisioningState.Code == elbv2types.LoadBalancerStateEnumActive || lbStatus.ProvisioningState.Code == elbv2types.LoadBalancerStateEnumActiveImpaired
+
+}
diff --git a/controllers/gateway/utils.go b/controllers/gateway/utils.go
index 9fb62b5c3d..50dfebcb46 100644
--- a/controllers/gateway/utils.go
+++ b/controllers/gateway/utils.go
@@ -14,16 +14,21 @@ import (
 	"strconv"
 	"strings"
 	"time"
+	"unicode/utf8"
 )
 
 const (
 	gatewayClassAnnotationLastProcessedConfig          = "elbv2.k8s.aws/last-processed-config"
 	gatewayClassAnnotationLastProcessedConfigTimestamp = gatewayClassAnnotationLastProcessedConfig + "-timestamp"
+
+	// The max message that can be stored in a condition
+	maxMessageLength = 32700
 )
 
+// updateGatewayClassLastProcessedConfig updates the gateway class annotations with the last processed lb config resource version or "" if no lb config is attached to the gatewayclass
 func updateGatewayClassLastProcessedConfig(ctx context.Context, k8sClient client.Client, gwClass *gwv1.GatewayClass, lbConf *elbv2gw.LoadBalancerConfiguration) error {
 
-	calculatedVersion := gatewayClassAnnotationLastProcessedConfig
+	calculatedVersion := ""
 
 	if lbConf != nil {
 		calculatedVersion = lbConf.ResourceVersion
@@ -36,12 +41,16 @@ func updateGatewayClassLastProcessedConfig(ctx context.Context, k8sClient client
 	}
 
 	gwClassOld := gwClass.DeepCopy()
+	if gwClass.Annotations == nil {
+		gwClass.Annotations = make(map[string]string)
+	}
 	gwClass.Annotations[gatewayClassAnnotationLastProcessedConfig] = calculatedVersion
 	gwClass.Annotations[gatewayClassAnnotationLastProcessedConfigTimestamp] = strconv.FormatInt(time.Now().Unix(), 10)
 
 	return k8sClient.Patch(ctx, gwClass, client.MergeFrom(gwClassOld))
 }
 
+// getStoredProcessedConfig retrieves the resource version attached to the lb config referenced by the gateway class or nil if no such mapping exists.
 func getStoredProcessedConfig(gwClass *gwv1.GatewayClass) *string {
 	var storedVersion *string
 
@@ -54,10 +63,20 @@ func getStoredProcessedConfig(gwClass *gwv1.GatewayClass) *string {
 	return storedVersion
 }
 
+// updateGatewayClassAcceptedCondition updates the 'accepted' condition on the gateway class to the passed in parameters. if no 'Accepted' condition exists, do nothing.
 func updateGatewayClassAcceptedCondition(ctx context.Context, k8sClient client.Client, gwClass *gwv1.GatewayClass, newStatus metav1.ConditionStatus, reason string, message string) error {
-	derivedStatus, indxToUpdate := deriveGatewayClassAcceptedStatus(gwClass)
+	indxToUpdate, ok := deriveAcceptedConditionIndex(gwClass)
+
+	if ok {
+
+		storedStatus := gwClass.Status.Conditions[indxToUpdate].Status
+		storedMessage := gwClass.Status.Conditions[indxToUpdate].Message
+		storedReason := gwClass.Status.Conditions[indxToUpdate].Reason
+
+		if storedStatus == newStatus && storedMessage == message && storedReason == reason {
+			return nil
+		}
 
-	if indxToUpdate != -1 && derivedStatus != newStatus {
 		gwClassOld := gwClass.DeepCopy()
 		gwClass.Status.Conditions[indxToUpdate].LastTransitionTime = metav1.NewTime(time.Now())
 		gwClass.Status.Conditions[indxToUpdate].ObservedGeneration = gwClass.Generation
@@ -71,15 +90,56 @@ func updateGatewayClassAcceptedCondition(ctx context.Context, k8sClient client.C
 	return nil
 }
 
-func deriveGatewayClassAcceptedStatus(gwClass *gwv1.GatewayClass) (metav1.ConditionStatus, int) {
+// prepareGatewayConditionUpdate inserts the necessary data into the condition field of the gateway. The caller should patch the corresponding gateway. Returns false when no change was performed.
+func prepareGatewayConditionUpdate(gw *gwv1.Gateway, targetConditionType string, newStatus metav1.ConditionStatus, reason string, message string) bool {
+
+	indxToUpdate := -1
+	var derivedCondition metav1.Condition
+	for i, condition := range gw.Status.Conditions {
+		if condition.Type == targetConditionType {
+			indxToUpdate = i
+			derivedCondition = condition
+			break
+		}
+	}
+
+	// 32768 is the max message limit
+	truncatedMessage := truncateMessage(message)
+
+	if indxToUpdate != -1 {
+		if derivedCondition.Status != newStatus || derivedCondition.Message != truncatedMessage || derivedCondition.Reason != reason {
+			gw.Status.Conditions[indxToUpdate].LastTransitionTime = metav1.NewTime(time.Now())
+			gw.Status.Conditions[indxToUpdate].ObservedGeneration = gw.Generation
+			gw.Status.Conditions[indxToUpdate].Status = newStatus
+			gw.Status.Conditions[indxToUpdate].Message = truncatedMessage
+			gw.Status.Conditions[indxToUpdate].Reason = reason
+			return true
+		}
+	}
+	return false
+}
+
+func truncateMessage(s string) string {
+	if utf8.RuneCountInString(s) <= maxMessageLength {
+		return s
+	}
+
+	runes := []rune(s)
+	return string(runes[:maxMessageLength]) + "..."
+}
+
+// deriveAcceptedConditionIndex returns the index of the condition pertaining to the accepted condition.
+// -1 if the condition doesn't exist
+func deriveAcceptedConditionIndex(gwClass *gwv1.GatewayClass) (int, bool) {
 	for i, v := range gwClass.Status.Conditions {
 		if v.Type == string(gwv1.GatewayClassReasonAccepted) {
-			return v.Status, i
+			return i, true
 		}
 	}
-	return metav1.ConditionFalse, -1
+	return -1, false
 }
 
+// resolveLoadBalancerConfig returns the lb config referenced in the ParametersReference.
 func resolveLoadBalancerConfig(ctx context.Context, k8sClient client.Client, reference *gwv1.ParametersReference) (*elbv2gw.LoadBalancerConfiguration, error) {
 	var lbConf *elbv2gw.LoadBalancerConfiguration
 
diff --git a/controllers/gateway/utils_test.go b/controllers/gateway/utils_test.go
index bd9b2a6c55..868cf77bd6 100644
--- a/controllers/gateway/utils_test.go
+++ b/controllers/gateway/utils_test.go
@@ -1,11 +1,760 @@
 package gateway
 
 import (
+	"context"
+	"fmt"
+	awssdk "github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	elbv2gw "sigs.k8s.io/aws-load-balancer-controller/apis/gateway/v1beta1"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/routeutils"
+	"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
+	"sigs.k8s.io/aws-load-balancer-controller/pkg/testutils"
+	gwv1 "sigs.k8s.io/gateway-api/apis/v1"
+	"strconv"
 	"testing"
+	"time"
 )
 
+func Test_updateGatewayClassLastProcessedConfig(t *testing.T) {
+	testCases := []struct {
+		name            string
+		gwClass         gwv1.GatewayClass
+		lbConf          *elbv2gw.LoadBalancerConfiguration
+		expectedVersion string
+		noPatch         bool
+	}{
+		{
+			name: "no lb conf, no prior annotation",
+			gwClass: gwv1.GatewayClass{
+				ObjectMeta: v1.ObjectMeta{
+					Name: "gwclass",
+				},
+			},
+			expectedVersion: "",
+		},
+		{
+			name: "no lb conf, with prior annotation",
+			gwClass: gwv1.GatewayClass{
+				ObjectMeta: v1.ObjectMeta{
+					Name: "gwclass",
+					Annotations: map[string]string{
+						gatewayClassAnnotationLastProcessedConfig:          "foo",
+						gatewayClassAnnotationLastProcessedConfigTimestamp: "0",
+					},
+				},
+			},
+			expectedVersion: "",
+		},
+		{
+			name: "with lb conf, no prior annotation",
+			lbConf: &elbv2gw.LoadBalancerConfiguration{
+				ObjectMeta: v1.ObjectMeta{
+					ResourceVersion: "bar",
+				},
+			},
+			gwClass: gwv1.GatewayClass{
+				ObjectMeta: v1.ObjectMeta{
+					Name: "gwclass",
+				},
+			},
+			expectedVersion: "bar",
+		},
+		{
+			name: "with lb conf, with prior annotation",
+			lbConf: &elbv2gw.LoadBalancerConfiguration{
+				ObjectMeta: v1.ObjectMeta{
+					ResourceVersion: "bar",
+				},
+			},
+			gwClass: gwv1.GatewayClass{
+				ObjectMeta: v1.ObjectMeta{
+					Name: "gwclass",
+					Annotations: map[string]string{
+						gatewayClassAnnotationLastProcessedConfig:          "foo",
+						gatewayClassAnnotationLastProcessedConfigTimestamp: "0",
+					},
+				},
+			},
+			expectedVersion: "bar",
+		},
+		{
+			name: "no change in stored version should not trigger patch",
+			lbConf: &elbv2gw.LoadBalancerConfiguration{
+				ObjectMeta: v1.ObjectMeta{
+					ResourceVersion: "foo",
+				},
+			},
+			gwClass: gwv1.GatewayClass{
+				ObjectMeta: v1.ObjectMeta{
+					Name: "gwclass",
+					Annotations: map[string]string{
+						gatewayClassAnnotationLastProcessedConfig:          "foo",
+						gatewayClassAnnotationLastProcessedConfigTimestamp: "10",
+					},
+				},
+			},
+			expectedVersion: "foo",
+			noPatch:         true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			client := testutils.GenerateTestClient()
+			original := tc.gwClass.DeepCopy()
+			err := client.Create(context.Background(), original)
+			assert.NoError(t, err)
+			err = updateGatewayClassLastProcessedConfig(context.Background(), client, original, tc.lbConf)
+			assert.NoError(t, err)
+			stored := &gwv1.GatewayClass{}
+			err = client.Get(context.Background(), k8s.NamespacedName(original), stored)
+			assert.NoError(t, err)
+			assert.Equal(t, tc.expectedVersion, stored.Annotations[gatewayClassAnnotationLastProcessedConfig])
+
+			ts, err := strconv.Atoi(stored.Annotations[gatewayClassAnnotationLastProcessedConfigTimestamp])
+			assert.NoError(t, err)
+			assert.NotZero(t, ts)
+			if tc.noPatch {
+				assert.Equal(t, tc.gwClass.Annotations[gatewayClassAnnotationLastProcessedConfigTimestamp], stored.Annotations[gatewayClassAnnotationLastProcessedConfigTimestamp])
+			}
+		})
+	}
+}
+
+func Test_updateGatewayClassAcceptedCondition(t *testing.T) {
+	testCases := []struct {
+		name    string
+		gwClass gwv1.GatewayClass
+		status  metav1.ConditionStatus
+		reason  string
+		message string
+
+		expectedConditions []metav1.Condition
+		noPatch            bool
+	}{
+		{
+			name: "nil conditions",
+			gwClass: gwv1.GatewayClass{
+				ObjectMeta: v1.ObjectMeta{
+					Name: "gwclass",
+				},
+			},
+		},
+		{
+			name: "no conditions",
+			gwClass: gwv1.GatewayClass{
+				ObjectMeta: v1.ObjectMeta{
+					Name: "gwclass",
+				},
+				Status: gwv1.GatewayClassStatus{
+					Conditions: make([]v1.Condition, 0),
+				},
+			},
+		},
+		{
+			name:    "flip condition to true",
+			status:  metav1.ConditionTrue,
+			reason:  "flip to true",
+			message: "test message",
+			gwClass: gwv1.GatewayClass{
+				ObjectMeta: v1.ObjectMeta{
+					Name:       "gwclass-flip-true",
+					Generation: 100,
+				},
+				Status: gwv1.GatewayClassStatus{
+					Conditions: []v1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:               string(gwv1.GatewayClassReasonAccepted),
+							Status:             metav1.ConditionFalse,
+							ObservedGeneration: 1000,
+							Reason:             "",
+							Message:            "",
+						},
+					},
+				},
+			},
+			expectedConditions: []v1.Condition{
+				{
+					Type:               "other condition",
+					Status:             metav1.ConditionTrue,
+					ObservedGeneration: 1000,
+					Reason:             "other reason",
+					Message:            "other message",
+				},
+				{
+					Type:               string(gwv1.GatewayClassReasonAccepted),
+					Status:             metav1.ConditionTrue,
+					ObservedGeneration: 100,
+					Reason:             "flip to true",
+					Message:            "test message",
+				},
+			},
+		},
+		{
+			name:    "flip condition to false",
+			status:  metav1.ConditionFalse,
+			reason:  "flip to false",
+			message: "test message",
+			gwClass: gwv1.GatewayClass{
+				ObjectMeta: v1.ObjectMeta{
+					Name:       "gwclass-flip",
+					Generation: 100,
+				},
+				Status: gwv1.GatewayClassStatus{
+					Conditions: []v1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:               string(gwv1.GatewayClassReasonAccepted),
+							Status:             metav1.ConditionFalse,
+							ObservedGeneration: 1000,
+							Reason:             "",
+							Message:            "",
+						},
+					},
+				},
+			},
+			expectedConditions: []v1.Condition{
+				{
+					Type:               "other condition",
+					Status:             metav1.ConditionTrue,
+					ObservedGeneration: 1000,
+					Reason:             "other reason",
+					Message:            "other message",
+				},
+				{
+					Type:               string(gwv1.GatewayClassReasonAccepted),
+					Status:             metav1.ConditionFalse,
+					ObservedGeneration: 100,
+					Reason:             "flip to false",
+					Message:            "test message",
+				},
+			},
+		},
+		{
+			name:    "no change results in no patch",
+			status:  metav1.ConditionFalse,
+			reason:  "reason",
+			message: "msg",
+			gwClass: gwv1.GatewayClass{
+				ObjectMeta: v1.ObjectMeta{
+					Name:       "gwclass-flip",
+					Generation: 100,
+				},
+				Status: gwv1.GatewayClassStatus{
+					Conditions: []v1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:               string(gwv1.GatewayClassReasonAccepted),
+							Status:             metav1.ConditionFalse,
+							ObservedGeneration: 1000,
+							Reason:             "reason",
+							Message:            "msg",
+						},
+					},
+				},
+			},
+			expectedConditions: []v1.Condition{
+				{
+					Type:               "other condition",
+					Status:             metav1.ConditionTrue,
+					ObservedGeneration: 1000,
+					Reason:             "other reason",
+					Message:            "other message",
+				},
+				{
+					Type:               string(gwv1.GatewayClassReasonAccepted),
+					Status:             metav1.ConditionFalse,
+					ObservedGeneration: 1000,
+					Reason:             "reason",
+					Message:            "msg",
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			mockClient := testutils.GenerateTestClient()
+			err := mockClient.Create(context.Background(), &tc.gwClass)
+			assert.NoError(t, err)
+			time.Sleep(1 * time.Second)
+
+			stored := &gwv1.GatewayClass{}
+			err = mockClient.Get(context.Background(), k8s.NamespacedName(&tc.gwClass), stored)
+			assert.NoError(t, err)
+
+			err = updateGatewayClassAcceptedCondition(context.Background(), mockClient, tc.gwClass.DeepCopy(), tc.status, tc.reason, tc.message)
+			assert.NoError(t, err)
+			stored = &gwv1.GatewayClass{}
+			err = mockClient.Get(context.Background(), k8s.NamespacedName(&tc.gwClass), stored)
+			assert.NoError(t, err)
+
+			fixedTime := metav1.NewTime(time.Now())
+
+			// In order to use equals(), we need to make the time fields are fixed.
+			if tc.expectedConditions != nil {
+				for i := range tc.expectedConditions {
+					tmp := &tc.expectedConditions[i]
+					tmp.LastTransitionTime = fixedTime
+				}
+			}
+
+			if stored.Status.Conditions != nil {
+				for i := range stored.Status.Conditions {
+					tmp := &stored.Status.Conditions[i]
+					tmp.LastTransitionTime = fixedTime
+				}
+			}
+
+			assert.Equal(t, tc.expectedConditions, stored.Status.Conditions)
+		})
+	}
+}
+
+func Test_prepareGatewayConditionUpdate(t *testing.T) {
+
+	longString := ""
+	for i := 0; i < 50000; i++ {
+		longString = fmt.Sprintf("%s%s", longString, "a")
+	}
+	truncatedString := ""
+	for i := 0; i < 32700; i++ {
+		truncatedString = fmt.Sprintf("%s%s", truncatedString, "a")
+	}
+	truncatedString = fmt.Sprintf("%s...", truncatedString)
+
+	testCases := []struct {
+		name                string
+		gw                  gwv1.Gateway
+		targetConditionType string
+		newStatus           metav1.ConditionStatus
+		reason              string
+		message             string
+
+		expectedGw gwv1.Gateway
+		expected   bool
+	}{
+		{
+			name: "target condition not found",
+			gw: gwv1.Gateway{
+				Status: gwv1.GatewayStatus{
+					Conditions: []metav1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:               "other condition2",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1001,
+							Reason:             "other reason2",
+							Message:            "other message2",
+						},
+					},
+				},
+			},
+			expectedGw: gwv1.Gateway{
+				Status: gwv1.GatewayStatus{
+					Conditions: []metav1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:               "other condition2",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1001,
+							Reason:             "other reason2",
+							Message:            "other message2",
+						},
+					},
+				},
+			},
+			targetConditionType: string(gwv1.GatewayConditionAccepted),
+			newStatus:           metav1.ConditionTrue,
+			reason:              "other reason",
+			message:             "other message",
+		},
+		{
+			name: "target condition found",
+			gw: gwv1.Gateway{
+				ObjectMeta: metav1.ObjectMeta{
+					Generation: 50,
+				},
+				Status: gwv1.GatewayStatus{
+					Conditions: []metav1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:    string(gwv1.GatewayConditionAccepted),
+							Status:  metav1.ConditionFalse,
+							Reason:  "other reason",
+							Message: "other message",
+						},
+						{
+							Type:               "other condition2",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1001,
+							Reason:             "other reason2",
+							Message:            "other message2",
+						},
+					},
+				},
+			},
+			expectedGw: gwv1.Gateway{
+				ObjectMeta: metav1.ObjectMeta{
+					Generation: 50,
+				},
+				Status: gwv1.GatewayStatus{
+					Conditions: []metav1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:               string(gwv1.GatewayConditionAccepted),
+							Status:             metav1.ConditionTrue,
+							Reason:             "new reason",
+							Message:            "new message",
+							ObservedGeneration: 50,
+						},
+						{
+							Type:               "other condition2",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1001,
+							Reason:             "other reason2",
+							Message:            "other message2",
+						},
+					},
+				},
+			},
+			targetConditionType: string(gwv1.GatewayConditionAccepted),
+			newStatus:           metav1.ConditionTrue,
+			reason:              "new reason",
+			message:             "new message",
+			expected:            true,
+		},
+		{
+			name: "target condition found - long message truncated",
+			gw: gwv1.Gateway{
+				ObjectMeta: metav1.ObjectMeta{
+					Generation: 50,
+				},
+				Status: gwv1.GatewayStatus{
+					Conditions: []metav1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:    string(gwv1.GatewayConditionAccepted),
+							Status:  metav1.ConditionFalse,
+							Reason:  "other reason",
+							Message: "other message",
+						},
+						{
+							Type:               "other condition2",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1001,
+							Reason:             "other reason2",
+							Message:            "other message2",
+						},
+					},
+				},
+			},
+			expectedGw: gwv1.Gateway{
+				ObjectMeta: metav1.ObjectMeta{
+					Generation: 50,
+				},
+				Status: gwv1.GatewayStatus{
+					Conditions: []metav1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:               string(gwv1.GatewayConditionAccepted),
+							Status:             metav1.ConditionTrue,
+							Reason:             "new reason",
+							Message:            truncatedString,
+							ObservedGeneration: 50,
+						},
+						{
+							Type:               "other condition2",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1001,
+							Reason:             "other reason2",
+							Message:            "other message2",
+						},
+					},
+				},
+			},
+			targetConditionType: string(gwv1.GatewayConditionAccepted),
+			newStatus:           metav1.ConditionTrue,
+			reason:              "new reason",
+			message:             longString,
+			expected:            true,
+		},
+		{
+			name: "target condition found already correct",
+			gw: gwv1.Gateway{
+				ObjectMeta: metav1.ObjectMeta{
+					Generation: 50,
+				},
+				Status: gwv1.GatewayStatus{
+					Conditions: []metav1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:               string(gwv1.GatewayConditionAccepted),
+							Status:             metav1.ConditionTrue,
+							Reason:             "new reason",
+							Message:            "new message",
+							ObservedGeneration: 1000,
+						},
+						{
+							Type:               "other condition2",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1001,
+							Reason:             "other reason2",
+							Message:            "other message2",
+						},
+					},
+				},
+			},
+			expectedGw: gwv1.Gateway{
+				ObjectMeta: metav1.ObjectMeta{
+					Generation: 50,
+				},
+				Status: gwv1.GatewayStatus{
+					Conditions: []metav1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:               string(gwv1.GatewayConditionAccepted),
+							Status:             metav1.ConditionTrue,
+							Reason:             "new reason",
+							Message:            "new message",
+							ObservedGeneration: 1000,
+						},
+						{
+							Type:               "other condition2",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1001,
+							Reason:             "other reason2",
+							Message:            "other message2",
+						},
+					},
+				},
+			},
+			targetConditionType: string(gwv1.GatewayConditionAccepted),
+			newStatus:           metav1.ConditionTrue,
+			reason:              "new reason",
+			message:             "new message",
+		},
+		{
+			name: "target condition found - long message truncated",
+			gw: gwv1.Gateway{
+				ObjectMeta: metav1.ObjectMeta{
+					Generation: 50,
+				},
+				Status: gwv1.GatewayStatus{
+					Conditions: []metav1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:               string(gwv1.GatewayConditionAccepted),
+							Status:             metav1.ConditionTrue,
+							Reason:             "other reason",
+							Message:            truncatedString,
+							ObservedGeneration: 1000,
+						},
+						{
+							Type:               "other condition2",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1001,
+							Reason:             "other reason2",
+							Message:            "other message2",
+						},
+					},
+				},
+			},
+			expectedGw: gwv1.Gateway{
+				ObjectMeta: metav1.ObjectMeta{
+					Generation: 50,
+				},
+				Status: gwv1.GatewayStatus{
+					Conditions: []metav1.Condition{
+						{
+							Type:               "other condition",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1000,
+							Reason:             "other reason",
+							Message:            "other message",
+						},
+						{
+							Type:               string(gwv1.GatewayConditionAccepted),
+							Status:             metav1.ConditionTrue,
+							Reason:             "other reason",
+							Message:            truncatedString,
+							ObservedGeneration: 1000,
+						},
+						{
+							Type:               "other condition2",
+							Status:             metav1.ConditionTrue,
+							ObservedGeneration: 1001,
+							Reason:             "other reason2",
+							Message:            "other message2",
+						},
+					},
+				},
+			},
+			targetConditionType: string(gwv1.GatewayConditionAccepted),
+			newStatus:           metav1.ConditionTrue,
+			reason:              "other reason",
+			message:             longString,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			res := prepareGatewayConditionUpdate(&tc.gw, tc.targetConditionType, tc.newStatus, tc.reason, tc.message)
+
+			// In order to use equals(), we need to make the time fields are fixed.
+			fixedTime := metav1.NewTime(time.Now())
+			if tc.gw.Status.Conditions != nil {
+				for i := range tc.gw.Status.Conditions {
+					tmp := &tc.gw.Status.Conditions[i]
+					tmp.LastTransitionTime = fixedTime
+				}
+			}
+
+			if tc.expectedGw.Status.Conditions != nil {
+				for i := range tc.expectedGw.Status.Conditions {
+					tmp := &tc.expectedGw.Status.Conditions[i]
+					tmp.LastTransitionTime = fixedTime
+				}
+			}
+
+			assert.Equal(t, tc.expected, res)
+			assert.Equal(t, tc.expectedGw, tc.gw)
+		})
+	}
+}
+
+func Test_resolveLoadBalancerConfig(t *testing.T) {
+	testCases := []struct {
+		name      string
+		reference *gwv1.ParametersReference
+		lbConf    *elbv2gw.LoadBalancerConfiguration
+		expectErr bool
+	}{
+		{
+			name: "lb conf found",
+			reference: &gwv1.ParametersReference{
+				Name:      "foo",
+				Namespace: (*gwv1.Namespace)(awssdk.String("ns")),
+			},
+			lbConf: &elbv2gw.LoadBalancerConfiguration{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "foo",
+					Namespace: "ns",
+				},
+			},
+		},
+		{
+			name: "no lb conf",
+			reference: &gwv1.ParametersReference{
+				Name:      "foo",
+				Namespace: (*gwv1.Namespace)(awssdk.String("ns")),
+			},
+			expectErr: true,
+		},
+		{
+			name: "no namespace",
+			reference: &gwv1.ParametersReference{
+				Name: "foo",
+			},
+			expectErr: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			mockClient := testutils.GenerateTestClient()
+			if tc.lbConf != nil {
+				err := mockClient.Create(context.Background(), tc.lbConf)
+				assert.NoError(t, err)
+			}
+			time.Sleep(1 * time.Second)
+
+			res, err := resolveLoadBalancerConfig(context.Background(), mockClient, tc.reference)
+			if tc.expectErr {
+				assert.Error(t, err)
+				return
+			}
+			assert.NoError(t, err)
+			assert.Equal(t, tc.lbConf, res)
+		})
+	}
+}
+
 func Test_generateRouteList(t *testing.T) {
 	testCases := []struct {
 		name     string
diff --git a/pkg/deploy/elbv2/load_balancer_manager.go b/pkg/deploy/elbv2/load_balancer_manager.go
index 093991679c..447ccabefc 100644
--- a/pkg/deploy/elbv2/load_balancer_manager.go
+++ b/pkg/deploy/elbv2/load_balancer_manager.go
@@ -391,8 +391,9 @@ func buildSDKSubnetMapping(modelSubnetMapping elbv2model.SubnetMapping) elbv2typ
 
 func buildResLoadBalancerStatus(sdkLB LoadBalancerWithTags) elbv2model.LoadBalancerStatus {
 	return elbv2model.LoadBalancerStatus{
-		LoadBalancerARN: awssdk.ToString(sdkLB.LoadBalancer.LoadBalancerArn),
-		DNSName:         awssdk.ToString(sdkLB.LoadBalancer.DNSName),
+		LoadBalancerARN:   awssdk.ToString(sdkLB.LoadBalancer.LoadBalancerArn),
+		DNSName:           awssdk.ToString(sdkLB.LoadBalancer.DNSName),
+		ProvisioningState: sdkLB.LoadBalancer.State,
 	}
 }
 
diff --git a/pkg/deploy/elbv2/load_balancer_manager_test.go b/pkg/deploy/elbv2/load_balancer_manager_test.go
index 02cdb9b6e1..0064b5b879 100644
--- a/pkg/deploy/elbv2/load_balancer_manager_test.go
+++ b/pkg/deploy/elbv2/load_balancer_manager_test.go
@@ -429,12 +429,20 @@ func Test_buildResLoadBalancerStatus(t *testing.T) {
 					LoadBalancer: &elbv2types.LoadBalancer{
 						LoadBalancerArn: awssdk.String("my-arn"),
 						DNSName:         awssdk.String("www.example.com"),
+						State: &elbv2types.LoadBalancerState{
+							Code:   elbv2types.LoadBalancerStateEnumProvisioning,
+							Reason: awssdk.String("foo"),
+						},
 					},
 				},
 			},
 			want: elbv2model.LoadBalancerStatus{
 				LoadBalancerARN: "my-arn",
 				DNSName:         "www.example.com",
+				ProvisioningState: &elbv2types.LoadBalancerState{
+					Code:   elbv2types.LoadBalancerStateEnumProvisioning,
+					Reason: awssdk.String("foo"),
+				},
 			},
 		},
 	}
diff --git a/pkg/gateway/model/model_build_target_group.go b/pkg/gateway/model/model_build_target_group.go
index 11d80d1828..a6b990ed9d 100644
--- a/pkg/gateway/model/model_build_target_group.go
+++ b/pkg/gateway/model/model_build_target_group.go
@@ -31,7 +31,7 @@ type targetGroupBuilder interface {
 	buildTargetGroup(stack core.Stack,
 		gw *gwv1.Gateway, lbConfig elbv2gw.LoadBalancerConfiguration, lbIPType elbv2model.IPAddressType, routeDescriptor routeutils.RouteDescriptor, backend routeutils.Backend, backendSGIDToken core.StringToken) (*elbv2model.TargetGroup, error)
 	buildTargetGroupSpec(gw *gwv1.Gateway, route routeutils.RouteDescriptor, lbConfig elbv2gw.LoadBalancerConfiguration, lbIPType elbv2model.IPAddressType, backend routeutils.Backend, targetGroupProps *elbv2gw.TargetGroupProps) (elbv2model.TargetGroupSpec, error)
-	buildTargetGroupBindingSpec(tgProps *elbv2gw.TargetGroupProps, tgSpec elbv2model.TargetGroupSpec, nodeSelector *metav1.LabelSelector, backend routeutils.Backend, backendSGIDToken core.StringToken) elbv2model.TargetGroupBindingResourceSpec
+	buildTargetGroupBindingSpec(gw *gwv1.Gateway, tgProps *elbv2gw.TargetGroupProps, tgSpec elbv2model.TargetGroupSpec, nodeSelector *metav1.LabelSelector, backend routeutils.Backend, backendSGIDToken core.StringToken) elbv2model.TargetGroupBindingResourceSpec
 }
 
 type targetGroupBuilderImpl struct {
@@ -108,7 +108,7 @@ func (t *targetGroupBuilderImpl) buildTargetGroup(stack core.Stack,
 		return nil, err
 	}
 	nodeSelector := t.buildTargetGroupBindingNodeSelector(targetGroupProps, tgSpec.TargetType)
-	bindingSpec := t.buildTargetGroupBindingSpec(targetGroupProps, tgSpec, nodeSelector, backend, backendSGIDToken)
+	bindingSpec := t.buildTargetGroupBindingSpec(gw, targetGroupProps, tgSpec, nodeSelector, backend, backendSGIDToken)
 
 	tgOut := buildTargetGroupOutput{
 		targetGroupSpec: tgSpec,
@@ -130,7 +130,7 @@ func (builder *targetGroupBuilderImpl) getTargetGroupProps(routeDescriptor route
 	return targetGroupProps
 }
 
-func (builder *targetGroupBuilderImpl) buildTargetGroupBindingSpec(tgProps *elbv2gw.TargetGroupProps, tgSpec elbv2model.TargetGroupSpec, nodeSelector *metav1.LabelSelector, backend routeutils.Backend, backendSGIDToken core.StringToken) elbv2model.TargetGroupBindingResourceSpec {
+func (builder *targetGroupBuilderImpl) buildTargetGroupBindingSpec(gw *gwv1.Gateway, tgProps *elbv2gw.TargetGroupProps, tgSpec elbv2model.TargetGroupSpec, nodeSelector *metav1.LabelSelector, backend routeutils.Backend, backendSGIDToken core.StringToken) elbv2model.TargetGroupBindingResourceSpec {
 	targetType := elbv2api.TargetType(tgSpec.TargetType)
 	targetPort := backend.ServicePort.TargetPort
 	if targetType == elbv2api.TargetTypeInstance {
@@ -140,11 +140,30 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupBindingSpec(tgProps *elbv
 
 	multiClusterEnabled := builder.buildTargetGroupBindingMultiClusterFlag(tgProps)
 
+	annotations := make(map[string]string)
+	labels := make(map[string]string)
+
+	if gw != nil && gw.Spec.Infrastructure != nil {
+		if gw.Spec.Infrastructure.Annotations != nil {
+			for k, v := range gw.Spec.Infrastructure.Annotations {
+				annotations[string(k)] = string(v)
+			}
+		}
+
+		if gw.Spec.Infrastructure.Labels != nil {
+			for k, v := range gw.Spec.Infrastructure.Labels {
+				labels[string(k)] = string(v)
+			}
+		}
+	}
+
 	return elbv2model.TargetGroupBindingResourceSpec{
 		Template: elbv2model.TargetGroupBindingTemplate{
 			ObjectMeta: metav1.ObjectMeta{
-				Namespace: backend.Service.Namespace,
-				Name:      tgSpec.Name,
+				Namespace:   backend.Service.Namespace,
+				Name:        tgSpec.Name,
+				Annotations: annotations,
+				Labels:      labels,
 			},
 			Spec: elbv2model.TargetGroupBindingSpec{
 				TargetGroupARN: nil, // This should get filled in later!
diff --git a/pkg/gateway/model/model_build_target_group_test.go b/pkg/gateway/model/model_build_target_group_test.go
index 4d57f5ae13..35663c5b03 100644
--- a/pkg/gateway/model/model_build_target_group_test.go
+++ b/pkg/gateway/model/model_build_target_group_test.go
@@ -360,8 +360,10 @@ func Test_buildTargetGroupBindingSpec(t *testing.T) {
 			expectedTgBindingSpec: elbv2model.TargetGroupBindingResourceSpec{
 				Template: elbv2model.TargetGroupBindingTemplate{
 					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "my-svc-ns",
-						Name:      "k8s-myrouten-myroute-d02da2803b",
+						Namespace:   "my-svc-ns",
+						Name:        "k8s-myrouten-myroute-d02da2803b",
+						Annotations: make(map[string]string),
+						Labels:      make(map[string]string),
 					},
 					Spec: elbv2model.TargetGroupBindingSpec{
 						TargetType: &instanceType,
@@ -437,8 +439,10 @@ func Test_buildTargetGroupBindingSpec(t *testing.T) {
 			expectedTgBindingSpec: elbv2model.TargetGroupBindingResourceSpec{
 				Template: elbv2model.TargetGroupBindingTemplate{
 					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "my-svc-ns",
-						Name:      "k8s-myrouten-myroute-d146029dfb",
+						Namespace:   "my-svc-ns",
+						Name:        "k8s-myrouten-myroute-d146029dfb",
+						Annotations: make(map[string]string),
+						Labels:      make(map[string]string),
 					},
 					Spec: elbv2model.TargetGroupBindingSpec{
 						TargetType: &instanceType,
@@ -509,8 +513,10 @@ func Test_buildTargetGroupBindingSpec(t *testing.T) {
 			expectedTgBindingSpec: elbv2model.TargetGroupBindingResourceSpec{
 				Template: elbv2model.TargetGroupBindingTemplate{
 					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "my-svc-ns",
-						Name:      "k8s-myrouten-myroute-d9d6c4e6eb",
+						Namespace:   "my-svc-ns",
+						Name:        "k8s-myrouten-myroute-d9d6c4e6eb",
+						Annotations: make(map[string]string),
+						Labels:      make(map[string]string),
 					},
 					Spec: elbv2model.TargetGroupBindingSpec{
 						TargetType: &ipType,
@@ -583,11 +589,106 @@ func Test_buildTargetGroupBindingSpec(t *testing.T) {
 				TargetGroupAttributes: make([]elbv2model.TargetGroupAttribute, 0),
 				Tags:                  make(map[string]string),
 			},
+			expectedTgBindingSpec: elbv2model.TargetGroupBindingResourceSpec{
+				Template: elbv2model.TargetGroupBindingTemplate{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace:   "my-svc-ns",
+						Name:        "k8s-myrouten-myroute-400113e816",
+						Annotations: make(map[string]string),
+						Labels:      make(map[string]string),
+					},
+					Spec: elbv2model.TargetGroupBindingSpec{
+						TargetType: &ipType,
+						ServiceRef: elbv2api.ServiceReference{
+							Name: "my-svc",
+							Port: intstr.FromInt32(80), // TODO - Figure out why this port is added and not the node port.
+						},
+						IPAddressType: elbv2api.TargetGroupIPAddressType(elbv2model.IPAddressTypeIPV4),
+						VpcID:         "vpc-xxx",
+					},
+				},
+			},
+		},
+		{
+			name:                     "no tg config - ip - alb - add infra annotations / labels",
+			tags:                     make(map[string]string),
+			lbType:                   elbv2model.LoadBalancerTypeApplication,
+			disableRestrictedSGRules: false,
+			defaultTargetType:        string(elbv2model.TargetTypeIP),
+			gateway: &gwv1.Gateway{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "my-gw-ns",
+					Name:      "my-gw",
+				},
+				Spec: gwv1.GatewaySpec{
+					Infrastructure: &gwv1.GatewayInfrastructure{
+						Annotations: map[gwv1.AnnotationKey]gwv1.AnnotationValue{
+							"foo": "bar",
+						},
+						Labels: map[gwv1.LabelKey]gwv1.LabelValue{
+							"labelfoo": "labelbar",
+						},
+					},
+				},
+			},
+			route: &routeutils.MockRoute{
+				Kind:      routeutils.HTTPRouteKind,
+				Name:      "my-route",
+				Namespace: "my-route-ns",
+			},
+			backend: routeutils.Backend{
+				Service: &corev1.Service{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "my-svc-ns",
+						Name:      "my-svc",
+					},
+				},
+				ServicePort: &corev1.ServicePort{
+					Protocol: corev1.ProtocolTCP,
+					Port:     80,
+					TargetPort: intstr.IntOrString{
+						IntVal: 80,
+						Type:   intstr.Int,
+					},
+					NodePort: 8080,
+				},
+			},
+			expectedTgSpec: elbv2model.TargetGroupSpec{
+				Name:            "k8s-myrouten-myroute-400113e816",
+				TargetType:      elbv2model.TargetTypeIP,
+				Port:            awssdk.Int32(80),
+				Protocol:        elbv2model.ProtocolHTTP,
+				ProtocolVersion: &http1,
+				IPAddressType:   elbv2model.TargetGroupIPAddressTypeIPv4,
+				HealthCheckConfig: &elbv2model.TargetGroupHealthCheckConfig{
+					Port: &intstr.IntOrString{
+						StrVal: shared_constants.HealthCheckPortTrafficPort,
+						Type:   intstr.String,
+					},
+					Path: awssdk.String("/"),
+					Matcher: &elbv2model.HealthCheckMatcher{
+						HTTPCode: awssdk.String("200-399"),
+					},
+					Protocol:                elbv2model.ProtocolHTTP,
+					IntervalSeconds:         awssdk.Int32(15),
+					TimeoutSeconds:          awssdk.Int32(5),
+					HealthyThresholdCount:   awssdk.Int32(3),
+					UnhealthyThresholdCount: awssdk.Int32(3),
+				},
+				TargetGroupAttributes: make([]elbv2model.TargetGroupAttribute, 0),
+				Tags:                  make(map[string]string),
+			},
 			expectedTgBindingSpec: elbv2model.TargetGroupBindingResourceSpec{
 				Template: elbv2model.TargetGroupBindingTemplate{
 					ObjectMeta: metav1.ObjectMeta{
 						Namespace: "my-svc-ns",
 						Name:      "k8s-myrouten-myroute-400113e816",
+						Annotations: map[string]string{
+							"foo": "bar",
+						},
+						Labels: map[string]string{
+							"labelfoo": "labelbar",
+						},
 					},
 					Spec: elbv2model.TargetGroupBindingSpec{
 						TargetType: &ipType,
@@ -613,7 +714,7 @@ func Test_buildTargetGroupBindingSpec(t *testing.T) {
 
 			builder := newTargetGroupBuilder("my-cluster", "vpc-xxx", tagger, tc.lbType, tc.disableRestrictedSGRules, tc.defaultTargetType)
 
-			out := builder.buildTargetGroupBindingSpec(nil, tc.expectedTgSpec, nil, tc.backend, nil)
+			out := builder.buildTargetGroupBindingSpec(tc.gateway, nil, tc.expectedTgSpec, nil, tc.backend, nil)
 
 			assert.Equal(t, tc.expectedTgBindingSpec, out)
 		})
diff --git a/pkg/gateway/routeutils/backend.go b/pkg/gateway/routeutils/backend.go
index 31816272e9..54f2c44f48 100644
--- a/pkg/gateway/routeutils/backend.go
+++ b/pkg/gateway/routeutils/backend.go
@@ -31,7 +31,8 @@ func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpeci
 
 	// We only support references of type service.
 	if backendRef.Kind != nil && *backendRef.Kind != "Service" {
-		return nil, nil
+		initialErrorMessage := "Backend Ref must be of kind 'Service'"
+		return nil, wrapError(errors.Errorf("%s", generateInvalidMessageWithRouteDetails(initialErrorMessage, routeKind, routeIdentifier)), gwv1.GatewayReasonListenersNotValid, gwv1.RouteReasonInvalidKind)
 	}
 
 	if backendRef.Weight != nil && *backendRef.Weight == 0 {
@@ -39,7 +40,8 @@ func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpeci
 	}
 
 	if backendRef.Port == nil {
-		return nil, errors.Errorf("Missing port in backend reference")
+		initialErrorMessage := "Port is required"
+		return nil, wrapError(errors.Errorf("%s", generateInvalidMessageWithRouteDetails(initialErrorMessage, routeKind, routeIdentifier)), gwv1.GatewayReasonListenersNotValid, gwv1.RouteReasonUnsupportedValue)
 	}
 
 	var svcNamespace string
@@ -58,6 +60,7 @@ func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpeci
 	if svcNamespace != routeIdentifier.Namespace {
 		allowed, err := referenceGrantCheck(ctx, k8sClient, svcIdentifier, routeIdentifier, routeKind)
 		if err != nil {
+			// Currently, this API only fails for a k8s related error message, hence no status update.
 			return nil, errors.Wrapf(err, "Unable to perform reference grant check")
 		}
 
@@ -72,9 +75,17 @@ func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpeci
 	svc := &corev1.Service{}
 	err := k8sClient.Get(ctx, svcIdentifier, svc)
 	if err != nil {
+
+		convertToNotFoundError := client.IgnoreNotFound(err)
+
+		if convertToNotFoundError == nil {
+			// Svc not found, post an updated status.
+			initialErrorMessage := fmt.Sprintf("Service (%s:%s) not found)", svcIdentifier.Namespace, svcIdentifier.Name)
+			return nil, wrapError(errors.Errorf("%s", generateInvalidMessageWithRouteDetails(initialErrorMessage, routeKind, routeIdentifier)), gwv1.GatewayReasonListenersNotValid, gwv1.RouteReasonBackendNotFound)
+		}
+		// Otherwise, general error. No need for status update.
 		return nil, errors.Wrap(err, fmt.Sprintf("Unable to fetch svc object %+v", svcIdentifier))
 	}
-
 	var servicePort *corev1.ServicePort
 
 	for _, svcPort := range svc.Spec.Ports {
@@ -87,11 +98,13 @@ func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpeci
 	tgConfig, err := lookUpTargetGroupConfiguration(ctx, k8sClient, k8s.NamespacedName(svc))
 
 	if err != nil {
+		// As of right now, this error can only be thrown because of a k8s api error hence no status update.
 		return nil, errors.Wrap(err, fmt.Sprintf("Unable to fetch tg config object"))
 	}
 
 	if servicePort == nil {
-		return nil, errors.Errorf("Unable to find service port for port %d", *backendRef.Port)
+		initialErrorMessage := fmt.Sprintf("Unable to find service port for port %d", *backendRef.Port)
+		return nil, wrapError(errors.Errorf("%s", generateInvalidMessageWithRouteDetails(initialErrorMessage, routeKind, routeIdentifier)), gwv1.GatewayReasonListenersNotValid, gwv1.RouteReasonBackendNotFound)
 	}
 
 	// Weight specifies the proportion of requests forwarded to the referenced
diff --git a/pkg/gateway/routeutils/backend_test.go b/pkg/gateway/routeutils/backend_test.go
index 1ec9b4f4f8..d870819ce6 100644
--- a/pkg/gateway/routeutils/backend_test.go
+++ b/pkg/gateway/routeutils/backend_test.go
@@ -283,7 +283,7 @@ func TestCommonBackendLoader(t *testing.T) {
 					Port:      portConverter(80),
 				},
 			},
-			expectNoResult: true,
+			expectErr: true,
 		},
 		{
 			name: "missing port in backend ref should result in an error",
diff --git a/pkg/gateway/routeutils/grpc.go b/pkg/gateway/routeutils/grpc.go
index 8a1586d76a..83736ee7f8 100644
--- a/pkg/gateway/routeutils/grpc.go
+++ b/pkg/gateway/routeutils/grpc.go
@@ -128,5 +128,5 @@ func ListGRPCRoutes(context context.Context, k8sClient client.Client) ([]preLoad
 		result = append(result, convertGRPCRoute(item))
 	}
 
-	return result, err
+	return result, nil
 }
diff --git a/pkg/gateway/routeutils/http.go b/pkg/gateway/routeutils/http.go
index a5b2b9660b..b2e137ab9d 100644
--- a/pkg/gateway/routeutils/http.go
+++ b/pkg/gateway/routeutils/http.go
@@ -128,5 +128,5 @@ func ListHTTPRoutes(context context.Context, k8sClient client.Client) ([]preLoad
 		result = append(result, convertHTTPRoute(item))
 	}
 
-	return result, err
+	return result, nil
 }
diff --git a/pkg/gateway/routeutils/listener_attachment_helper.go b/pkg/gateway/routeutils/listener_attachment_helper.go
index fd1de8bdac..4bb55d98e3 100644
--- a/pkg/gateway/routeutils/listener_attachment_helper.go
+++ b/pkg/gateway/routeutils/listener_attachment_helper.go
@@ -2,7 +2,9 @@ package routeutils
 
 import (
 	"context"
+	"fmt"
 	"github.com/go-logr/logr"
+	"github.com/pkg/errors"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	gwv1 "sigs.k8s.io/gateway-api/apis/v1"
@@ -145,8 +147,10 @@ func (attachmentHelper *listenerAttachmentHelperImpl) hostnameCheck(listener gwv
 	isListenerHostnameValid, err := IsHostNameInValidFormat(string(*listener.Hostname))
 	if err != nil {
 		attachmentHelper.logger.Error(err, "listener hostname is not valid", "listener", listener.Name, "hostname", *listener.Hostname)
-		return false, err
+		initialErrorMessage := fmt.Sprintf("listener hostname %s is not valid (listener name %s)", listener.Name, *listener.Hostname)
+		return false, wrapError(errors.Errorf("%s", initialErrorMessage), gwv1.GatewayReasonListenersNotValid, gwv1.RouteReasonUnsupportedValue)
 	}
+
 	if !isListenerHostnameValid {
 		return false, nil
 	}
diff --git a/pkg/gateway/routeutils/loader_error.go b/pkg/gateway/routeutils/loader_error.go
new file mode 100644
index 0000000000..86c2713010
--- /dev/null
+++ b/pkg/gateway/routeutils/loader_error.go
@@ -0,0 +1,42 @@
+package routeutils
+
+import (
+	gwv1 "sigs.k8s.io/gateway-api/apis/v1"
+)
+
+type LoaderError interface {
+	Error() string
+	GetRawError() error
+	GetGatewayReason() gwv1.GatewayConditionReason
+	GetRouteReason() gwv1.RouteConditionReason
+}
+
+type loaderErrorImpl struct {
+	resolvedGatewayCondition gwv1.GatewayConditionReason
+	resolvedRouteCondition   gwv1.RouteConditionReason
+	underlyingErr            error
+}
+
+func wrapError(underlyingErr error, gatewayCondition gwv1.GatewayConditionReason, routeCondition gwv1.RouteConditionReason) LoaderError {
+	return &loaderErrorImpl{
+		underlyingErr:            underlyingErr,
+		resolvedGatewayCondition: gatewayCondition,
+		resolvedRouteCondition:   routeCondition,
+	}
+}
+
+func (e *loaderErrorImpl) GetRawError() error {
+	return e.underlyingErr
+}
+
+func (e *loaderErrorImpl) GetGatewayReason() gwv1.GatewayConditionReason {
+	return e.resolvedGatewayCondition
+}
+
+func (e *loaderErrorImpl) GetRouteReason() gwv1.RouteConditionReason {
+	return e.resolvedRouteCondition
+}
+
+func (e *loaderErrorImpl) Error() string {
+	return e.underlyingErr.Error()
+}
diff --git a/pkg/gateway/routeutils/namespace_selector.go b/pkg/gateway/routeutils/namespace_selector.go
index c54f1aeacb..6cbc226502 100644
--- a/pkg/gateway/routeutils/namespace_selector.go
+++ b/pkg/gateway/routeutils/namespace_selector.go
@@ -2,10 +2,12 @@ package routeutils
 
 import (
 	"context"
+	"github.com/pkg/errors"
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+	gwv1 "sigs.k8s.io/gateway-api/apis/v1"
 )
 
 // namespaceSelector is an internal utility
@@ -33,7 +35,7 @@ func (n *namespaceSelectorImpl) getNamespacesFromSelector(context context.Contex
 
 	convertedSelector, err := metav1.LabelSelectorAsSelector(selector)
 	if err != nil {
-		return nil, err
+		return nil, wrapError(errors.Wrapf(err, "Unable to parse selector %s", selector), gwv1.GatewayReasonListenersNotValid, gwv1.RouteReasonUnsupportedValue)
 	}
 
 	err = n.k8sClient.List(context, &namespaceList, client.MatchingLabelsSelector{Selector: convertedSelector})
diff --git a/pkg/gateway/routeutils/route_listener_mapper.go b/pkg/gateway/routeutils/route_listener_mapper.go
index 84adaebfb5..df423d3091 100644
--- a/pkg/gateway/routeutils/route_listener_mapper.go
+++ b/pkg/gateway/routeutils/route_listener_mapper.go
@@ -8,7 +8,7 @@ import (
 )
 
 // listenerToRouteMapper is an internal utility that will map a list of routes to the listeners of a gateway
-// if the gateway and/or route are incompatible, then route is discarded.
+// if the gateway and/or route are incompatible, then the route is discarded.
 type listenerToRouteMapper interface {
 	mapGatewayAndRoutes(context context.Context, gw gwv1.Gateway, routes []preLoadRouteDescriptor, deferredRouteReconciler RouteReconciler) (map[int][]preLoadRouteDescriptor, error)
 }
diff --git a/pkg/gateway/routeutils/tcp.go b/pkg/gateway/routeutils/tcp.go
index a34e79d468..ec3a6984bd 100644
--- a/pkg/gateway/routeutils/tcp.go
+++ b/pkg/gateway/routeutils/tcp.go
@@ -128,5 +128,5 @@ func ListTCPRoutes(context context.Context, k8sClient client.Client) ([]preLoadR
 	for _, item := range routeList.Items {
 		result = append(result, convertTCPRoute(item))
 	}
-	return result, err
+	return result, nil
 }
diff --git a/pkg/gateway/routeutils/tls.go b/pkg/gateway/routeutils/tls.go
index 911b18dfdf..fde47b24e6 100644
--- a/pkg/gateway/routeutils/tls.go
+++ b/pkg/gateway/routeutils/tls.go
@@ -127,5 +127,5 @@ func ListTLSRoutes(context context.Context, k8sClient client.Client) ([]preLoadR
 		result = append(result, convertTLSRoute(item))
 	}
 
-	return result, err
+	return result, nil
 }
diff --git a/pkg/gateway/routeutils/udp.go b/pkg/gateway/routeutils/udp.go
index fca6c9d124..3662015b20 100644
--- a/pkg/gateway/routeutils/udp.go
+++ b/pkg/gateway/routeutils/udp.go
@@ -126,5 +126,5 @@ func ListUDPRoutes(context context.Context, k8sClient client.Client) ([]preLoadR
 		result = append(result, convertUDPRoute(item))
 	}
 
-	return result, err
+	return result, nil
 }
diff --git a/pkg/gateway/routeutils/utils.go b/pkg/gateway/routeutils/utils.go
index 30dd2e22e3..537173b043 100644
--- a/pkg/gateway/routeutils/utils.go
+++ b/pkg/gateway/routeutils/utils.go
@@ -3,6 +3,7 @@ package routeutils
 import (
 	"context"
 	"fmt"
+	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"net"
@@ -13,24 +14,24 @@ import (
 // ListL4Routes retrieves all Layer 4 routes (TCP, UDP, TLS) from the cluster.
 func ListL4Routes(ctx context.Context, k8sClient client.Client) ([]preLoadRouteDescriptor, error) {
 	l4Routes := make([]preLoadRouteDescriptor, 0)
-	var routekinds []RouteKind
+	var failedRoutes []RouteKind
 	tcpRoutes, err := ListTCPRoutes(ctx, k8sClient)
 	if err != nil {
-		routekinds = append(routekinds, TCPRouteKind)
+		failedRoutes = append(failedRoutes, TCPRouteKind)
 	}
 	l4Routes = append(l4Routes, tcpRoutes...)
 	udpRoutes, err := ListUDPRoutes(ctx, k8sClient)
 	if err != nil {
-		routekinds = append(routekinds, UDPRouteKind)
+		failedRoutes = append(failedRoutes, UDPRouteKind)
 	}
 	l4Routes = append(l4Routes, udpRoutes...)
 	tlsRoutes, err := ListTLSRoutes(ctx, k8sClient)
 	if err != nil {
-		routekinds = append(routekinds, TLSRouteKind)
+		failedRoutes = append(failedRoutes, TLSRouteKind)
 	}
 	l4Routes = append(l4Routes, tlsRoutes...)
-	if len(routekinds) > 0 {
-		err = fmt.Errorf("failed to list L4 routes, %s", routekinds)
+	if len(failedRoutes) > 0 {
+		err = errors.Errorf("failed to list L4 routes, %s", failedRoutes)
 	}
 	return l4Routes, err
 }
@@ -38,19 +39,19 @@ func ListL4Routes(ctx context.Context, k8sClient client.Client) ([]preLoadRouteD
 // ListL7Routes retrieves all Layer 7 routes (HTTP, gRPC) from the cluster.
 func ListL7Routes(ctx context.Context, k8sClient client.Client) ([]preLoadRouteDescriptor, error) {
 	l7Routes := make([]preLoadRouteDescriptor, 0)
-	var routekinds []RouteKind
+	var failedRoutes []RouteKind
 	httpRoutes, err := ListHTTPRoutes(ctx, k8sClient)
 	if err != nil {
-		routekinds = append(routekinds, HTTPRouteKind)
+		failedRoutes = append(failedRoutes, HTTPRouteKind)
 	}
 	l7Routes = append(l7Routes, httpRoutes...)
 	grpcRoutes, err := ListGRPCRoutes(ctx, k8sClient)
 	if err != nil {
-		routekinds = append(routekinds, GRPCRouteKind)
+		failedRoutes = append(failedRoutes, GRPCRouteKind)
 	}
 	l7Routes = append(l7Routes, grpcRoutes...)
-	if len(routekinds) > 0 {
-		err = fmt.Errorf("failed to list L7 routes, %s", routekinds)
+	if len(failedRoutes) > 0 {
+		err = errors.Errorf("failed to list L7 routes, %s", failedRoutes)
 	}
 	return l7Routes, err
 }
@@ -160,3 +161,7 @@ func GetHostnamePrecedenceOrder(hostnameOne, hostnameTwo string) int {
 		}
 	}
 }
+
+func generateInvalidMessageWithRouteDetails(initialMessage string, routeKind RouteKind, routeIdentifier types.NamespacedName) string {
+	return fmt.Sprintf("%s. Invalid data can be found in route (%s, %s:%s)", initialMessage, routeKind, routeIdentifier.Namespace, routeIdentifier.Name)
+}
diff --git a/pkg/gateway/routeutils/utils_test.go b/pkg/gateway/routeutils/utils_test.go
index e17de4b0ad..49504bd2fb 100644
--- a/pkg/gateway/routeutils/utils_test.go
+++ b/pkg/gateway/routeutils/utils_test.go
@@ -163,7 +163,6 @@ func Test_ListL4Routes(t *testing.T) {
 				return k8sClient
 			},
 			expectedRoutes: 3,
-			expectedErr:    nil,
 		},
 		{
 			name: "Handles error in TCP routes",
@@ -188,7 +187,11 @@ func Test_ListL4Routes(t *testing.T) {
 			client := tt.mockSetup(ctrl)
 			routes, err := ListL4Routes(context.Background(), client)
 
-			assert.Equal(t, err, tt.expectedErr)
+			if tt.expectedErr == nil {
+				assert.NoError(t, err)
+			} else {
+				assert.Equal(t, tt.expectedErr.Error(), err.Error())
+			}
 			assert.Len(t, routes, tt.expectedRoutes)
 
 		})
@@ -293,7 +296,11 @@ func Test_ListL7Routes(t *testing.T) {
 			client := tt.mockSetup(ctrl)
 			routes, err := ListL7Routes(context.Background(), client)
 
-			assert.Equal(t, err, tt.expectedErr)
+			if tt.expectedErr == nil {
+				assert.NoError(t, err)
+			} else {
+				assert.Equal(t, tt.expectedErr.Error(), err.Error())
+			}
 			assert.Len(t, routes, tt.expectedRoutes)
 
 		})
diff --git a/pkg/model/elbv2/load_balancer.go b/pkg/model/elbv2/load_balancer.go
index 94cbc3f3e4..35f97c11a5 100644
--- a/pkg/model/elbv2/load_balancer.go
+++ b/pkg/model/elbv2/load_balancer.go
@@ -2,6 +2,7 @@ package elbv2
 
 import (
 	"context"
+	elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
 	"github.com/pkg/errors"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/model/core"
 )
@@ -212,4 +213,7 @@ type LoadBalancerStatus struct {
 
 	// The public DNS name of the load balancer.
 	DNSName string `json:"dnsName"`
+
+	// The current state of the load balancer (active, provisioning, etc)
+	ProvisioningState *elbv2types.LoadBalancerState `json:"provisioningState"`
 }
diff --git a/pkg/testutils/client_test_utils.go b/pkg/testutils/client_test_utils.go
index 6ced1c3f71..da390aef85 100644
--- a/pkg/testutils/client_test_utils.go
+++ b/pkg/testutils/client_test_utils.go
@@ -52,5 +52,5 @@ func GenerateTestClient() client.Client {
 	elbv2gw.AddToScheme(k8sSchema)
 	gwbeta1.AddToScheme(k8sSchema)
 
-	return testclient.NewClientBuilder().WithScheme(k8sSchema).Build()
+	return testclient.NewClientBuilder().WithStatusSubresource(&gwv1.GatewayClass{}).WithStatusSubresource(&gwv1.Gateway{}).WithScheme(k8sSchema).Build()
 }


Reviews:
user: login: shuqz
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T21:58:33Z
user: login: zac-nixon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T22:07:01Z
user: login: shuqz
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-28T00:19:59Z
user: login: zac-nixon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-28T14:16:36Z
user: login: zac-nixon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-28T14:16:52Z
user: login: zac-nixon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-28T14:20:50Z
user: login: zac-nixon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-28T14:21:11Z
user: login: zac-nixon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-28T14:22:48Z
user: login: shuqz
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-28T17:49:14Z
user: login: wweiwei-li
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-28T23:32:30Z
user: login: zac-nixon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T00:47:30Z
user: login: zac-nixon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T00:49:39Z
user: login: zac-nixon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T00:51:12Z
user: login: wweiwei-li
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T04:43:56Z
user: login: zac-nixon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T05:19:09Z
user: login: wweiwei-li
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-29T16:41:23Z

Review comments:
diff_hunk: @@ -27,19 +27,21 @@ type Backend struct {
 }
 
 // commonBackendLoader this function will load the services and target group configurations associated with this gateway backend.
-func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpecificBackend interface{}, backendRef gwv1.BackendRef, routeIdentifier types.NamespacedName, routeKind RouteKind) (*Backend, error) {
+func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpecificBackend interface{}, backendRef gwv1.BackendRef, routeIdentifier types.NamespacedName, routeKind RouteKind) (*Backend, LoaderError) {
 
path: pkg/gateway/routeutils/backend.go
user: login: shuqz
body: why do we have to modify this? i was doing something like 
```
type BackendLoadError struct {
	Reason  string
	Message string
}

func (error *BackendLoadError) Error() string {
	return fmt.Sprintf("reason: %v, message: %v", error.Reason, error.Message)
}
```
and use it like 
```

var backendLoadError *BackendLoadError
if errors.As(err, &backendLoadError) { ......}
```
so it is like any other error and can be used anywhere an error is expected and this function can return other errors (and do not need to modify `error` to `LoadError` in each route type)
created_at: 2025-05-27T21:58:33Z
author_association: COLLABORATOR
diff_hunk: @@ -27,19 +27,21 @@ type Backend struct {
 }
 
 // commonBackendLoader this function will load the services and target group configurations associated with this gateway backend.
-func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpecificBackend interface{}, backendRef gwv1.BackendRef, routeIdentifier types.NamespacedName, routeKind RouteKind) (*Backend, error) {
+func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpecificBackend interface{}, backendRef gwv1.BackendRef, routeIdentifier types.NamespacedName, routeKind RouteKind) (*Backend, LoaderError) {
 
path: pkg/gateway/routeutils/backend.go
user: login: zac-nixon
body: good suggestion. I can refactor it.
created_at: 2025-05-27T22:07:01Z
author_association: COLLABORATOR
diff_hunk: @@ -186,12 +196,23 @@ func (r *gatewayReconciler) reconcileHelper(ctx context.Context, req reconcile.R
 	mergedLbConfig, err := r.cfgResolver.getLoadBalancerConfigForGateway(ctx, r.k8sClient, gw, gwClass)
 
 	if err != nil {
+		statusErr := r.updateGatewayStatusFailure(ctx, gw, gwv1.GatewayReasonInvalid, err)
+		if statusErr != nil {
+			r.logger.Error(err, "Unable to update gateway status on failure to retrieve attached config")
path: controllers/gateway/gateway_controller.go
user: login: shuqz
body: err or statusErr?
created_at: 2025-05-27T23:56:20Z
author_association: COLLABORATOR
diff_hunk: @@ -295,28 +312,59 @@ func (r *gatewayReconciler) buildModel(ctx context.Context, gw *gwv1.Gateway, cf
 	return stack, lb, backendSGRequired, nil
 }
 
-func (r *gatewayReconciler) updateGatewayStatus(ctx context.Context, lbDNS string, gw *gwv1.Gateway) error {
-	// TODO Consider LB ARN.
+func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbStatus *elbv2model.LoadBalancerStatus, gw *gwv1.Gateway) error {
+	// LB Status should always be set, if it's not, we need to prevent NPE
+	if lbStatus == nil {
+		r.logger.Info("Unable to update Gateway Status due to null LB status")
+		return nil
+	}
+	gwOld := gw.DeepCopy()
+
+	var needPatch bool
+	var requeueNeeded bool
+	if isGatewayProgrammed(*lbStatus) {
+		needPatch = prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionProgrammed), metav1.ConditionTrue, string(gwv1.GatewayConditionProgrammed), lbStatus.LoadBalancerARN)
+	} else {
+		requeueNeeded = true
+	}
 
-	// Gateway Address Status
+	needPatch = prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionTrue, string(gwv1.GatewayConditionAccepted), "") || needPatch
 	if len(gw.Status.Addresses) != 1 ||
 		gw.Status.Addresses[0].Value != "" ||
-		gw.Status.Addresses[0].Value != lbDNS {
-		gwOld := gw.DeepCopy()
+		gw.Status.Addresses[0].Value != lbStatus.DNSName {
 		ipAddressType := gwv1.HostnameAddressType
 		gw.Status.Addresses = []gwv1.GatewayStatusAddress{
 			{
 				Type:  &ipAddressType,
-				Value: lbDNS,
+				Value: lbStatus.DNSName,
 			},
 		}
+		needPatch = true
+	}
+
+	if needPatch {
 		if err := r.k8sClient.Status().Patch(ctx, gw, client.MergeFrom(gwOld)); err != nil {
 			return errors.Wrapf(err, "failed to update gw status: %v", k8s.NamespacedName(gw))
 		}
 	}
 
-	// TODO: Listener status ListenerStatus
-	// https://github.com/aws/aws-application-networking-k8s/blob/main/pkg/controllers/gateway_controller.go#L350
+	if requeueNeeded {
+		return runtime.NewRequeueNeededAfter(requeueMessage, statusUpdateRequeueTime)
+	}
+
+	return nil
+}
+
+func (r *gatewayReconciler) updateGatewayStatusFailure(ctx context.Context, gw *gwv1.Gateway, reason gwv1.GatewayConditionReason, err error) error {
+	gwOld := gw.DeepCopy()
+
+	needPatch := prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionFalse, string(reason), err.Error())
path: controllers/gateway/gateway_controller.go
user: login: shuqz
body: i saw there is requeue for updateGatewayStatusSuccess in case of update failure, but do not see it here. 
also, why are we splitting updateGatewayStatusFailure and updateGatewayStatusSuccess instead of one function?
created_at: 2025-05-28T00:04:51Z
author_association: COLLABORATOR
diff_hunk: @@ -31,15 +31,17 @@ func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpeci
 
 	// We only support references of type service.
 	if backendRef.Kind != nil && *backendRef.Kind != "Service" {
-		return nil, nil
+		initialErrorMessage := "Backend Ref must be of kind 'Service'"
+		return nil, wrapError(errors.Errorf("%s", generateInvalidMessageWithRouteDetails(initialErrorMessage, routeKind, routeIdentifier)), gwv1.GatewayReasonListenersNotValid)
path: pkg/gateway/routeutils/backend.go
user: login: shuqz
body: can we reuse some of the existing error constant? e.g. gwv1.RouteReasonInvalidKind
created_at: 2025-05-28T00:17:18Z
author_association: COLLABORATOR
diff_hunk: @@ -72,9 +75,17 @@ func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpeci
 	svc := &corev1.Service{}
 	err := k8sClient.Get(ctx, svcIdentifier, svc)
 	if err != nil {
+
+		convertToNotFoundError := client.IgnoreNotFound(err)
path: pkg/gateway/routeutils/backend.go
user: login: shuqz
body: gwv1.RouteReasonBackendNotFound
created_at: 2025-05-28T00:18:06Z
author_association: COLLABORATOR
diff_hunk: @@ -3,9 +3,11 @@ package gateway
 import (
path: controllers/gateway/gateway_controller.go
user: login: shuqz
body: in your description, you mentioned fix gw deletion not working, i am curious what exactly is the fix?
created_at: 2025-05-28T00:19:49Z
author_association: COLLABORATOR
diff_hunk: @@ -3,9 +3,11 @@ package gateway
 import (
path: controllers/gateway/gateway_controller.go
user: login: zac-nixon
body: Sorry, I broke that out into a different PR:
https://github.com/kubernetes-sigs/aws-load-balancer-controller/pull/4192
created_at: 2025-05-28T14:16:36Z
author_association: COLLABORATOR
diff_hunk: @@ -186,12 +196,23 @@ func (r *gatewayReconciler) reconcileHelper(ctx context.Context, req reconcile.R
 	mergedLbConfig, err := r.cfgResolver.getLoadBalancerConfigForGateway(ctx, r.k8sClient, gw, gwClass)
 
 	if err != nil {
+		statusErr := r.updateGatewayStatusFailure(ctx, gw, gwv1.GatewayReasonInvalid, err)
+		if statusErr != nil {
+			r.logger.Error(err, "Unable to update gateway status on failure to retrieve attached config")
path: controllers/gateway/gateway_controller.go
user: login: zac-nixon
body: should be statusErr. thanks :)
created_at: 2025-05-28T14:16:51Z
author_association: COLLABORATOR
diff_hunk: @@ -295,28 +312,59 @@ func (r *gatewayReconciler) buildModel(ctx context.Context, gw *gwv1.Gateway, cf
 	return stack, lb, backendSGRequired, nil
 }
 
-func (r *gatewayReconciler) updateGatewayStatus(ctx context.Context, lbDNS string, gw *gwv1.Gateway) error {
-	// TODO Consider LB ARN.
+func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbStatus *elbv2model.LoadBalancerStatus, gw *gwv1.Gateway) error {
+	// LB Status should always be set, if it's not, we need to prevent NPE
+	if lbStatus == nil {
+		r.logger.Info("Unable to update Gateway Status due to null LB status")
+		return nil
+	}
+	gwOld := gw.DeepCopy()
+
+	var needPatch bool
+	var requeueNeeded bool
+	if isGatewayProgrammed(*lbStatus) {
+		needPatch = prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionProgrammed), metav1.ConditionTrue, string(gwv1.GatewayConditionProgrammed), lbStatus.LoadBalancerARN)
+	} else {
+		requeueNeeded = true
+	}
 
-	// Gateway Address Status
+	needPatch = prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionTrue, string(gwv1.GatewayConditionAccepted), "") || needPatch
 	if len(gw.Status.Addresses) != 1 ||
 		gw.Status.Addresses[0].Value != "" ||
-		gw.Status.Addresses[0].Value != lbDNS {
-		gwOld := gw.DeepCopy()
+		gw.Status.Addresses[0].Value != lbStatus.DNSName {
 		ipAddressType := gwv1.HostnameAddressType
 		gw.Status.Addresses = []gwv1.GatewayStatusAddress{
 			{
 				Type:  &ipAddressType,
-				Value: lbDNS,
+				Value: lbStatus.DNSName,
 			},
 		}
+		needPatch = true
+	}
+
+	if needPatch {
 		if err := r.k8sClient.Status().Patch(ctx, gw, client.MergeFrom(gwOld)); err != nil {
 			return errors.Wrapf(err, "failed to update gw status: %v", k8s.NamespacedName(gw))
 		}
 	}
 
-	// TODO: Listener status ListenerStatus
-	// https://github.com/aws/aws-application-networking-k8s/blob/main/pkg/controllers/gateway_controller.go#L350
+	if requeueNeeded {
+		return runtime.NewRequeueNeededAfter(requeueMessage, statusUpdateRequeueTime)
+	}
+
+	return nil
+}
+
+func (r *gatewayReconciler) updateGatewayStatusFailure(ctx context.Context, gw *gwv1.Gateway, reason gwv1.GatewayConditionReason, err error) error {
+	gwOld := gw.DeepCopy()
+
+	needPatch := prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionFalse, string(reason), err.Error())
path: controllers/gateway/gateway_controller.go
user: login: zac-nixon
body: We don't need to explicitly re-queue for the failure case because returning an err is good enough to trigger another reconcile, take one of the examples:
```
	mergedLbConfig, err := r.cfgResolver.getLoadBalancerConfigForGateway(ctx, r.k8sClient, gw, gwClass)

	if err != nil {
		statusErr := r.updateGatewayStatusFailure(ctx, gw, gwv1.GatewayReasonInvalid, err)
		if statusErr != nil {
			r.logger.Error(statusErr, "Unable to update gateway status on failure to retrieve attached config")
		}
		return err
	}
```

We always propagate the original error message.


We need the re-enqueue for the success case because there is no error to bubble up, and we need to re-queue in order to pick up the programmed status.

I did two separate functions because the failure case only ever handles the 'Accepted' condition, while the success case could potentially update both 'Accepted' and 'Programmed'. Along with needing to support re-queue logic, I thought breaking success / failure was cleaner.


created_at: 2025-05-28T14:20:50Z
author_association: COLLABORATOR
diff_hunk: @@ -31,15 +31,17 @@ func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpeci
 
 	// We only support references of type service.
 	if backendRef.Kind != nil && *backendRef.Kind != "Service" {
-		return nil, nil
+		initialErrorMessage := "Backend Ref must be of kind 'Service'"
+		return nil, wrapError(errors.Errorf("%s", generateInvalidMessageWithRouteDetails(initialErrorMessage, routeKind, routeIdentifier)), gwv1.GatewayReasonListenersNotValid)
path: pkg/gateway/routeutils/backend.go
user: login: zac-nixon
body: Where do you want that introduced? Sorry it was not clear to me.
created_at: 2025-05-28T14:21:11Z
author_association: COLLABORATOR
diff_hunk: @@ -72,9 +75,17 @@ func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpeci
 	svc := &corev1.Service{}
 	err := k8sClient.Get(ctx, svcIdentifier, svc)
 	if err != nil {
+
+		convertToNotFoundError := client.IgnoreNotFound(err)
path: pkg/gateway/routeutils/backend.go
user: login: zac-nixon
body: I think that should be applied to the route condition, however the Gateway condition has it's own set of conditions to set. 
created_at: 2025-05-28T14:22:47Z
author_association: COLLABORATOR
diff_hunk: @@ -31,15 +31,17 @@ func commonBackendLoader(ctx context.Context, k8sClient client.Client, typeSpeci
 
 	// We only support references of type service.
 	if backendRef.Kind != nil && *backendRef.Kind != "Service" {
-		return nil, nil
+		initialErrorMessage := "Backend Ref must be of kind 'Service'"
+		return nil, wrapError(errors.Errorf("%s", generateInvalidMessageWithRouteDetails(initialErrorMessage, routeKind, routeIdentifier)), gwv1.GatewayReasonListenersNotValid)
path: pkg/gateway/routeutils/backend.go
user: login: shuqz
body: i was thinking this can be the returned reason. but now realize that gateway status has a different set of error reason (gwv1.GatewayReasonListenersNotValid). in this case, we might wanna think about how to handle both route and gateway status here, cuz in the same place, route status is supposed to have gwv1.RouteReasonInvalidKind. i can take gwv1.GatewayReasonListenersNotValid and map it to invalidKind if this is the only place ListenersNotValid is thrown
created_at: 2025-05-28T17:49:14Z
author_association: COLLABORATOR
diff_hunk: @@ -86,6 +94,7 @@ func newGatewayReconciler(controllerName string, lbType elbv2model.LoadBalancerT
 		reconcileTracker:        reconcileTracker,
 		cfgResolver:             cfgResolver,
 		routeReconciler:         routeReconciler,
+		gatewayConditionUpdater: prepareGatewayConditionUpdate,
path: controllers/gateway/gateway_controller.go
user: login: wweiwei-li
body: Looks like you are calling `prepareGatewayConditionUpdate` directly everywhere where you are using it.  Do you want to remove it from the struct
created_at: 2025-05-28T23:05:08Z
author_association: COLLABORATOR
diff_hunk: @@ -295,28 +312,59 @@ func (r *gatewayReconciler) buildModel(ctx context.Context, gw *gwv1.Gateway, cf
 	return stack, lb, backendSGRequired, nil
 }
 
-func (r *gatewayReconciler) updateGatewayStatus(ctx context.Context, lbDNS string, gw *gwv1.Gateway) error {
-	// TODO Consider LB ARN.
+func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbStatus *elbv2model.LoadBalancerStatus, gw *gwv1.Gateway) error {
+	// LB Status should always be set, if it's not, we need to prevent NPE
+	if lbStatus == nil {
+		r.logger.Info("Unable to update Gateway Status due to null LB status")
+		return nil
+	}
+	gwOld := gw.DeepCopy()
+
+	var needPatch bool
+	var requeueNeeded bool
+	if isGatewayProgrammed(*lbStatus) {
+		needPatch = prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionProgrammed), metav1.ConditionTrue, string(gwv1.GatewayConditionProgrammed), lbStatus.LoadBalancerARN)
+	} else {
+		requeueNeeded = true
+	}
 
-	// Gateway Address Status
+	needPatch = prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionTrue, string(gwv1.GatewayConditionAccepted), "") || needPatch
 	if len(gw.Status.Addresses) != 1 ||
 		gw.Status.Addresses[0].Value != "" ||
-		gw.Status.Addresses[0].Value != lbDNS {
-		gwOld := gw.DeepCopy()
+		gw.Status.Addresses[0].Value != lbStatus.DNSName {
 		ipAddressType := gwv1.HostnameAddressType
 		gw.Status.Addresses = []gwv1.GatewayStatusAddress{
 			{
path: controllers/gateway/gateway_controller.go
user: login: wweiwei-li
body: Just to confirm. Is this correct ?  `gw.Status.Addresses[0].Value != "" The second condition will always be true if the address value is not empty. 
created_at: 2025-05-28T23:23:10Z
author_association: COLLABORATOR
diff_hunk: @@ -295,28 +312,59 @@ func (r *gatewayReconciler) buildModel(ctx context.Context, gw *gwv1.Gateway, cf
 	return stack, lb, backendSGRequired, nil
 }
 
-func (r *gatewayReconciler) updateGatewayStatus(ctx context.Context, lbDNS string, gw *gwv1.Gateway) error {
-	// TODO Consider LB ARN.
+func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbStatus *elbv2model.LoadBalancerStatus, gw *gwv1.Gateway) error {
+	// LB Status should always be set, if it's not, we need to prevent NPE
+	if lbStatus == nil {
+		r.logger.Info("Unable to update Gateway Status due to null LB status")
+		return nil
path: controllers/gateway/gateway_controller.go
user: login: wweiwei-li
body: if lbStatus == nil, the updateGatewayStatusSuccess return nil (no error).  
There's a potential issue with this part in reconcileUpdate.  it will success reconcile.  Should this not be expected ? How about return an error ?
```
if err = r.updateGatewayStatusSuccess(ctx, lb.Status, gw); err != nil {
    r.eventRecorder.Event(gw, corev1.EventTypeWarning, k8s.GatewayEventReasonFailedUpdateStatus, fmt.Sprintf("Failed update status due to %v", err))
    return err
}
r.eventRecorder.Event(gw, corev1.EventTypeNormal, k8s.GatewayEventReasonSuccessfullyReconciled, "Successfully reconciled")
	return nil
} 

created_at: 2025-05-28T23:32:04Z
author_association: COLLABORATOR
diff_hunk: @@ -86,6 +94,7 @@ func newGatewayReconciler(controllerName string, lbType elbv2model.LoadBalancerT
 		reconcileTracker:        reconcileTracker,
 		cfgResolver:             cfgResolver,
 		routeReconciler:         routeReconciler,
+		gatewayConditionUpdater: prepareGatewayConditionUpdate,
path: controllers/gateway/gateway_controller.go
user: login: zac-nixon
body: nice find, thanks. i've replaced the direct usage with calls from the struct.
created_at: 2025-05-29T00:47:29Z
author_association: COLLABORATOR
diff_hunk: @@ -295,28 +312,59 @@ func (r *gatewayReconciler) buildModel(ctx context.Context, gw *gwv1.Gateway, cf
 	return stack, lb, backendSGRequired, nil
 }
 
-func (r *gatewayReconciler) updateGatewayStatus(ctx context.Context, lbDNS string, gw *gwv1.Gateway) error {
-	// TODO Consider LB ARN.
+func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbStatus *elbv2model.LoadBalancerStatus, gw *gwv1.Gateway) error {
+	// LB Status should always be set, if it's not, we need to prevent NPE
+	if lbStatus == nil {
+		r.logger.Info("Unable to update Gateway Status due to null LB status")
+		return nil
path: controllers/gateway/gateway_controller.go
user: login: zac-nixon
body: yeah, i wasn't exactly sure what to do there. My thinking is that the lbstatus being null is a weird and unexpected condition. I'd prefer to silently fail (with a log message) rather than keep performing reconciles when the LBStatus is busted.
created_at: 2025-05-29T00:49:39Z
author_association: COLLABORATOR
diff_hunk: @@ -295,28 +312,59 @@ func (r *gatewayReconciler) buildModel(ctx context.Context, gw *gwv1.Gateway, cf
 	return stack, lb, backendSGRequired, nil
 }
 
-func (r *gatewayReconciler) updateGatewayStatus(ctx context.Context, lbDNS string, gw *gwv1.Gateway) error {
-	// TODO Consider LB ARN.
+func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbStatus *elbv2model.LoadBalancerStatus, gw *gwv1.Gateway) error {
+	// LB Status should always be set, if it's not, we need to prevent NPE
+	if lbStatus == nil {
+		r.logger.Info("Unable to update Gateway Status due to null LB status")
+		return nil
+	}
+	gwOld := gw.DeepCopy()
+
+	var needPatch bool
+	var requeueNeeded bool
+	if isGatewayProgrammed(*lbStatus) {
+		needPatch = prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionProgrammed), metav1.ConditionTrue, string(gwv1.GatewayConditionProgrammed), lbStatus.LoadBalancerARN)
+	} else {
+		requeueNeeded = true
+	}
 
-	// Gateway Address Status
+	needPatch = prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionTrue, string(gwv1.GatewayConditionAccepted), "") || needPatch
 	if len(gw.Status.Addresses) != 1 ||
 		gw.Status.Addresses[0].Value != "" ||
-		gw.Status.Addresses[0].Value != lbDNS {
-		gwOld := gw.DeepCopy()
+		gw.Status.Addresses[0].Value != lbStatus.DNSName {
 		ipAddressType := gwv1.HostnameAddressType
 		gw.Status.Addresses = []gwv1.GatewayStatusAddress{
 			{
path: controllers/gateway/gateway_controller.go
user: login: zac-nixon
body: I didn't follow this comment, sorry. 

Are you saying that if the address is not empty, then it must equal the DNS name of the LB?
created_at: 2025-05-29T00:51:12Z
author_association: COLLABORATOR
diff_hunk: @@ -295,28 +312,59 @@ func (r *gatewayReconciler) buildModel(ctx context.Context, gw *gwv1.Gateway, cf
 	return stack, lb, backendSGRequired, nil
 }
 
-func (r *gatewayReconciler) updateGatewayStatus(ctx context.Context, lbDNS string, gw *gwv1.Gateway) error {
-	// TODO Consider LB ARN.
+func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbStatus *elbv2model.LoadBalancerStatus, gw *gwv1.Gateway) error {
+	// LB Status should always be set, if it's not, we need to prevent NPE
+	if lbStatus == nil {
+		r.logger.Info("Unable to update Gateway Status due to null LB status")
+		return nil
+	}
+	gwOld := gw.DeepCopy()
+
+	var needPatch bool
+	var requeueNeeded bool
+	if isGatewayProgrammed(*lbStatus) {
+		needPatch = prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionProgrammed), metav1.ConditionTrue, string(gwv1.GatewayConditionProgrammed), lbStatus.LoadBalancerARN)
+	} else {
+		requeueNeeded = true
+	}
 
-	// Gateway Address Status
+	needPatch = prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionTrue, string(gwv1.GatewayConditionAccepted), "") || needPatch
 	if len(gw.Status.Addresses) != 1 ||
 		gw.Status.Addresses[0].Value != "" ||
-		gw.Status.Addresses[0].Value != lbDNS {
-		gwOld := gw.DeepCopy()
+		gw.Status.Addresses[0].Value != lbStatus.DNSName {
 		ipAddressType := gwv1.HostnameAddressType
 		gw.Status.Addresses = []gwv1.GatewayStatusAddress{
 			{
path: controllers/gateway/gateway_controller.go
user: login: wweiwei-li
body: I may need to understand the intent here better.  I was thinking ` gw.Status.Addresses[0].Value ` cannot be simultaneously equal to both an empty string and lbStatus.DNSName. One of these conditions will always be true. Therefore,  `gw.Status.Addresses[0].Value != "" ||
		gw.Status.Addresses[0].Value != lbStatus.DNSName` will always be true.  

Was your intent here to update the address only when necessary. More specifically, only when either: 
1. The address value is empty OR
2. The address does not match the load balancer's DNS name
```
if len(gw.Status.Addresses) != 1 ||
    gw.Status.Addresses[0].Value == "" || gw.Status.Addresses[0].Value != lbStatus.DNSName) {
    ipAddressType := gwv1.HostnameAddressType
    gw.Status.Addresses = []gwv1.GatewayStatusAddress{
        {
            Type:  &ipAddressType,
            Value: lbStatus.DNSName,
        },
    }
    needPatch = true
}
```
Else,  simply remove `gw.Status.Addresses[0].Value != ""`
```
if len(gw.Status.Addresses) != 1 ||
    gw.Status.Addresses[0].Value != lbStatus.DNSName {
    ipAddressType := gwv1.HostnameAddressType
    gw.Status.Addresses = []gwv1.GatewayStatusAddress{
        {
            Type:  &ipAddressType,
            Value: lbStatus.DNSName,
        },
    }
    needPatch = true
}
```




created_at: 2025-05-29T04:43:56Z
author_association: COLLABORATOR
diff_hunk: @@ -295,28 +312,59 @@ func (r *gatewayReconciler) buildModel(ctx context.Context, gw *gwv1.Gateway, cf
 	return stack, lb, backendSGRequired, nil
 }
 
-func (r *gatewayReconciler) updateGatewayStatus(ctx context.Context, lbDNS string, gw *gwv1.Gateway) error {
-	// TODO Consider LB ARN.
+func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbStatus *elbv2model.LoadBalancerStatus, gw *gwv1.Gateway) error {
+	// LB Status should always be set, if it's not, we need to prevent NPE
+	if lbStatus == nil {
+		r.logger.Info("Unable to update Gateway Status due to null LB status")
+		return nil
+	}
+	gwOld := gw.DeepCopy()
+
+	var needPatch bool
+	var requeueNeeded bool
+	if isGatewayProgrammed(*lbStatus) {
+		needPatch = prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionProgrammed), metav1.ConditionTrue, string(gwv1.GatewayConditionProgrammed), lbStatus.LoadBalancerARN)
+	} else {
+		requeueNeeded = true
+	}
 
-	// Gateway Address Status
+	needPatch = prepareGatewayConditionUpdate(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionTrue, string(gwv1.GatewayConditionAccepted), "") || needPatch
 	if len(gw.Status.Addresses) != 1 ||
 		gw.Status.Addresses[0].Value != "" ||
-		gw.Status.Addresses[0].Value != lbDNS {
-		gwOld := gw.DeepCopy()
+		gw.Status.Addresses[0].Value != lbStatus.DNSName {
 		ipAddressType := gwv1.HostnameAddressType
 		gw.Status.Addresses = []gwv1.GatewayStatusAddress{
 			{
path: controllers/gateway/gateway_controller.go
user: login: zac-nixon
body: 🤦 wow -- thanks for catching that and the explanation.
created_at: 2025-05-29T05:19:09Z
author_association: COLLABORATOR

PR comments:
user: login: k8s-ci-robot
created_at: 2025-05-29T16:41:29Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/aws-load-balancer-controller/pull/4189#pullrequestreview-2878936363" title="Approved">wweiwei-li</a>*, *<a href="https://github.com/kubernetes-sigs/aws-load-balancer-controller/pull/4189#" title="Author self-approved">zac-nixon</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Faws-load-balancer-controller).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/main/OWNERS)~~ [wweiwei-li,zac-nixon]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR
user: login: wweiwei-li
created_at: 2025-05-29T16:41:34Z
body: /lgtm
author_association: COLLABORATOR