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
53 changes: 41 additions & 12 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 All @@ -21,6 +23,7 @@ import (
elbv2deploy "sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/elbv2"
"sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/tracking"
"sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/constants"
gateway_constants "sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/constants"
gatewaymodel "sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/model"
"sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/referencecounter"
"sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/routeutils"
Expand All @@ -42,7 +45,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 +207,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 = gateway_constants.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 @@ -240,7 +252,6 @@ func (r *gatewayReconciler) reconcileHelper(ctx context.Context, req reconcile.R
if err != nil {
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 +277,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 +319,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 +337,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 +376,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,6 +405,13 @@ func (r *gatewayReconciler) updateGatewayStatusSuccess(ctx context.Context, lbSt
needPatch = true
}

// update listeners status
ListenerStatuses := buildListenerStatus(r.controllerName, *gw, attachedRoutesMap, nil)
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 {
return errors.Wrapf(err, "failed to update gw status: %v", k8s.NamespacedName(gw))
Expand All @@ -407,11 +425,22 @@ 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 := buildListenerStatus(r.controllerName, *gw, attachedRoutesMap, &listenerValidationResults)
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 {
return errors.Wrapf(err, "failed to update gw status: %v", k8s.NamespacedName(gw))
Expand Down
175 changes: 175 additions & 0 deletions controllers/gateway/listener_status_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package gateway

import (
"fmt"
"sort"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gateway_constants "sigs.k8s.io/aws-load-balancer-controller/pkg/gateway/constants"
"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 {
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
}

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, gateway_constants.ListenerAcceptedMessage))
}
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, gateway_constants.ListenerAcceptedMessage))
}

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