From cc27ee17c97217852e6da4fc6c227b3c0725d760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Ma=C5=82ek?= Date: Thu, 19 Dec 2024 10:05:44 +0100 Subject: [PATCH 1/3] refactor: refactor Gateway API's route parent status update code --- internal/controllers/gateway/conditions.go | 156 ++++ .../gateway/grpcroute_controller.go | 75 +- .../gateway/httproute_controller.go | 73 +- .../gateway/route_parent_status.go | 119 ++- .../gateway/route_parent_status_test.go | 798 ++++++++++++++++-- internal/controllers/gateway/route_utils.go | 120 --- .../controllers/gateway/route_utils_test.go | 44 +- .../gateway/tcproute_controller.go | 71 +- .../gateway/tlsroute_controller.go | 75 +- .../gateway/udproute_controller.go | 70 +- internal/gatewayapi/typemeta.go | 15 - internal/util/builder/backendref.go | 2 +- internal/util/builder/parentref.go | 53 ++ internal/util/conversions.go | 29 +- 14 files changed, 1130 insertions(+), 570 deletions(-) create mode 100644 internal/controllers/gateway/conditions.go create mode 100644 internal/util/builder/parentref.go diff --git a/internal/controllers/gateway/conditions.go b/internal/controllers/gateway/conditions.go new file mode 100644 index 0000000000..7f5ed33be6 --- /dev/null +++ b/internal/controllers/gateway/conditions.go @@ -0,0 +1,156 @@ +package gateway + +import ( + "context" + + "github.com/samber/lo" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + + "github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi" +) + +func newCondition( + typ string, + status metav1.ConditionStatus, + reason string, + generation int64, +) metav1.Condition { + return metav1.Condition{ + Type: typ, + Status: status, + Reason: reason, + ObservedGeneration: generation, + LastTransitionTime: metav1.Now(), + } +} + +func newProgrammedConditionUnknown(obj interface{ GetGeneration() int64 }) metav1.Condition { + return newCondition( + ConditionTypeProgrammed, + metav1.ConditionUnknown, + string(ConditionReasonProgrammedUnknown), + obj.GetGeneration(), + ) +} + +// sameCondition returns true if the conditions in parameter has the same type, status, reason and message. +func sameCondition(a, b metav1.Condition) bool { + return a.Type == b.Type && + a.Status == b.Status && + a.Reason == b.Reason && + a.Message == b.Message && + a.ObservedGeneration == b.ObservedGeneration +} + +func setRouteParentStatusCondition(parentStatus *gatewayapi.RouteParentStatus, newCondition metav1.Condition) bool { + var conditionFound, changed bool + for i, condition := range parentStatus.Conditions { + if condition.Type == newCondition.Type { + conditionFound = true + if !sameCondition(condition, newCondition) { + parentStatus.Conditions[i] = newCondition + changed = true + } + } + } + + if !conditionFound { + parentStatus.Conditions = append(parentStatus.Conditions, newCondition) + changed = true + } + return changed +} + +func parentStatusHasProgrammedCondition(parentStatus *gatewayapi.RouteParentStatus) bool { + for _, condition := range parentStatus.Conditions { + if condition.Type == ConditionTypeProgrammed { + return true + } + } + return false +} + +// ensureParentsProgrammedCondition ensures that provided route's parent statuses +// have Programmed condition set properly. It returns a boolean flag indicating +// whether an update to the provided route has been performed. +// +// Use the condition argument to specify the Reason, Status and Message. +// Type will be set to Programmed whereas ObservedGeneration and LastTransitionTime +// will be set accordingly based on the route's generation and current time. +func ensureParentsProgrammedCondition[ + routeT gatewayapi.RouteT, +]( + ctx context.Context, + client client.SubResourceWriter, + route routeT, + routeParentStatuses []gatewayapi.RouteParentStatus, + gateways []supportedGatewayWithCondition, + condition metav1.Condition, +) (bool, error) { + // map the existing parentStatues to avoid duplications + parentStatuses := getParentStatuses(route, routeParentStatuses) + + condition.Type = ConditionTypeProgrammed + condition.ObservedGeneration = route.GetGeneration() + condition.LastTransitionTime = metav1.Now() + + statusChanged := false + for _, g := range gateways { + gateway := g.gateway + + parentRefKey := routeParentStatusKey(route, g) + parentStatus, ok := parentStatuses[parentRefKey] + if ok { + // update existing parent in status. + changed := setRouteParentStatusCondition(parentStatus, condition) + if changed { + parentStatuses[parentRefKey] = parentStatus + setRouteParentInStatusForParent(route, *parentStatus, g) + } + statusChanged = statusChanged || changed + } else { + // add a new parent if the parent is not found in status. + newParentStatus := gatewayapi.RouteParentStatus{ + ParentRef: gatewayapi.ParentReference{ + Namespace: lo.ToPtr(gatewayapi.Namespace(gateway.Namespace)), + Name: gatewayapi.ObjectName(gateway.Name), + Kind: lo.ToPtr(gatewayapi.Kind("Gateway")), + Group: lo.ToPtr(gatewayapi.Group(gatewayv1.GroupName)), + SectionName: func() *gatewayapi.SectionName { + // We don't need to check whether the listener matches route's spec + // because that should already be done via getSupportedGatewayForRoute + // at this point. + if g.listenerName != "" { + return lo.ToPtr(gatewayapi.SectionName(g.listenerName)) + } + return nil + }(), + + // TODO: set port after gateway port matching implemented: + // https://github.com/Kong/kubernetes-ingress-controller/issues/3016 + }, + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + condition, + }, + } + setRouteParentInStatusForParent(route, newParentStatus, g) + + routeParentStatuses = append(routeParentStatuses, newParentStatus) + parentStatuses[parentRefKey] = &newParentStatus + statusChanged = true + } + } + + // update status if needed. + if statusChanged { + if err := client.Update(ctx, route); err != nil { + return false, err + } + return true, nil + } + // no need to update if no status is changed. + return false, nil +} diff --git a/internal/controllers/gateway/grpcroute_controller.go b/internal/controllers/gateway/grpcroute_controller.go index d41896d8cb..c887b73bf5 100644 --- a/internal/controllers/gateway/grpcroute_controller.go +++ b/internal/controllers/gateway/grpcroute_controller.go @@ -8,7 +8,6 @@ import ( "time" "github.com/go-logr/logr" - "github.com/samber/lo" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -27,7 +26,6 @@ import ( "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers" "github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi" - "github.com/kong/kubernetes-ingress-controller/v3/internal/util" k8sobj "github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object" "github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object/status" ) @@ -424,78 +422,17 @@ func (r *GRPCRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // GRPCRouteReconciler - Status Helpers // ----------------------------------------------------------------------------- -// grpcrouteParentKind indicates the only object KIND that this GRPCRoute -// implementation supports for route object parent references. -var grpcrouteParentKind = "Gateway" - // ensureGatewayReferenceStatus takes any number of Gateways that should be // considered "attached" to a given GRPCRoute and ensures that the status // for the GRPCRoute is updated appropriately. func (r *GRPCRouteReconciler) ensureGatewayReferenceStatusAdded(ctx context.Context, grpcroute *gatewayapi.GRPCRoute, gateways ...supportedGatewayWithCondition) (bool, error) { - // map the existing parentStatues to avoid duplications - parentStatuses := getParentStatuses(grpcroute, grpcroute.Status.Parents) - - // overlay the parent ref statuses for all new gateway references - statusChangesWereMade := false - for _, gateway := range gateways { - // build a new status for the parent Gateway - gatewayParentStatus := &gatewayapi.RouteParentStatus{ - ParentRef: gatewayapi.ParentReference{ - Group: (*gatewayapi.Group)(&gatewayv1.GroupVersion.Group), - Kind: util.StringToGatewayAPIKindPtr(grpcrouteParentKind), - Namespace: (*gatewayapi.Namespace)(&gateway.gateway.Namespace), - Name: gatewayapi.ObjectName(gateway.gateway.Name), - }, - ControllerName: GetControllerName(), - Conditions: []metav1.Condition{{ - Type: string(gatewayapi.RouteConditionAccepted), - Status: metav1.ConditionTrue, - ObservedGeneration: grpcroute.Generation, - LastTransitionTime: metav1.Now(), - Reason: string(gatewayapi.RouteReasonAccepted), - }}, - } - - if gateway.listenerName != "" { - sectionName := gatewayapi.SectionName(gateway.listenerName) - gatewayParentStatus.ParentRef.SectionName = §ionName - } - - // if the reference already exists and doesn't require any changes - // then just leave it alone. - parentRefKey := fmt.Sprintf("%s/%s/%s", gateway.gateway.Namespace, gateway.gateway.Name, gateway.listenerName) - if existingGatewayParentStatus, exists := parentStatuses[parentRefKey]; exists { - // check if the parentRef and controllerName are equal, and whether the new condition is present in existing conditions - if reflect.DeepEqual(existingGatewayParentStatus.ParentRef, gatewayParentStatus.ParentRef) && - existingGatewayParentStatus.ControllerName == gatewayParentStatus.ControllerName && - lo.ContainsBy(existingGatewayParentStatus.Conditions, func(condition metav1.Condition) bool { - return sameCondition(gatewayParentStatus.Conditions[0], condition) - }) { - continue - } - } - - // otherwise overlay the new status on top the list of parentStatuses - parentStatuses[parentRefKey] = gatewayParentStatus - statusChangesWereMade = true - } + parentStatuses, statusChangesWereMade := parentStatusesForRoute( + grpcroute, + grpcroute.Status.Parents, + gateways..., + ) - // initialize "programmed" condition to Unknown. - // do not update the condition If a "Programmed" condition is already present. - programmedConditionChanged := false - programmedConditionUnknown := metav1.Condition{ - Type: ConditionTypeProgrammed, - Status: metav1.ConditionUnknown, - Reason: string(ConditionReasonProgrammedUnknown), - ObservedGeneration: grpcroute.Generation, - LastTransitionTime: metav1.Now(), - } - for _, parentStatus := range parentStatuses { - if !parentStatusHasProgrammedCondition(parentStatus) { - programmedConditionChanged = true - parentStatus.Conditions = append(parentStatus.Conditions, programmedConditionUnknown) - } - } + programmedConditionChanged := initializeParentStatusesWithProgrammedCondition(grpcroute, parentStatuses) // if we didn't have to actually make any changes, no status update is needed if !statusChangesWereMade && !programmedConditionChanged { diff --git a/internal/controllers/gateway/httproute_controller.go b/internal/controllers/gateway/httproute_controller.go index d739b446f9..8a9b6a7f3c 100644 --- a/internal/controllers/gateway/httproute_controller.go +++ b/internal/controllers/gateway/httproute_controller.go @@ -10,7 +10,6 @@ import ( "time" "github.com/go-logr/logr" - "github.com/samber/lo" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -518,10 +517,6 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // HTTPRouteReconciler - Status Helpers // ----------------------------------------------------------------------------- -// httprouteParentKind indicates the only object KIND that this HTTPRoute -// implementation supports for route object parent references. -var httprouteParentKind = "Gateway" - // ensureGatewayReferenceStatus takes any number of Gateways that should be // considered "attached" to a given HTTPRoute and ensures that the status // for the HTTPRoute is updated appropriately. @@ -530,74 +525,18 @@ var httprouteParentKind = "Gateway" func (r *HTTPRouteReconciler) ensureGatewayReferenceStatusAdded( ctx context.Context, httproute *gatewayapi.HTTPRoute, gateways ...supportedGatewayWithCondition, ) (bool, ctrl.Result, error) { - // map the existing parentStatues to avoid duplications - parentStatuses := getParentStatuses(httproute, httproute.Status.Parents) - - // overlay the parent ref statuses for all new gateway references - statusChangesWereMade := false - for _, gateway := range gateways { - // build a new status for the parent Gateway - gatewayParentStatus := &gatewayapi.RouteParentStatus{ - ParentRef: gatewayapi.ParentReference{ - Group: (*gatewayapi.Group)(&gatewayv1.GroupVersion.Group), - Kind: util.StringToGatewayAPIKindPtr(httprouteParentKind), - Namespace: (*gatewayapi.Namespace)(&gateway.gateway.Namespace), - Name: gatewayapi.ObjectName(gateway.gateway.Name), - }, - ControllerName: GetControllerName(), - Conditions: []metav1.Condition{{ - Type: gateway.condition.Type, - Status: gateway.condition.Status, - ObservedGeneration: httproute.Generation, - LastTransitionTime: metav1.Now(), - Reason: gateway.condition.Reason, - }}, - } - if gateway.listenerName != "" { - gatewayParentStatus.ParentRef.SectionName = lo.ToPtr(gatewayapi.SectionName(gateway.listenerName)) - } - - key := fmt.Sprintf("%s/%s/%s", gateway.gateway.Namespace, gateway.gateway.Name, gateway.listenerName) - - // if the reference already exists and doesn't require any changes - // then just leave it alone. - if existingGatewayParentStatus, exists := parentStatuses[key]; exists { - // check if the parentRef and controllerName are equal, and whether the new condition is present in existing conditions - if reflect.DeepEqual(existingGatewayParentStatus.ParentRef, gatewayParentStatus.ParentRef) && - existingGatewayParentStatus.ControllerName == gatewayParentStatus.ControllerName && - lo.ContainsBy(existingGatewayParentStatus.Conditions, func(condition metav1.Condition) bool { - return sameCondition(gatewayParentStatus.Conditions[0], condition) - }) { - continue - } - } - - // otherwise overlay the new status on top the list of parentStatuses - parentStatuses[key] = gatewayParentStatus - statusChangesWereMade = true - } + parentStatuses, statusChangesWereMade := parentStatusesForRoute( + httproute, + httproute.Status.Parents, + gateways..., + ) parentStatuses, resolvedRefsChanged, err := r.setRouteConditionResolvedRefsCondition(ctx, httproute, parentStatuses) if err != nil { return false, ctrl.Result{}, err } - // initialize "programmed" condition to Unknown. - // do not update the condition If a "Programmed" condition is already present. - programmedConditionChanged := false - programmedConditionUnknown := metav1.Condition{ - Type: ConditionTypeProgrammed, - Status: metav1.ConditionUnknown, - Reason: string(ConditionReasonProgrammedUnknown), - ObservedGeneration: httproute.Generation, - LastTransitionTime: metav1.Now(), - } - for _, parentStatus := range parentStatuses { - if !parentStatusHasProgrammedCondition(parentStatus) { - programmedConditionChanged = true - parentStatus.Conditions = append(parentStatus.Conditions, programmedConditionUnknown) - } - } + programmedConditionChanged := initializeParentStatusesWithProgrammedCondition(httproute, parentStatuses) // if we didn't have to actually make any changes, no status update is needed if !statusChangesWereMade && !resolvedRefsChanged && !programmedConditionChanged { diff --git a/internal/controllers/gateway/route_parent_status.go b/internal/controllers/gateway/route_parent_status.go index f369f8f7d3..430cb303c0 100644 --- a/internal/controllers/gateway/route_parent_status.go +++ b/internal/controllers/gateway/route_parent_status.go @@ -2,11 +2,14 @@ package gateway import ( "fmt" + "reflect" "github.com/samber/lo" "github.com/samber/mo" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi" + "github.com/kong/kubernetes-ingress-controller/v3/internal/util" ) // getParentStatuses creates a parent status map for the provided route given the @@ -41,12 +44,8 @@ func routeParentStatusKey[routeT gatewayapi.RouteT]( } switch any(route).(type) { - case *gatewayapi.HTTPRoute: - return fmt.Sprintf("%s/%s/%s", - namespace, - parentRef.GetName(), - parentRef.GetSectionName().OrEmpty()) - case *gatewayapi.GRPCRoute: + case *gatewayapi.HTTPRoute, + *gatewayapi.GRPCRoute: return fmt.Sprintf("%s/%s/%s", namespace, parentRef.GetName(), @@ -126,3 +125,111 @@ func setRouteStatusParents[T gatewayapi.RouteT](route T, parents []gatewayapi.Ro r.Status.Parents = parents } } + +// parentStatusesForRoute returns route parent statuses for the given route +// and its supported gateways. +// It returns a map of parent statuses and a boolean indicating whether any +// changes were made. +func parentStatusesForRoute[routeT gatewayapi.RouteT]( + route routeT, + routeParentStatuses []gatewayapi.RouteParentStatus, + gateways ...supportedGatewayWithCondition, +) (map[string]*gatewayapi.RouteParentStatus, bool) { + // map the existing parentStatues to avoid duplications + parentStatuses := getParentStatuses(route, routeParentStatuses) + + // overlay the parent ref statuses for all new gateway references + statusChangesWereMade := false + for _, gateway := range gateways { + + // build a new status for the parent Gateway + gatewayParentStatus := gatewayParentStatusForRoute(route, gateway, withSectionName(gateway.listenerName)) + + // if the reference already exists and doesn't require any changes + // then just leave it alone. + parentRefKey := routeParentStatusKey(route, gateway) + if existing, ok := parentStatuses[parentRefKey]; ok { + // check if the parentRef and controllerName are equal, and whether + // the new condition is present in existing conditions + if reflect.DeepEqual(existing.ParentRef, gatewayParentStatus.ParentRef) && + existing.ControllerName == gatewayParentStatus.ControllerName && + lo.ContainsBy(existing.Conditions, func(condition metav1.Condition) bool { + return sameCondition(gatewayParentStatus.Conditions[0], condition) + }) { + continue + } + } + + // otherwise overlay the new status on top the list of parentStatuses + parentStatuses[parentRefKey] = gatewayParentStatus + statusChangesWereMade = true + } + return parentStatuses, statusChangesWereMade +} + +func withSectionName(sectionName string) func(*gatewayapi.RouteParentStatus) { + return func(routeParentStatus *gatewayapi.RouteParentStatus) { + if sectionName != "" { + routeParentStatus.ParentRef.SectionName = (*gatewayapi.SectionName)(§ionName) + } + } +} + +func gatewayParentStatusForRoute[routeT gatewayapi.RouteT]( + route routeT, + parentGateway supportedGatewayWithCondition, + opts ...func(*gatewayapi.RouteParentStatus), +) *gatewayapi.RouteParentStatus { + parentGVK := parentGateway.gateway.GroupVersionKind() + if parentGVK.Kind == "" { + parentGVK.Kind = gatewayapi.V1GatewayTypeMeta.Kind + } + if parentGVK.Group == "" { + parentGVK.Group = gatewayapi.V1GatewayTypeMeta.GroupVersionKind().Group + parentGateway.gateway.SetGroupVersionKind(parentGVK) + } + + var ( + parentRef = gatewayapi.ParentReference{ + Group: util.StringToTypedPtr[*gatewayapi.Group](parentGateway.gateway.GroupVersionKind().Group), + Kind: util.StringToTypedPtr[*gatewayapi.Kind](parentGateway.gateway.Kind), + Namespace: (*gatewayapi.Namespace)(&parentGateway.gateway.Namespace), + Name: gatewayapi.ObjectName(parentGateway.gateway.Name), + } + routeParentStatus = &gatewayapi.RouteParentStatus{ + ParentRef: parentRef, + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + { + Type: parentGateway.condition.Type, + Status: parentGateway.condition.Status, + ObservedGeneration: route.GetGeneration(), + LastTransitionTime: metav1.Now(), + Reason: parentGateway.condition.Reason, + }, + }, + } + ) + + for _, opt := range opts { + opt(routeParentStatus) + } + + return routeParentStatus +} + +func initializeParentStatusesWithProgrammedCondition[routeT gatewayapi.RouteT]( + route routeT, + parentStatuses map[string]*gatewayapi.RouteParentStatus, +) bool { + // do not update the condition if a "Programmed" condition is already present. + changed := false + programmedConditionUnknown := newProgrammedConditionUnknown(route) + for _, ps := range parentStatuses { + if !parentStatusHasProgrammedCondition(ps) { + ps.Conditions = append(ps.Conditions, programmedConditionUnknown) + changed = true + } + } + return changed +} diff --git a/internal/controllers/gateway/route_parent_status_test.go b/internal/controllers/gateway/route_parent_status_test.go index 486110fb48..d8e44145ab 100644 --- a/internal/controllers/gateway/route_parent_status_test.go +++ b/internal/controllers/gateway/route_parent_status_test.go @@ -3,12 +3,14 @@ package gateway import ( "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" - "github.com/samber/lo" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi" + "github.com/kong/kubernetes-ingress-controller/v3/internal/util/builder" ) func TestGetParentStatuses(t *testing.T) { @@ -29,13 +31,13 @@ func TestGetParentStatuses(t *testing.T) { RouteStatus: gatewayapi.RouteStatus{ Parents: []gatewayapi.RouteParentStatus{ { - ParentRef: gatewayapi.ParentReference{ - Group: lo.ToPtr(gatewayapi.Group("group")), - Kind: lo.ToPtr(gatewayapi.Kind("kind")), - Namespace: lo.ToPtr(gatewayapi.Namespace("namespace")), - Name: gatewayapi.ObjectName("name"), - SectionName: lo.ToPtr(gatewayapi.SectionName("section1")), - }, + ParentRef: builder.NewParentReference(). + Group("group"). + Kind("kind"). + Namespace("namespace"). + Name("name"). + SectionName("section1"). + Build(), }, }, }, @@ -43,13 +45,13 @@ func TestGetParentStatuses(t *testing.T) { }, want: map[string]*gatewayapi.RouteParentStatus{ "namespace/name/section1": { - ParentRef: gatewayapi.ParentReference{ - Group: lo.ToPtr(gatewayapi.Group("group")), - Kind: lo.ToPtr(gatewayapi.Kind("kind")), - Namespace: lo.ToPtr(gatewayapi.Namespace("namespace")), - Name: gatewayapi.ObjectName("name"), - SectionName: lo.ToPtr(gatewayapi.SectionName("section1")), - }, + ParentRef: builder.NewParentReference(). + Group("group"). + Kind("kind"). + Namespace("namespace"). + Name("name"). + SectionName("section1"). + Build(), }, }, }, @@ -79,12 +81,12 @@ func TestGetParentStatuses(t *testing.T) { RouteStatus: gatewayapi.RouteStatus{ Parents: []gatewayapi.RouteParentStatus{ { - ParentRef: gatewayapi.ParentReference{ - Group: lo.ToPtr(gatewayapi.Group("group")), - Kind: lo.ToPtr(gatewayapi.Kind("kind")), - Namespace: lo.ToPtr(gatewayapi.Namespace("namespace")), - Name: gatewayapi.ObjectName("name"), - }, + ParentRef: builder.NewParentReference(). + Group("group"). + Kind("kind"). + Namespace("namespace"). + Name("name"). + Build(), }, }, }, @@ -92,12 +94,12 @@ func TestGetParentStatuses(t *testing.T) { }, want: map[string]*gatewayapi.RouteParentStatus{ "namespace/name": { - ParentRef: gatewayapi.ParentReference{ - Group: lo.ToPtr(gatewayapi.Group("group")), - Kind: lo.ToPtr(gatewayapi.Kind("kind")), - Namespace: lo.ToPtr(gatewayapi.Namespace("namespace")), - Name: gatewayapi.ObjectName("name"), - }, + ParentRef: builder.NewParentReference(). + Group("group"). + Kind("kind"). + Namespace("namespace"). + Name("name"). + Build(), }, }, }, @@ -127,12 +129,12 @@ func TestGetParentStatuses(t *testing.T) { RouteStatus: gatewayapi.RouteStatus{ Parents: []gatewayapi.RouteParentStatus{ { - ParentRef: gatewayapi.ParentReference{ - Group: lo.ToPtr(gatewayapi.Group("group")), - Kind: lo.ToPtr(gatewayapi.Kind("kind")), - Namespace: lo.ToPtr(gatewayapi.Namespace("namespace")), - Name: gatewayapi.ObjectName("name"), - }, + ParentRef: builder.NewParentReference(). + Group("group"). + Kind("kind"). + Namespace("namespace"). + Name("name"). + Build(), }, }, }, @@ -140,12 +142,12 @@ func TestGetParentStatuses(t *testing.T) { }, want: map[string]*gatewayapi.RouteParentStatus{ "namespace/name": { - ParentRef: gatewayapi.ParentReference{ - Group: lo.ToPtr(gatewayapi.Group("group")), - Kind: lo.ToPtr(gatewayapi.Kind("kind")), - Namespace: lo.ToPtr(gatewayapi.Namespace("namespace")), - Name: gatewayapi.ObjectName("name"), - }, + ParentRef: builder.NewParentReference(). + Group("group"). + Kind("kind"). + Namespace("namespace"). + Name("name"). + Build(), }, }, }, @@ -175,12 +177,12 @@ func TestGetParentStatuses(t *testing.T) { RouteStatus: gatewayapi.RouteStatus{ Parents: []gatewayapi.RouteParentStatus{ { - ParentRef: gatewayapi.ParentReference{ - Group: lo.ToPtr(gatewayapi.Group("group")), - Kind: lo.ToPtr(gatewayapi.Kind("kind")), - Namespace: lo.ToPtr(gatewayapi.Namespace("namespace")), - Name: gatewayapi.ObjectName("name"), - }, + ParentRef: builder.NewParentReference(). + Group("group"). + Kind("kind"). + Namespace("namespace"). + Name("name"). + Build(), }, }, }, @@ -188,12 +190,12 @@ func TestGetParentStatuses(t *testing.T) { }, want: map[string]*gatewayapi.RouteParentStatus{ "namespace/name": { - ParentRef: gatewayapi.ParentReference{ - Group: lo.ToPtr(gatewayapi.Group("group")), - Kind: lo.ToPtr(gatewayapi.Kind("kind")), - Namespace: lo.ToPtr(gatewayapi.Namespace("namespace")), - Name: gatewayapi.ObjectName("name"), - }, + ParentRef: builder.NewParentReference(). + Group("group"). + Kind("kind"). + Namespace("namespace"). + Name("name"). + Build(), }, }, }, @@ -206,3 +208,697 @@ func TestGetParentStatuses(t *testing.T) { } }) } + +func TestParentStatusesForRoute(t *testing.T) { + t.Run("HTTPRoute", func(t *testing.T) { + tests := []struct { + name string + route *gatewayapi.HTTPRoute + statuses []gatewayapi.RouteParentStatus + gateways []supportedGatewayWithCondition + want map[string]*gatewayapi.RouteParentStatus + changed bool + }{ + { + name: "no Programmed condition on a route, update and return true", + route: &gatewayapi.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: uuid.NewString(), + Generation: 7, + }, + Status: gatewayapi.HTTPRouteStatus{ + RouteStatus: gatewayapi.RouteStatus{ + Parents: []gatewayapi.RouteParentStatus{ + { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + SectionName("section1"). + Build(), + ControllerName: GetControllerName(), + }, + }, + }, + }, + }, + gateways: []supportedGatewayWithCondition{ + { + gateway: &gatewayapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway1", + Namespace: "namespace", + }, + }, + condition: metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "Ready", + }, + listenerName: "section1", + }, + }, + want: map[string]*gatewayapi.RouteParentStatus{ + "namespace/gateway1/section1": { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + SectionName("section1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + changed: true, + }, + { + name: "Programmed condition is as exptected on a route, don't update and return false", + route: &gatewayapi.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: uuid.NewString(), + Generation: 7, + }, + Status: gatewayapi.HTTPRouteStatus{ + RouteStatus: gatewayapi.RouteStatus{ + Parents: []gatewayapi.RouteParentStatus{ + { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + SectionName("section1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + }, + }, + }, + gateways: []supportedGatewayWithCondition{ + { + gateway: &gatewayapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway1", + Namespace: "namespace", + }, + }, + condition: metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "Ready", + }, + listenerName: "section1", + }, + }, + want: map[string]*gatewayapi.RouteParentStatus{ + "namespace/gateway1/section1": { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + SectionName("section1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + changed: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, changed := parentStatusesForRoute(tt.route, tt.route.Status.Parents, tt.gateways...) + ignoreLastTransitionTime := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") + if !cmp.Equal(tt.want, got, ignoreLastTransitionTime) { + assert.Equal(t, tt.want, got) + } + assert.Equal(t, tt.changed, changed) + }) + } + }) + + t.Run("UDPRoute", func(t *testing.T) { + tests := []struct { + name string + route *gatewayapi.UDPRoute + statuses []gatewayapi.RouteParentStatus + gateways []supportedGatewayWithCondition + want map[string]*gatewayapi.RouteParentStatus + changed bool + }{ + { + name: "no Programmed condition on a route, update and return true", + route: &gatewayapi.UDPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: uuid.NewString(), + Generation: 7, + }, + Status: gatewayapi.UDPRouteStatus{ + RouteStatus: gatewayapi.RouteStatus{ + Parents: []gatewayapi.RouteParentStatus{ + { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + Build(), + ControllerName: GetControllerName(), + }, + }, + }, + }, + }, + gateways: []supportedGatewayWithCondition{ + { + gateway: &gatewayapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway1", + Namespace: "namespace", + }, + }, + condition: metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "Ready", + }, + }, + }, + want: map[string]*gatewayapi.RouteParentStatus{ + "namespace/gateway1": { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + changed: true, + }, + { + name: "Programmed condition is as exptected on a route, don't update and return false", + route: &gatewayapi.UDPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: uuid.NewString(), + Generation: 7, + }, + Status: gatewayapi.UDPRouteStatus{ + RouteStatus: gatewayapi.RouteStatus{ + Parents: []gatewayapi.RouteParentStatus{ + { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + }, + }, + }, + gateways: []supportedGatewayWithCondition{ + { + gateway: &gatewayapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway1", + Namespace: "namespace", + }, + }, + condition: metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "Ready", + }, + }, + }, + want: map[string]*gatewayapi.RouteParentStatus{ + "namespace/gateway1": { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + changed: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, changed := parentStatusesForRoute(tt.route, tt.route.Status.Parents, tt.gateways...) + ignoreLastTransitionTime := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") + if !cmp.Equal(tt.want, got, ignoreLastTransitionTime) { + assert.Equal(t, tt.want, got) + } + assert.Equal(t, tt.changed, changed) + }) + } + }) + + t.Run("TCPRoute", func(t *testing.T) { + tests := []struct { + name string + route *gatewayapi.TCPRoute + statuses []gatewayapi.RouteParentStatus + gateways []supportedGatewayWithCondition + want map[string]*gatewayapi.RouteParentStatus + changed bool + }{ + { + name: "no Programmed condition on a route, update and return true", + route: &gatewayapi.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: uuid.NewString(), + Generation: 7, + }, + Status: gatewayapi.TCPRouteStatus{ + RouteStatus: gatewayapi.RouteStatus{ + Parents: []gatewayapi.RouteParentStatus{ + { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + Build(), + ControllerName: GetControllerName(), + }, + }, + }, + }, + }, + gateways: []supportedGatewayWithCondition{ + { + gateway: &gatewayapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway1", + Namespace: "namespace", + }, + }, + condition: metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "Ready", + }, + }, + }, + want: map[string]*gatewayapi.RouteParentStatus{ + "namespace/gateway1": { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + changed: true, + }, + { + name: "Programmed condition is as exptected on a route, don't update and return false", + route: &gatewayapi.TCPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: uuid.NewString(), + Generation: 7, + }, + Status: gatewayapi.TCPRouteStatus{ + RouteStatus: gatewayapi.RouteStatus{ + Parents: []gatewayapi.RouteParentStatus{ + { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + }, + }, + }, + gateways: []supportedGatewayWithCondition{ + { + gateway: &gatewayapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway1", + Namespace: "namespace", + }, + }, + condition: metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "Ready", + }, + }, + }, + want: map[string]*gatewayapi.RouteParentStatus{ + "namespace/gateway1": { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + changed: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, changed := parentStatusesForRoute(tt.route, tt.route.Status.Parents, tt.gateways...) + ignoreLastTransitionTime := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") + if !cmp.Equal(tt.want, got, ignoreLastTransitionTime) { + assert.Equal(t, tt.want, got) + } + assert.Equal(t, tt.changed, changed) + }) + } + }) + + t.Run("TLSRoute", func(t *testing.T) { + tests := []struct { + name string + route *gatewayapi.TLSRoute + statuses []gatewayapi.RouteParentStatus + gateways []supportedGatewayWithCondition + want map[string]*gatewayapi.RouteParentStatus + changed bool + }{ + { + name: "no Programmed condition on a route, update and return true", + route: &gatewayapi.TLSRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: uuid.NewString(), + Generation: 7, + }, + Status: gatewayapi.TLSRouteStatus{ + RouteStatus: gatewayapi.RouteStatus{ + Parents: []gatewayapi.RouteParentStatus{ + { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + Build(), + ControllerName: GetControllerName(), + }, + }, + }, + }, + }, + gateways: []supportedGatewayWithCondition{ + { + gateway: &gatewayapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway1", + Namespace: "namespace", + }, + }, + condition: metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "Ready", + }, + }, + }, + want: map[string]*gatewayapi.RouteParentStatus{ + "namespace/gateway1": { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + changed: true, + }, + { + name: "Programmed condition is as exptected on a route, don't update and return false", + route: &gatewayapi.TLSRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: uuid.NewString(), + Generation: 7, + }, + Status: gatewayapi.TLSRouteStatus{ + RouteStatus: gatewayapi.RouteStatus{ + Parents: []gatewayapi.RouteParentStatus{ + { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + }, + }, + }, + gateways: []supportedGatewayWithCondition{ + { + gateway: &gatewayapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway1", + Namespace: "namespace", + }, + }, + condition: metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "Ready", + }, + }, + }, + want: map[string]*gatewayapi.RouteParentStatus{ + "namespace/gateway1": { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + changed: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, changed := parentStatusesForRoute(tt.route, tt.route.Status.Parents, tt.gateways...) + ignoreLastTransitionTime := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") + if !cmp.Equal(tt.want, got, ignoreLastTransitionTime) { + assert.Equal(t, tt.want, got) + } + assert.Equal(t, tt.changed, changed) + }) + } + }) + + t.Run("GRPCRoute", func(t *testing.T) { + tests := []struct { + name string + route *gatewayapi.GRPCRoute + statuses []gatewayapi.RouteParentStatus + gateways []supportedGatewayWithCondition + want map[string]*gatewayapi.RouteParentStatus + changed bool + }{ + { + name: "no Programmed condition on a route, update and return true", + route: &gatewayapi.GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: uuid.NewString(), + Generation: 7, + }, + Status: gatewayapi.GRPCRouteStatus{ + RouteStatus: gatewayapi.RouteStatus{ + Parents: []gatewayapi.RouteParentStatus{ + { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + SectionName("section1"). + Build(), + ControllerName: GetControllerName(), + }, + }, + }, + }, + }, + gateways: []supportedGatewayWithCondition{ + { + gateway: &gatewayapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway1", + Namespace: "namespace", + }, + }, + condition: metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "Ready", + }, + listenerName: "section1", + }, + }, + want: map[string]*gatewayapi.RouteParentStatus{ + "namespace/gateway1/section1": { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + SectionName("section1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + changed: true, + }, + { + name: "Programmed condition is as exptected on a route, don't update and return false", + route: &gatewayapi.GRPCRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: uuid.NewString(), + Namespace: uuid.NewString(), + Generation: 7, + }, + Status: gatewayapi.GRPCRouteStatus{ + RouteStatus: gatewayapi.RouteStatus{ + Parents: []gatewayapi.RouteParentStatus{ + { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + SectionName("section1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + }, + }, + }, + gateways: []supportedGatewayWithCondition{ + { + gateway: &gatewayapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway1", + Namespace: "namespace", + }, + }, + condition: metav1.Condition{ + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "Ready", + }, + listenerName: "section1", + }, + }, + want: map[string]*gatewayapi.RouteParentStatus{ + "namespace/gateway1/section1": { + ParentRef: builder.NewParentReference(). + Group("gateway.networking.k8s.io"). + Kind("Gateway"). + Namespace("namespace"). + Name("gateway1"). + SectionName("section1"). + Build(), + ControllerName: GetControllerName(), + Conditions: []metav1.Condition{ + newCondition("Ready", metav1.ConditionTrue, "Ready", 7), + }, + }, + }, + changed: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, changed := parentStatusesForRoute(tt.route, tt.route.Status.Parents, tt.gateways...) + ignoreLastTransitionTime := cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime") + if !cmp.Equal(tt.want, got, ignoreLastTransitionTime) { + assert.Equal(t, tt.want, got) + } + assert.Equal(t, tt.changed, changed) + }) + } + }) +} diff --git a/internal/controllers/gateway/route_utils.go b/internal/controllers/gateway/route_utils.go index fc47451d40..90615d7bb4 100644 --- a/internal/controllers/gateway/route_utils.go +++ b/internal/controllers/gateway/route_utils.go @@ -724,126 +724,6 @@ func isHTTPReferenceGranted(grantSpec gatewayapi.ReferenceGrantSpec, backendRef return false } -// sameCondition returns true if the conditions in parameter has the same type, status, reason and message. -func sameCondition(a, b metav1.Condition) bool { - return a.Type == b.Type && - a.Status == b.Status && - a.Reason == b.Reason && - a.Message == b.Message && - a.ObservedGeneration == b.ObservedGeneration -} - -func setRouteParentStatusCondition(parentStatus *gatewayapi.RouteParentStatus, newCondition metav1.Condition) bool { - var conditionFound, changed bool - for i, condition := range parentStatus.Conditions { - if condition.Type == newCondition.Type { - conditionFound = true - if !sameCondition(condition, newCondition) { - parentStatus.Conditions[i] = newCondition - changed = true - } - } - } - - if !conditionFound { - parentStatus.Conditions = append(parentStatus.Conditions, newCondition) - changed = true - } - return changed -} - -func parentStatusHasProgrammedCondition(parentStatus *gatewayapi.RouteParentStatus) bool { - for _, condition := range parentStatus.Conditions { - if condition.Type == ConditionTypeProgrammed { - return true - } - } - return false -} - -// ensureParentsProgrammedCondition ensures that provided route's parent statuses -// have Programmed condition set properly. It returns a boolean flag indicating -// whether an update to the provided route has been performed. -// -// Use the condition argument to specify the Reason, Status and Message. -// Type will be set to Programmed whereas ObservedGeneration and LastTransitionTime -// will be set accordingly based on the route's generation and current time. -func ensureParentsProgrammedCondition[ - routeT gatewayapi.RouteT, -]( - ctx context.Context, - client client.SubResourceWriter, - route routeT, - routeParentStatuses []gatewayapi.RouteParentStatus, - gateways []supportedGatewayWithCondition, - condition metav1.Condition, -) (bool, error) { - // map the existing parentStatues to avoid duplications - parentStatuses := getParentStatuses(route, routeParentStatuses) - - condition.Type = ConditionTypeProgrammed - condition.ObservedGeneration = route.GetGeneration() - condition.LastTransitionTime = metav1.Now() - - statusChanged := false - for _, g := range gateways { - gateway := g.gateway - - parentRefKey := routeParentStatusKey(route, g) - parentStatus, ok := parentStatuses[parentRefKey] - if ok { - // update existing parent in status. - changed := setRouteParentStatusCondition(parentStatus, condition) - if changed { - parentStatuses[parentRefKey] = parentStatus - setRouteParentInStatusForParent(route, *parentStatus, g) - } - statusChanged = statusChanged || changed - } else { - // add a new parent if the parent is not found in status. - newParentStatus := gatewayapi.RouteParentStatus{ - ParentRef: gatewayapi.ParentReference{ - Namespace: lo.ToPtr(gatewayapi.Namespace(gateway.Namespace)), - Name: gatewayapi.ObjectName(gateway.Name), - Kind: lo.ToPtr(gatewayapi.Kind("Gateway")), - Group: lo.ToPtr(gatewayapi.Group(gatewayv1.GroupName)), - SectionName: func() *gatewayapi.SectionName { - // We don't need to check whether the listener matches route's spec - // because that should already be done via getSupportedGatewayForRoute - // at this point. - if g.listenerName != "" { - return lo.ToPtr(gatewayapi.SectionName(g.listenerName)) - } - return nil - }(), - - // TODO: set port after gateway port matching implemented: - // https://github.com/Kong/kubernetes-ingress-controller/issues/3016 - }, - ControllerName: GetControllerName(), - Conditions: []metav1.Condition{ - condition, - }, - } - setRouteParentInStatusForParent(route, newParentStatus, g) - - routeParentStatuses = append(routeParentStatuses, newParentStatus) - parentStatuses[parentRefKey] = &newParentStatus - statusChanged = true - } - } - - // update status if needed. - if statusChanged { - if err := client.Update(ctx, route); err != nil { - return false, err - } - return true, nil - } - // no need to update if no status is changed. - return false, nil -} - // setRouteParentInStatusForParent checks if the provided route Status, contains // status for the provided parent and if it does it sets it to the provided // RouteStatusParent. If it does not then it appends the provided RouteStatusParent diff --git a/internal/controllers/gateway/route_utils_test.go b/internal/controllers/gateway/route_utils_test.go index 26c8d2364e..985ed7d1cd 100644 --- a/internal/controllers/gateway/route_utils_test.go +++ b/internal/controllers/gateway/route_utils_test.go @@ -45,15 +45,15 @@ func TestFilterHostnames(t *testing.T) { Listeners: []gatewayapi.Listener{ { Name: "listener-1", - Hostname: util.StringToGatewayAPIHostnamePtr("very.specific.com"), + Hostname: util.StringToTypedPtr[*gatewayapi.Hostname]("very.specific.com"), }, { Name: "listener-2", - Hostname: util.StringToGatewayAPIHostnamePtr("*.wildcard.io"), + Hostname: util.StringToTypedPtr[*gatewayapi.Hostname]("*.wildcard.io"), }, { Name: "listener-3", - Hostname: util.StringToGatewayAPIHostnamePtr("*.anotherwildcard.io"), + Hostname: util.StringToTypedPtr[*gatewayapi.Hostname]("*.anotherwildcard.io"), }, { Name: "listener-4", @@ -81,16 +81,16 @@ func TestFilterHostnames(t *testing.T) { httpRoute: &gatewayapi.HTTPRoute{ Spec: gatewayapi.HTTPRouteSpec{ Hostnames: []gatewayapi.Hostname{ - util.StringToGatewayAPIHostname("*.anotherwildcard.io"), - util.StringToGatewayAPIHostname("*.nonmatchingwildcard.io"), - util.StringToGatewayAPIHostname("very.specific.com"), + "*.anotherwildcard.io", + "*.nonmatchingwildcard.io", + "very.specific.com", }, }, }, expectedHTTPRoute: &gatewayapi.HTTPRoute{ Spec: gatewayapi.HTTPRouteSpec{ Hostnames: []gatewayapi.Hostname{ - util.StringToGatewayAPIHostname("very.specific.com"), + "very.specific.com", }, }, }, @@ -106,15 +106,15 @@ func TestFilterHostnames(t *testing.T) { httpRoute: &gatewayapi.HTTPRoute{ Spec: gatewayapi.HTTPRouteSpec{ Hostnames: []gatewayapi.Hostname{ - util.StringToGatewayAPIHostname("non.matching.com"), - util.StringToGatewayAPIHostname("*.specific.com"), + "non.matching.com", + "*.specific.com", }, }, }, expectedHTTPRoute: &gatewayapi.HTTPRoute{ Spec: gatewayapi.HTTPRouteSpec{ Hostnames: []gatewayapi.Hostname{ - util.StringToGatewayAPIHostname("very.specific.com"), + "very.specific.com", }, }, }, @@ -130,20 +130,20 @@ func TestFilterHostnames(t *testing.T) { httpRoute: &gatewayapi.HTTPRoute{ Spec: gatewayapi.HTTPRouteSpec{ Hostnames: []gatewayapi.Hostname{ - util.StringToGatewayAPIHostname("non.matching.com"), - util.StringToGatewayAPIHostname("wildcard.io"), - util.StringToGatewayAPIHostname("foo.wildcard.io"), - util.StringToGatewayAPIHostname("bar.wildcard.io"), - util.StringToGatewayAPIHostname("foo.bar.wildcard.io"), + "non.matching.com", + "wildcard.io", + "foo.wildcard.io", + "bar.wildcard.io", + "foo.bar.wildcard.io", }, }, }, expectedHTTPRoute: &gatewayapi.HTTPRoute{ Spec: gatewayapi.HTTPRouteSpec{ Hostnames: []gatewayapi.Hostname{ - util.StringToGatewayAPIHostname("foo.wildcard.io"), - util.StringToGatewayAPIHostname("bar.wildcard.io"), - util.StringToGatewayAPIHostname("foo.bar.wildcard.io"), + "foo.wildcard.io", + "bar.wildcard.io", + "foo.bar.wildcard.io", }, }, }, @@ -159,14 +159,14 @@ func TestFilterHostnames(t *testing.T) { httpRoute: &gatewayapi.HTTPRoute{ Spec: gatewayapi.HTTPRouteSpec{ Hostnames: []gatewayapi.Hostname{ - util.StringToGatewayAPIHostname("*.anotherwildcard.io"), + "*.anotherwildcard.io", }, }, }, expectedHTTPRoute: &gatewayapi.HTTPRoute{ Spec: gatewayapi.HTTPRouteSpec{ Hostnames: []gatewayapi.Hostname{ - util.StringToGatewayAPIHostname("*.anotherwildcard.io"), + "*.anotherwildcard.io", }, }, }, @@ -200,8 +200,8 @@ func TestFilterHostnames(t *testing.T) { httpRoute: &gatewayapi.HTTPRoute{ Spec: gatewayapi.HTTPRouteSpec{ Hostnames: []gatewayapi.Hostname{ - util.StringToGatewayAPIHostname("specific.but.wrong.com"), - util.StringToGatewayAPIHostname("wildcard.io"), + "specific.but.wrong.com", + "wildcard.io", }, }, }, diff --git a/internal/controllers/gateway/tcproute_controller.go b/internal/controllers/gateway/tcproute_controller.go index f498ad1ec1..5862240ee6 100644 --- a/internal/controllers/gateway/tcproute_controller.go +++ b/internal/controllers/gateway/tcproute_controller.go @@ -8,7 +8,6 @@ import ( "time" "github.com/go-logr/logr" - "github.com/samber/lo" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -24,11 +23,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers" "github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi" - "github.com/kong/kubernetes-ingress-controller/v3/internal/util" k8sobj "github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object" "github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object/status" ) @@ -431,10 +428,6 @@ func (r *TCPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // TCPRouteReconciler - Status Helpers // ----------------------------------------------------------------------------- -// tcprouteParentKind indicates the only object KIND that this TCPRoute -// implementation supports for route object parent references. -var tcprouteParentKind = "Gateway" - // ensureGatewayReferenceStatus takes any number of Gateways that should be // considered "attached" to a given TCPRoute and ensures that the status // for the TCPRoute is updated appropriately. @@ -445,65 +438,13 @@ func (r *TCPRouteReconciler) ensureGatewayReferenceStatusAdded( tcproute *gatewayapi.TCPRoute, gateways ...supportedGatewayWithCondition, ) (bool, ctrl.Result, error) { - // map the existing parentStatues to avoid duplications - parentStatuses := getParentStatuses(tcproute, tcproute.Status.Parents) - - // overlay the parent ref statuses for all new gateway references - statusChangesWereMade := false - for _, gateway := range gateways { - // build a new status for the parent Gateway - gatewayParentStatus := &gatewayapi.RouteParentStatus{ - ParentRef: gatewayapi.ParentReference{ - Group: (*gatewayapi.Group)(&gatewayv1beta1.GroupVersion.Group), - Kind: util.StringToGatewayAPIKindPtr(tcprouteParentKind), - Namespace: (*gatewayapi.Namespace)(&gateway.gateway.Namespace), - Name: (gatewayapi.ObjectName)(gateway.gateway.Name), - }, - ControllerName: GetControllerName(), - Conditions: []metav1.Condition{{ - Type: string(gatewayapi.RouteConditionAccepted), - Status: metav1.ConditionTrue, - ObservedGeneration: tcproute.Generation, - LastTransitionTime: metav1.Now(), - Reason: string(gatewayapi.RouteReasonAccepted), - }}, - } - - // if the reference already exists and doesn't require any changes - // then just leave it alone. - parentRefKey := gateway.gateway.Namespace + "/" + gateway.gateway.Name - if existingGatewayParentStatus, exists := parentStatuses[parentRefKey]; exists { - // check if the parentRef and controllerName are equal, and whether the new condition is present in existing conditions - if reflect.DeepEqual(existingGatewayParentStatus.ParentRef, gatewayParentStatus.ParentRef) && - existingGatewayParentStatus.ControllerName == gatewayParentStatus.ControllerName && - lo.ContainsBy(existingGatewayParentStatus.Conditions, func(condition metav1.Condition) bool { - return sameCondition(gatewayParentStatus.Conditions[0], condition) - }) { - continue - } - } - - // otherwise overlay the new status on top the list of parentStatuses - parentStatuses[parentRefKey] = gatewayParentStatus - statusChangesWereMade = true - } + parentStatuses, statusChangesWereMade := parentStatusesForRoute( + tcproute, + tcproute.Status.Parents, + gateways..., + ) - // initialize "programmed" condition to Unknown. - // do not update the condition If a "Programmed" condition is already present. - programmedConditionChanged := false - programmedConditionUnknown := metav1.Condition{ - Type: ConditionTypeProgrammed, - Status: metav1.ConditionUnknown, - Reason: string(ConditionReasonProgrammedUnknown), - ObservedGeneration: tcproute.Generation, - LastTransitionTime: metav1.Now(), - } - for _, parentStatus := range parentStatuses { - if !parentStatusHasProgrammedCondition(parentStatus) { - programmedConditionChanged = true - parentStatus.Conditions = append(parentStatus.Conditions, programmedConditionUnknown) - } - } + programmedConditionChanged := initializeParentStatusesWithProgrammedCondition(tcproute, parentStatuses) // if we didn't have to actually make any changes, no status update is needed if !statusChangesWereMade && !programmedConditionChanged { diff --git a/internal/controllers/gateway/tlsroute_controller.go b/internal/controllers/gateway/tlsroute_controller.go index 51bfbd10f0..488ab82fdc 100644 --- a/internal/controllers/gateway/tlsroute_controller.go +++ b/internal/controllers/gateway/tlsroute_controller.go @@ -8,7 +8,6 @@ import ( "time" "github.com/go-logr/logr" - "github.com/samber/lo" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -27,7 +26,6 @@ import ( "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers" "github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi" - "github.com/kong/kubernetes-ingress-controller/v3/internal/util" k8sobj "github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object" "github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object/status" ) @@ -416,10 +414,6 @@ func (r *TLSRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // TLSRouteReconciler - Status Helpers // ----------------------------------------------------------------------------- -// tlsrouteParentKind indicates the only object KIND that this TLSRoute -// implementation supports for route object parent references. -var tlsrouteParentKind = "Gateway" - // ensureGatewayReferenceStatus takes any number of Gateways that should be // considered "attached" to a given TLSRoute and ensures that the status // for the TLSRoute is updated appropriately. @@ -428,70 +422,13 @@ var tlsrouteParentKind = "Gateway" func (r *TLSRouteReconciler) ensureGatewayReferenceStatusAdded( ctx context.Context, tlsroute *gatewayapi.TLSRoute, gateways ...supportedGatewayWithCondition, ) (bool, ctrl.Result, error) { - // map the existing parentStatues to avoid duplications - parentStatuses := getParentStatuses(tlsroute, tlsroute.Status.Parents) - - // overlay the parent ref statuses for all new gateway references - statusChangesWereMade := false - for _, gateway := range gateways { - // build a new status for the parent Gateway - gatewayParentStatus := &gatewayapi.RouteParentStatus{ - ParentRef: gatewayapi.ParentReference{ - Group: (*gatewayapi.Group)(&gatewayv1alpha2.GroupVersion.Group), - Kind: util.StringToGatewayAPIKindPtr(tlsrouteParentKind), - Namespace: (*gatewayapi.Namespace)(&gateway.gateway.Namespace), - Name: gatewayapi.ObjectName(gateway.gateway.Name), - }, - ControllerName: GetControllerName(), - Conditions: []metav1.Condition{{ - Type: string(gatewayapi.RouteConditionAccepted), - Status: metav1.ConditionTrue, - ObservedGeneration: tlsroute.Generation, - LastTransitionTime: metav1.Now(), - Reason: string(gatewayapi.RouteReasonAccepted), - }}, - } - - if gateway.listenerName != "" { - sectionName := gatewayapi.SectionName(gateway.listenerName) - gatewayParentStatus.ParentRef.SectionName = §ionName - } - - // if the reference already exists and doesn't require any changes - // then just leave it alone. - parentRefKey := gateway.gateway.Namespace + "/" + gateway.gateway.Name - if existingGatewayParentStatus, exists := parentStatuses[parentRefKey]; exists { - // check if the parentRef and controllerName are equal, and whether the new condition is present in existing conditions - if reflect.DeepEqual(existingGatewayParentStatus.ParentRef, gatewayParentStatus.ParentRef) && - existingGatewayParentStatus.ControllerName == gatewayParentStatus.ControllerName && - lo.ContainsBy(existingGatewayParentStatus.Conditions, func(condition metav1.Condition) bool { - return sameCondition(gatewayParentStatus.Conditions[0], condition) - }) { - continue - } - } - - // otherwise overlay the new status on top the list of parentStatuses - parentStatuses[parentRefKey] = gatewayParentStatus - statusChangesWereMade = true - } + parentStatuses, statusChangesWereMade := parentStatusesForRoute( + tlsroute, + tlsroute.Status.Parents, + gateways..., + ) - // initialize "programmed" condition to Unknown. - // do not update the condition If a "Programmed" condition is already present. - programmedConditionChanged := false - programmedConditionUnknown := metav1.Condition{ - Type: ConditionTypeProgrammed, - Status: metav1.ConditionUnknown, - Reason: string(ConditionReasonProgrammedUnknown), - ObservedGeneration: tlsroute.Generation, - LastTransitionTime: metav1.Now(), - } - for _, parentStatus := range parentStatuses { - if !parentStatusHasProgrammedCondition(parentStatus) { - programmedConditionChanged = true - parentStatus.Conditions = append(parentStatus.Conditions, programmedConditionUnknown) - } - } + programmedConditionChanged := initializeParentStatusesWithProgrammedCondition(tlsroute, parentStatuses) // if we didn't have to actually make any changes, no status update is needed if !statusChangesWereMade && !programmedConditionChanged { diff --git a/internal/controllers/gateway/udproute_controller.go b/internal/controllers/gateway/udproute_controller.go index d2ea89cd0f..8c0983d34c 100644 --- a/internal/controllers/gateway/udproute_controller.go +++ b/internal/controllers/gateway/udproute_controller.go @@ -8,7 +8,6 @@ import ( "time" "github.com/go-logr/logr" - "github.com/samber/lo" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -27,7 +26,6 @@ import ( "github.com/kong/kubernetes-ingress-controller/v3/internal/controllers" "github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi" - "github.com/kong/kubernetes-ingress-controller/v3/internal/util" k8sobj "github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object" "github.com/kong/kubernetes-ingress-controller/v3/internal/util/kubernetes/object/status" ) @@ -426,10 +424,6 @@ func (r *UDPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // UDPRouteReconciler - Status Helpers // ----------------------------------------------------------------------------- -// udprouteParentKind indicates the only object KIND that this UDPRoute -// implementation supports for route object parent references. -var udprouteParentKind = "Gateway" - // ensureGatewayReferenceStatus takes any number of Gateways that should be // considered "attached" to a given UDPRoute and ensures that the status // for the UDPRoute is updated appropriately. @@ -438,65 +432,13 @@ var udprouteParentKind = "Gateway" func (r *UDPRouteReconciler) ensureGatewayReferenceStatusAdded( ctx context.Context, udproute *gatewayapi.UDPRoute, gateways ...supportedGatewayWithCondition, ) (bool, ctrl.Result, error) { - // map the existing parentStatues to avoid duplications - parentStatuses := getParentStatuses(udproute, udproute.Status.Parents) - - // overlay the parent ref statuses for all new gateway references - statusChangesWereMade := false - for _, gateway := range gateways { - // build a new status for the parent Gateway - gatewayParentStatus := &gatewayapi.RouteParentStatus{ - ParentRef: gatewayapi.ParentReference{ - Group: (*gatewayapi.Group)(&gatewayv1alpha2.GroupVersion.Group), - Kind: util.StringToGatewayAPIKindPtr(udprouteParentKind), - Namespace: (*gatewayapi.Namespace)(&gateway.gateway.Namespace), - Name: gatewayapi.ObjectName(gateway.gateway.Name), - }, - ControllerName: GetControllerName(), - Conditions: []metav1.Condition{{ - Type: string(gatewayapi.RouteConditionAccepted), - Status: metav1.ConditionTrue, - ObservedGeneration: udproute.Generation, - LastTransitionTime: metav1.Now(), - Reason: string(gatewayapi.RouteReasonAccepted), - }}, - } - - // if the reference already exists and doesn't require any changes - // then just leave it alone. - parentRefKey := gateway.gateway.Namespace + "/" + gateway.gateway.Name - if existingGatewayParentStatus, exists := parentStatuses[parentRefKey]; exists { - // check if the parentRef and controllerName are equal, and whether the new condition is present in existing conditions - if reflect.DeepEqual(existingGatewayParentStatus.ParentRef, gatewayParentStatus.ParentRef) && - existingGatewayParentStatus.ControllerName == gatewayParentStatus.ControllerName && - lo.ContainsBy(existingGatewayParentStatus.Conditions, func(condition metav1.Condition) bool { - return sameCondition(gatewayParentStatus.Conditions[0], condition) - }) { - continue - } - } - - // otherwise overlay the new status on top the list of parentStatuses - parentStatuses[parentRefKey] = gatewayParentStatus - statusChangesWereMade = true - } + parentStatuses, statusChangesWereMade := parentStatusesForRoute( + udproute, + udproute.Status.Parents, + gateways..., + ) - // initialize "programmed" condition to Unknown. - // do not update the condition If a "Programmed" condition is already present. - programmedConditionChanged := false - programmedConditionUnknown := metav1.Condition{ - Type: ConditionTypeProgrammed, - Status: metav1.ConditionUnknown, - Reason: string(ConditionReasonProgrammedUnknown), - ObservedGeneration: udproute.Generation, - LastTransitionTime: metav1.Now(), - } - for _, parentStatus := range parentStatuses { - if !parentStatusHasProgrammedCondition(parentStatus) { - programmedConditionChanged = true - parentStatus.Conditions = append(parentStatus.Conditions, programmedConditionUnknown) - } - } + programmedConditionChanged := initializeParentStatusesWithProgrammedCondition(udproute, parentStatuses) // if we didn't have to actually make any changes, no status update is needed if !statusChangesWereMade && !programmedConditionChanged { diff --git a/internal/gatewayapi/typemeta.go b/internal/gatewayapi/typemeta.go index dd9ae42042..814676422e 100644 --- a/internal/gatewayapi/typemeta.go +++ b/internal/gatewayapi/typemeta.go @@ -12,31 +12,16 @@ var V1GatewayTypeMeta = metav1.TypeMeta{ Kind: "Gateway", } -var V1beta1GatewayTypeMeta = metav1.TypeMeta{ - APIVersion: gatewayv1beta1.GroupVersion.String(), - Kind: "Gateway", -} - var V1GatewayClassTypeMeta = metav1.TypeMeta{ APIVersion: gatewayv1.GroupVersion.String(), Kind: "GatewayClass", } -var V1beta1GatewayClassTypeMeta = metav1.TypeMeta{ - APIVersion: gatewayv1beta1.GroupVersion.String(), - Kind: "GatewayClass", -} - var V1HTTPRouteTypeMeta = metav1.TypeMeta{ APIVersion: gatewayv1.GroupVersion.String(), Kind: "HTTPRoute", } -var V1beta1HTTPRouteTypeMeta = metav1.TypeMeta{ - APIVersion: gatewayv1beta1.GroupVersion.String(), - Kind: "HTTPRoute", -} - var ReferenceGrantTypeMeta = metav1.TypeMeta{ APIVersion: gatewayv1beta1.GroupVersion.String(), Kind: "ReferenceGrant", diff --git a/internal/util/builder/backendref.go b/internal/util/builder/backendref.go index d53e6bd0cc..cd28021577 100644 --- a/internal/util/builder/backendref.go +++ b/internal/util/builder/backendref.go @@ -19,7 +19,7 @@ func NewBackendRef(name string) *BackendRefBuilder { backendRef: gatewayapi.BackendRef{ BackendObjectReference: gatewayapi.BackendObjectReference{ Name: gatewayapi.ObjectName(name), - Kind: util.StringToGatewayAPIKindV1Alpha2Ptr("Service"), // default value + Kind: util.StringToGatewayAPIKindPtr("Service"), // default value }, }, } diff --git a/internal/util/builder/parentref.go b/internal/util/builder/parentref.go new file mode 100644 index 0000000000..88e579275d --- /dev/null +++ b/internal/util/builder/parentref.go @@ -0,0 +1,53 @@ +package builder + +import ( + "github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi" +) + +// ParentReferenceBuilder is a builder for constructing gatewayapi.ParentReference objects. +// Primarily used for testing. +type ParentReferenceBuilder struct { + pr gatewayapi.ParentReference +} + +// NewParentReference creates a new instance of ParentReferenceBuilder. +func NewParentReference() *ParentReferenceBuilder { + return &ParentReferenceBuilder{ + pr: gatewayapi.ParentReference{}, + } +} + +// Group sets the Group field of the ParentReference. +func (b *ParentReferenceBuilder) Group(group gatewayapi.Group) *ParentReferenceBuilder { + b.pr.Group = &group + return b +} + +// Kind sets the Kind field of the ParentReference. +func (b *ParentReferenceBuilder) Kind(kind gatewayapi.Kind) *ParentReferenceBuilder { + b.pr.Kind = &kind + return b +} + +// Namespace sets the Namespace field of the ParentReference. +func (b *ParentReferenceBuilder) Namespace(namespace gatewayapi.Namespace) *ParentReferenceBuilder { + b.pr.Namespace = &namespace + return b +} + +// Name sets the Name field of the ParentReference. +func (b *ParentReferenceBuilder) Name(name gatewayapi.ObjectName) *ParentReferenceBuilder { + b.pr.Name = name + return b +} + +// SectionName sets the SectionName field of the ParentReference. +func (b *ParentReferenceBuilder) SectionName(sectionName gatewayapi.SectionName) *ParentReferenceBuilder { + b.pr.SectionName = §ionName + return b +} + +// Build returns the configured ParentReference. +func (b *ParentReferenceBuilder) Build() gatewayapi.ParentReference { + return b.pr +} diff --git a/internal/util/conversions.go b/internal/util/conversions.go index 5fa1db99d8..c83892c8c8 100644 --- a/internal/util/conversions.go +++ b/internal/util/conversions.go @@ -1,8 +1,6 @@ package util import ( - "github.com/samber/lo" - "github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi" ) @@ -10,27 +8,16 @@ import ( // Type conversion Utilities // ----------------------------------------------------------------------------- -// StringToGatewayAPIHostname converts a string to a gatewayapi.Hostname. -func StringToGatewayAPIHostname(hostname string) gatewayapi.Hostname { - return (gatewayapi.Hostname)(hostname) -} - -// StringToGatewayAPIHostnamePtr converts a string to a *gatewayapi.Hostname. -func StringToGatewayAPIHostnamePtr(hostname string) *gatewayapi.Hostname { - return lo.ToPtr(gatewayapi.Hostname(hostname)) -} - -// StringToGatewayAPIHostnameV1Beta1Ptr converts a string to a *gatewayapi.Hostname. -func StringToGatewayAPIHostnameV1Beta1Ptr(hostname string) *gatewayapi.Hostname { - return lo.ToPtr(gatewayapi.Hostname(hostname)) -} - -// StringToGatewayAPIKindV1Alpha2Ptr converts a string to a *gatewayapi.Kind. -func StringToGatewayAPIKindV1Alpha2Ptr(kind string) *gatewayapi.Kind { - return lo.ToPtr(gatewayapi.Kind(kind)) +// StringToTypedPtr converts a string to pointer to a typed designated by the provided type parameter. +func StringToTypedPtr[ + TT *T, + T ~string, +](s string) TT { + ret := T(s) + return &ret } // StringToGatewayAPIKindPtr converts a string to a *gatewayapi.Kind. func StringToGatewayAPIKindPtr(kind string) *gatewayapi.Kind { - return lo.ToPtr(gatewayapi.Kind(kind)) + return StringToTypedPtr[*gatewayapi.Kind](kind) } From 29470e66b02eb2604c4b7dadc876e54eb04b4af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Ma=C5=82ek?= Date: Tue, 24 Dec 2024 11:35:58 +0100 Subject: [PATCH 2/3] Update internal/controllers/gateway/route_parent_status.go Co-authored-by: Tao Yi --- internal/controllers/gateway/route_parent_status.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/controllers/gateway/route_parent_status.go b/internal/controllers/gateway/route_parent_status.go index 430cb303c0..8b1d94f036 100644 --- a/internal/controllers/gateway/route_parent_status.go +++ b/internal/controllers/gateway/route_parent_status.go @@ -141,7 +141,6 @@ func parentStatusesForRoute[routeT gatewayapi.RouteT]( // overlay the parent ref statuses for all new gateway references statusChangesWereMade := false for _, gateway := range gateways { - // build a new status for the parent Gateway gatewayParentStatus := gatewayParentStatusForRoute(route, gateway, withSectionName(gateway.listenerName)) From fea7a4c974da32bffaa928f27fd8caf7ff77abb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Ma=C5=82ek?= Date: Tue, 24 Dec 2024 11:40:38 +0100 Subject: [PATCH 3/3] refactor: use lo.EmptyableToPtr and lo.ContainsBy --- internal/controllers/gateway/conditions.go | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/internal/controllers/gateway/conditions.go b/internal/controllers/gateway/conditions.go index 7f5ed33be6..4ca0861493 100644 --- a/internal/controllers/gateway/conditions.go +++ b/internal/controllers/gateway/conditions.go @@ -64,12 +64,9 @@ func setRouteParentStatusCondition(parentStatus *gatewayapi.RouteParentStatus, n } func parentStatusHasProgrammedCondition(parentStatus *gatewayapi.RouteParentStatus) bool { - for _, condition := range parentStatus.Conditions { - if condition.Type == ConditionTypeProgrammed { - return true - } - } - return false + return lo.ContainsBy(parentStatus.Conditions, func(c metav1.Condition) bool { + return c.Type == ConditionTypeProgrammed + }) } // ensureParentsProgrammedCondition ensures that provided route's parent statuses @@ -118,15 +115,10 @@ func ensureParentsProgrammedCondition[ Name: gatewayapi.ObjectName(gateway.Name), Kind: lo.ToPtr(gatewayapi.Kind("Gateway")), Group: lo.ToPtr(gatewayapi.Group(gatewayv1.GroupName)), - SectionName: func() *gatewayapi.SectionName { - // We don't need to check whether the listener matches route's spec - // because that should already be done via getSupportedGatewayForRoute - // at this point. - if g.listenerName != "" { - return lo.ToPtr(gatewayapi.SectionName(g.listenerName)) - } - return nil - }(), + // We don't need to check whether the listener matches route's spec + // because that should already be done via getSupportedGatewayForRoute + // at this point. + SectionName: lo.EmptyableToPtr(gatewayapi.SectionName(g.listenerName)), // TODO: set port after gateway port matching implemented: // https://github.com/Kong/kubernetes-ingress-controller/issues/3016