-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[feat gateway-api]implement gateway listener status #4357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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 ( | ||
|
@@ -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) | ||
|
@@ -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 { | ||
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. | ||
|
@@ -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 | ||
|
@@ -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)) | ||
|
@@ -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 | ||
} | ||
|
@@ -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") | ||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) | ||
} | ||
} | ||
|
@@ -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()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) | ||
} | ||
} | ||
|
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 | ||
} |
There was a problem hiding this comment.
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.