Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 46 additions & 15 deletions controllers/gateway/gateway_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package gateway
import (
"context"
"fmt"
"time"

elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
"github.com/go-logr/logr"
"github.com/pkg/errors"
Expand Down Expand Up @@ -42,7 +44,6 @@ import (
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
gwalpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gwbeta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
"time"
)

const (
Expand Down Expand Up @@ -205,25 +206,35 @@ func (r *gatewayReconciler) reconcileHelper(ctx context.Context, req reconcile.R
mergedLbConfig, err := r.cfgResolver.getLoadBalancerConfigForGateway(ctx, r.k8sClient, r.finalizerManager, gw, gwClass)

if err != nil {
statusErr := r.updateGatewayStatusFailure(ctx, gw, gwv1.GatewayReasonInvalid, err.Error())
statusErr := r.updateGatewayStatusFailure(ctx, gw, gwv1.GatewayReasonInvalid, err.Error(), nil)
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)
loaderResults, err := r.gatewayLoader.LoadRoutesForGateway(ctx, *gw, r.routeFilter, r.controllerName)

if err != nil {
if err != nil || loaderResults.ValidationResults.HasErrors {
var loaderErr routeutils.LoaderError
if errors.As(err, &loaderErr) {
statusErr := r.updateGatewayStatusFailure(ctx, gw, loaderErr.GetGatewayReason(), loaderErr.GetGatewayMessage())
if errors.As(err, &loaderErr) || loaderResults.ValidationResults.HasErrors {
var gatewayReason gwv1.GatewayConditionReason
var gatewayMessage string
if loaderErr == nil && loaderResults.ValidationResults.HasErrors {
gatewayReason = gwv1.GatewayReasonAccepted
gatewayMessage = gatewayAcceptedFalseMessage
} else {
gatewayReason = loaderErr.GetGatewayReason()
gatewayMessage = loaderErr.GetGatewayMessage()
}
statusErr := r.updateGatewayStatusFailure(ctx, gw, gatewayReason, gatewayMessage, loaderResults)
if statusErr != nil {
r.logger.Error(statusErr, "Unable to update gateway status on failure to build routes")
}
}
return err
}
allRoutes := loaderResults.Routes

// To handle Addons, we need to build the set that has been previously enabled. This is stored within the Gateway annotations.
allAddOns := getStoredAddonConfig(gw, r.logger)
Expand All @@ -237,10 +248,6 @@ func (r *gatewayReconciler) reconcileHelper(ctx context.Context, req reconcile.R

stack, lb, newAddOnConfig, backendSGRequired, secrets, err := r.buildModel(ctx, gw, mergedLbConfig, allRoutes, currentAddOns)

if err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems this got removed, then didn't get re-added at a later place.

return err
}

r.logger.V(1).Info("Got this addon config", "current", currentAddOns, "new addon", newAddOnConfig)

// To accurately track the set of enabled addons, we need to figure out if any addons were added / removed during this run.
Expand All @@ -266,7 +273,7 @@ func (r *gatewayReconciler) reconcileHelper(ctx context.Context, req reconcile.R
return nil
}
r.serviceReferenceCounter.UpdateRelations(getServicesFromRoutes(allRoutes), k8s.NamespacedName(gw), false)
err = r.reconcileUpdate(ctx, gw, gwClass, stack, lb, backendSGRequired, secrets)
err = r.reconcileUpdate(ctx, gw, gwClass, stack, lb, backendSGRequired, secrets, loaderResults.AttachedRoutesMap)
if err != nil {
r.logger.Error(err, "Failed to process gateway update", "gw", k8s.NamespacedName(gw))
return err
Expand Down Expand Up @@ -308,7 +315,7 @@ func (r *gatewayReconciler) reconcileDelete(ctx context.Context, gw *gwv1.Gatewa
}

func (r *gatewayReconciler) reconcileUpdate(ctx context.Context, gw *gwv1.Gateway, gwClass *gwv1.GatewayClass, stack core.Stack,
lb *elbv2model.LoadBalancer, backendSGRequired bool, secrets []types.NamespacedName) error {
lb *elbv2model.LoadBalancer, backendSGRequired bool, secrets []types.NamespacedName, attachedRoutesMap map[gwv1.SectionName]int32) error {
// add gateway finalizer
if err := r.finalizerManager.AddFinalizers(ctx, gw, r.finalizer); err != nil {
r.eventRecorder.Event(gw, corev1.EventTypeWarning, k8s.GatewayEventReasonFailedAddFinalizer, fmt.Sprintf("Failed add gateway finalizer due to %v", err))
Expand All @@ -326,7 +333,7 @@ func (r *gatewayReconciler) reconcileUpdate(ctx context.Context, gw *gwv1.Gatewa
}
}

if err = r.updateGatewayStatusSuccess(ctx, lb.Status, gw); err != nil {
if err = r.updateGatewayStatusSuccess(ctx, lb.Status, gw, attachedRoutesMap); err != nil {
r.eventRecorder.Event(gw, corev1.EventTypeWarning, k8s.GatewayEventReasonFailedUpdateStatus, fmt.Sprintf("Failed update status due to %v", err))
return err
}
Expand Down Expand Up @@ -365,7 +372,7 @@ func (r *gatewayReconciler) buildModel(ctx context.Context, gw *gwv1.Gateway, cf
return stack, lb, newAddOnConfig, backendSGRequired, secrets, nil
}

func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbStatus *elbv2model.LoadBalancerStatus, gw *gwv1.Gateway) error {
func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbStatus *elbv2model.LoadBalancerStatus, gw *gwv1.Gateway, attachedRoutesMap map[gwv1.SectionName]int32) 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")
Expand Down Expand Up @@ -394,8 +401,18 @@ func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbSt
needPatch = true
}

// update listeners status
ListenerStatuses, err := buildListenerStatus(r.controllerName, *gw, attachedRoutesMap, nil)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this not possible to fail, can you remove returning an error?

if err != nil {
r.logger.Info("failed to build listeners status: %v", k8s.NamespacedName(gw))
} else if !isListenerStatusIdentical(gw.Status.Listeners, ListenerStatuses) {
gw.Status.Listeners = ListenerStatuses
needPatch = true
}

if needPatch {
if err := r.k8sClient.Status().Patch(ctx, gw, client.MergeFrom(gwOld)); err != nil {
fmt.Printf("failed to update status: %s\n", err.Error())
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will remove it

return errors.Wrapf(err, "failed to update gw status: %v", k8s.NamespacedName(gw))
}
}
Expand All @@ -407,13 +424,27 @@ func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbSt
return nil
}

func (r *gatewayReconciler) updateGatewayStatusFailure(ctx context.Context, gw *gwv1.Gateway, reason gwv1.GatewayConditionReason, errMessage string) error {
func (r *gatewayReconciler) updateGatewayStatusFailure(ctx context.Context, gw *gwv1.Gateway, reason gwv1.GatewayConditionReason, errMessage string, loadResults *routeutils.LoaderResult) error {
gwOld := gw.DeepCopy()

needPatch := r.gatewayConditionUpdater(gw, string(gwv1.GatewayConditionAccepted), metav1.ConditionFalse, string(reason), errMessage)

// update listener status
if loadResults != nil {
listenerValidationResults := loadResults.ValidationResults
attachedRoutesMap := loadResults.AttachedRoutesMap
ListenerStatuses, err := buildListenerStatus(r.controllerName, *gw, attachedRoutesMap, &listenerValidationResults)
if err != nil {
r.logger.Info("failed to build listeners status: %v", k8s.NamespacedName(gw))
} else if !isListenerStatusIdentical(gw.Status.Listeners, ListenerStatuses) {
gw.Status.Listeners = ListenerStatuses
needPatch = true
}
}

if needPatch {
if err := r.k8sClient.Status().Patch(ctx, gw, client.MergeFrom(gwOld)); err != nil {
fmt.Printf("failed to update status: %s\n", err.Error())
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will remove it

return errors.Wrapf(err, "failed to update gw status: %v", k8s.NamespacedName(gw))
}
}
Expand Down
174 changes: 174 additions & 0 deletions controllers/gateway/listener_status_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package gateway

import (
"fmt"
"sort"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/routeutils"
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
)

func buildListenerStatus(controllerName string, gateway gwv1.Gateway, attachedRoutesMap map[gwv1.SectionName]int32, validateListenerResults *routeutils.ListenerValidationResults) ([]gwv1.ListenerStatus, error) {
var listenerStatuses []gwv1.ListenerStatus

// if validateListenerResults is nil, getListenerConditions will build condition with accepted condition
for _, listener := range gateway.Spec.Listeners {
supportedKinds, _ := routeutils.GetSupportedKinds(controllerName, listener)
var condition []metav1.Condition
if validateListenerResults == nil {
condition = getListenerConditions(gateway, nil)
} else {
listenerValidationResult := validateListenerResults.Results[listener.Name]
condition = getListenerConditions(gateway, &listenerValidationResult)
}

listenerStatus := gwv1.ListenerStatus{
Name: listener.Name,
SupportedKinds: supportedKinds,
AttachedRoutes: attachedRoutesMap[listener.Name],
Conditions: condition,
}
listenerStatuses = append(listenerStatuses, listenerStatus)
}
return listenerStatuses, nil
}

func getListenerConditions(gw gwv1.Gateway, listenerValidationResult *routeutils.ListenerValidationResult) []metav1.Condition {
var conditions []metav1.Condition

// Determine condition type based on reason
if listenerValidationResult == nil {
return append(conditions, buildAcceptedCondition(gw, gwv1.ListenerReasonAccepted, "Listener is accepted"))
}
listenerReason := listenerValidationResult.Reason
listenerErrMessage := listenerValidationResult.Message
switch listenerReason {
case gwv1.ListenerReasonHostnameConflict, gwv1.ListenerReasonProtocolConflict:
conditions = append(conditions, buildConflictedCondition(gw, listenerReason, listenerErrMessage))
case gwv1.ListenerReasonPortUnavailable, gwv1.ListenerReasonUnsupportedProtocol:
conditions = append(conditions, buildAcceptedCondition(gw, listenerReason, listenerErrMessage))
case gwv1.ListenerReasonInvalidRouteKinds, gwv1.ListenerReasonRefNotPermitted:
conditions = append(conditions, buildResolvedRefsCondition(gw, listenerReason, listenerErrMessage))
default:
conditions = append(conditions, buildAcceptedCondition(gw, gwv1.ListenerReasonAccepted, "Listener is accepted"))
}

return conditions
}

func buildAcceptedCondition(gw gwv1.Gateway, reason gwv1.ListenerConditionReason, message string) metav1.Condition {
status := metav1.ConditionTrue
if reason != gwv1.ListenerReasonAccepted {
status = metav1.ConditionFalse
}

return metav1.Condition{
Type: string(gwv1.ListenerConditionAccepted),
Status: status,
Reason: string(reason),
Message: message,
LastTransitionTime: metav1.NewTime(time.Now()),
ObservedGeneration: gw.GetGeneration(),
}
}

func buildConflictedCondition(gw gwv1.Gateway, reason gwv1.ListenerConditionReason, message string) metav1.Condition {
status := metav1.ConditionTrue
if reason != gwv1.ListenerReasonAccepted {
status = metav1.ConditionFalse
}
return metav1.Condition{
Type: string(gwv1.ListenerConditionConflicted),
Status: status,
Reason: string(reason),
Message: message,
LastTransitionTime: metav1.NewTime(time.Now()),
ObservedGeneration: gw.GetGeneration(),
}
}

func buildResolvedRefsCondition(gw gwv1.Gateway, reason gwv1.ListenerConditionReason, message string) metav1.Condition {
status := metav1.ConditionTrue
if reason != gwv1.ListenerReasonAccepted {
status = metav1.ConditionFalse
}
return metav1.Condition{
Type: string(gwv1.ListenerConditionResolvedRefs),
Status: status,
Reason: string(reason),
Message: message,
LastTransitionTime: metav1.NewTime(time.Now()),
ObservedGeneration: gw.GetGeneration(),
}
}

func isListenerStatusIdentical(listenerStatus []gwv1.ListenerStatus, listenerStatusOld []gwv1.ListenerStatus) bool {
if len(listenerStatus) != len(listenerStatusOld) {
return false
}
// Sort both slices by Name before comparison
sort.Slice(listenerStatus, func(i, j int) bool {
return listenerStatus[i].Name < listenerStatus[j].Name
})
sort.Slice(listenerStatusOld, func(i, j int) bool {
return listenerStatusOld[i].Name < listenerStatusOld[j].Name
})
for i := range listenerStatus {
if listenerStatus[i].Name != listenerStatusOld[i].Name {
return false
}

if !compareSupportedKinds(listenerStatus[i].SupportedKinds, listenerStatusOld[i].SupportedKinds) {
return false
}

if listenerStatus[i].AttachedRoutes != listenerStatusOld[i].AttachedRoutes {
return false
}
if len(listenerStatus[i].Conditions) != len(listenerStatusOld[i].Conditions) {
return false
}
for j := range listenerStatus[i].Conditions {
if listenerStatus[i].Conditions[j].Type != listenerStatusOld[i].Conditions[j].Type {
return false
}
if listenerStatus[i].Conditions[j].Status != listenerStatusOld[i].Conditions[j].Status {
return false
}
if listenerStatus[i].Conditions[j].Reason != listenerStatusOld[i].Conditions[j].Reason {
return false
}
if listenerStatus[i].Conditions[j].Message != listenerStatusOld[i].Conditions[j].Message {
return false
}
if listenerStatus[i].Conditions[j].ObservedGeneration != listenerStatusOld[i].Conditions[j].ObservedGeneration {
return false
}
}
}
return true
}

func compareSupportedKinds(kinds1, kinds2 []gwv1.RouteGroupKind) bool {
if len(kinds1) != len(kinds2) {
return false
}

kindMap := make(map[string]int)
for _, kind := range kinds1 {
key := fmt.Sprintf("%s/%s", *kind.Group, kind.Kind)
kindMap[key]++
}

for _, kind := range kinds2 {
key := fmt.Sprintf("%s/%s", *kind.Group, kind.Kind)
if kindMap[key] == 0 {
return false
}
kindMap[key]--
}

return true
}
Loading
Loading