From 87a5613cc30926327fbfed52942b4f1fd1741e5b Mon Sep 17 00:00:00 2001 From: Kevin Fan Date: Tue, 5 Nov 2024 14:18:32 +0000 Subject: [PATCH] refactor: target status -> discoverability reconciler (#958) * refactor: initial target status -> gateway policy discoverability reconciler Signed-off-by: KevFan * refactor: initial target status -> http route policy discoverability reconciler Signed-off-by: KevFan * feat: policyAffectedBy condition is ListenerStatus Signed-off-by: KevFan * refactor: list all policies in httproute affected by condition Signed-off-by: KevFan * refactor: smaller function for gateway discoverability Signed-off-by: KevFan * refactor: breakdown to methods/functions + integration tests Signed-off-by: KevFan * refactor: methods/functions Signed-off-by: KevFan * tests: additional tests for auth, dns and tls Signed-off-by: KevFan * gomod: tidy Signed-off-by: KevFan --------- Signed-off-by: KevFan --- controllers/common.go | 62 ++- ...teway_policy_discoverability_reconciler.go | 153 ++++++- ...route_policy_discoverability_reconciler.go | 157 ++++++- controllers/target_status_controller.go | 431 ------------------ controllers/test_common.go | 11 - go.mod | 2 +- go.sum | 10 - main.go | 11 - .../target_status_controller_test.go | 416 ++++++++++++++++- 9 files changed, 762 insertions(+), 491 deletions(-) delete mode 100644 controllers/target_status_controller.go diff --git a/controllers/common.go b/controllers/common.go index a11fb358d..3adca2471 100644 --- a/controllers/common.go +++ b/controllers/common.go @@ -1,7 +1,24 @@ package controllers +import ( + "context" + "fmt" + "sync" + + "github.com/kuadrant/policy-machinery/machinery" + "github.com/samber/lo" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + kuadrantv1alpha1 "github.com/kuadrant/kuadrant-operator/api/v1alpha1" + kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" +) + const ( - KuadrantAppName = "kuadrant" + KuadrantAppName = "kuadrant" + PolicyAffectedConditionPattern = "kuadrant.io/%sAffected" // Policy kinds are expected to be named XPolicy ) var ( @@ -19,3 +36,46 @@ func CommonLabels() map[string]string { "app.kubernetes.io/part-of": KuadrantAppName, } } + +func PolicyAffectedCondition(policyKind string, policies []machinery.Policy) metav1.Condition { + condition := metav1.Condition{ + Type: PolicyAffectedConditionType(policyKind), + Status: metav1.ConditionTrue, + Reason: string(gatewayapiv1alpha2.PolicyReasonAccepted), + Message: fmt.Sprintf("Object affected by %s %s", policyKind, lo.Map(policies, func(item machinery.Policy, index int) client.ObjectKey { + return client.ObjectKey{Name: item.GetName(), Namespace: item.GetNamespace()} + })), + } + + return condition +} + +func PolicyAffectedConditionType(policyKind string) string { + return fmt.Sprintf(PolicyAffectedConditionPattern, policyKind) +} + +func IsPolicyAccepted(ctx context.Context, p machinery.Policy, s *sync.Map) bool { + switch t := p.(type) { + case *kuadrantv1beta3.AuthPolicy: + return isAuthPolicyAcceptedFunc(s)(p) + case *kuadrantv1beta3.RateLimitPolicy: + return isRateLimitPolicyAcceptedFunc(s)(p) + case *kuadrantv1alpha1.TLSPolicy: + isValid, _ := IsTLSPolicyValid(ctx, s, t) + return isValid + case *kuadrantv1alpha1.DNSPolicy: + isValid, _ := dnsPolicyAcceptedStatusFunc(s)(p) + return isValid + default: + return false + } +} + +func policyGroupKinds() []*schema.GroupKind { + return []*schema.GroupKind{ + &kuadrantv1beta3.AuthPolicyGroupKind, + &kuadrantv1beta3.RateLimitPolicyGroupKind, + &kuadrantv1alpha1.TLSPolicyGroupKind, + &kuadrantv1alpha1.DNSPolicyGroupKind, + } +} diff --git a/controllers/gateway_policy_discoverability_reconciler.go b/controllers/gateway_policy_discoverability_reconciler.go index 4802bcb68..22ab51f20 100644 --- a/controllers/gateway_policy_discoverability_reconciler.go +++ b/controllers/gateway_policy_discoverability_reconciler.go @@ -1,8 +1,23 @@ package controllers import ( + "context" + "sync" + + "github.com/go-logr/logr" "github.com/kuadrant/policy-machinery/controller" + "github.com/kuadrant/policy-machinery/machinery" + "github.com/samber/lo" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + + kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1" + kuadrantv1alpha1 "github.com/kuadrant/kuadrant-operator/api/v1alpha1" + kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" ) type GatewayPolicyDiscoverabilityReconciler struct { @@ -14,5 +29,141 @@ func NewGatewayPolicyDiscoverabilityReconciler(client *dynamic.DynamicClient) *G } func (r *GatewayPolicyDiscoverabilityReconciler) Subscription() *controller.Subscription { - return &controller.Subscription{} + return &controller.Subscription{ + Events: []controller.ResourceEventMatcher{ + {Kind: &machinery.GatewayGroupKind}, + {Kind: &kuadrantv1beta3.AuthPolicyGroupKind}, + {Kind: &kuadrantv1beta3.RateLimitPolicyGroupKind}, + {Kind: &kuadrantv1alpha1.TLSPolicyGroupKind}, + {Kind: &kuadrantv1alpha1.DNSPolicyGroupKind}, + }, + ReconcileFunc: r.reconcile, + } +} + +func (r *GatewayPolicyDiscoverabilityReconciler) reconcile(ctx context.Context, _ []controller.ResourceEvent, topology *machinery.Topology, _ error, syncMap *sync.Map) error { + logger := controller.LoggerFromContext(ctx).WithName("GatewayPolicyDiscoverabilityReconciler").WithName("reconcile") + + gateways := lo.FilterMap(topology.Targetables().Items(), func(item machinery.Targetable, _ int) (*machinery.Gateway, bool) { + gw, ok := item.(*machinery.Gateway) + return gw, ok + }) + policyKinds := policyGroupKinds() + + for _, gw := range gateways { + updatedStatus := buildGatewayStatus(ctx, syncMap, gw, topology, logger, policyKinds) + if !equality.Semantic.DeepEqual(updatedStatus, gw.Status) { + gw.Status = *updatedStatus + if err := r.updateGatewayStatus(ctx, gw); err != nil { + logger.Error(err, "failed to update gateway status", "gateway", gw.GetName()) + } + } + } + + return nil +} + +func (r *GatewayPolicyDiscoverabilityReconciler) updateGatewayStatus(ctx context.Context, gw *machinery.Gateway) error { + obj, err := controller.Destruct(gw.Gateway) + if err != nil { + return err + } + _, err = r.Client.Resource(controller.GatewaysResource).Namespace(gw.GetNamespace()).UpdateStatus(ctx, obj, metav1.UpdateOptions{}) + return err +} + +func buildGatewayStatus(ctx context.Context, syncMap *sync.Map, gw *machinery.Gateway, topology *machinery.Topology, logger logr.Logger, policyKinds []*schema.GroupKind) *gatewayapiv1.GatewayStatus { + status := gw.Status.DeepCopy() + + listeners := lo.Map(topology.Targetables().Children(gw), func(item machinery.Targetable, _ int) *machinery.Listener { + listener, _ := item.(*machinery.Listener) + return listener + }) + + for _, listener := range listeners { + updatedListenerStatus := updateListenerStatus(ctx, syncMap, gw, listener, logger, policyKinds) + status.Listeners = updateListenerList(status.Listeners, updatedListenerStatus) + } + + for _, policyKind := range policyKinds { + updatePolicyConditions(ctx, syncMap, gw, policyKind, status, logger) + } + + return status +} + +func updateListenerStatus(ctx context.Context, syncMap *sync.Map, gw *machinery.Gateway, listener *machinery.Listener, logger logr.Logger, policyKinds []*schema.GroupKind) gatewayapiv1.ListenerStatus { + status, _, exists := findListenerStatus(gw.Status.Listeners, listener.Name) + if !exists { + status = gatewayapiv1.ListenerStatus{Name: listener.Name, Conditions: []metav1.Condition{}} + } + + for _, kind := range policyKinds { + conditionType := PolicyAffectedConditionType(kind.Kind) + policies := extractAcceptedPolicies(ctx, syncMap, kind, gw, listener) + + if len(policies) == 0 { + removeConditionIfExists(&status.Conditions, conditionType, logger, listener.GetName()) + } else { + addOrUpdateCondition(&status.Conditions, PolicyAffectedCondition(kind.Kind, policies), gw.GetGeneration(), logger) + } + } + + return status +} + +func updatePolicyConditions(ctx context.Context, syncMap *sync.Map, gw *machinery.Gateway, policyKind *schema.GroupKind, status *gatewayapiv1.GatewayStatus, logger logr.Logger) { + conditionType := PolicyAffectedConditionType(policyKind.Kind) + policies := extractAcceptedPolicies(ctx, syncMap, policyKind, gw) + + if len(policies) == 0 { + removeConditionIfExists(&status.Conditions, conditionType, logger, gw.GetName()) + } else { + addOrUpdateCondition(&status.Conditions, PolicyAffectedCondition(policyKind.Kind, policies), gw.GetGeneration(), logger) + } +} + +func addOrUpdateCondition(conditions *[]metav1.Condition, condition metav1.Condition, generation int64, logger logr.Logger) { + existingCondition := meta.FindStatusCondition(*conditions, condition.Type) + if existingCondition != nil && equality.Semantic.DeepEqual(*existingCondition, condition) { + logger.V(1).Info("condition unchanged", "condition", condition.Type) + return + } + + condition.ObservedGeneration = generation + meta.SetStatusCondition(conditions, condition) + logger.V(1).Info("updated condition", "condition", condition.Type) +} + +func removeConditionIfExists(conditions *[]metav1.Condition, conditionType string, logger logr.Logger, name string) { + if meta.RemoveStatusCondition(conditions, conditionType) { + logger.V(1).Info("removed condition", "condition", conditionType, "name", name) + } else { + logger.V(1).Info("condition absent, skipping removal", "condition", conditionType, "name", name) + } +} + +func updateListenerList(listeners []gatewayapiv1.ListenerStatus, updatedStatus gatewayapiv1.ListenerStatus) []gatewayapiv1.ListenerStatus { + _, index, exists := findListenerStatus(listeners, updatedStatus.Name) + if exists { + listeners[index] = updatedStatus + } else { + listeners = append(listeners, updatedStatus) + } + return listeners +} + +func findListenerStatus(listeners []gatewayapiv1.ListenerStatus, name gatewayapiv1.SectionName) (gatewayapiv1.ListenerStatus, int, bool) { + for i, status := range listeners { + if status.Name == name { + return status, i, true + } + } + return gatewayapiv1.ListenerStatus{}, -1, false +} + +func extractAcceptedPolicies(ctx context.Context, syncMap *sync.Map, policyKind *schema.GroupKind, targets ...machinery.Targetable) []machinery.Policy { + return kuadrantv1.PoliciesInPath(targets, func(policy machinery.Policy) bool { + return policy.GroupVersionKind().GroupKind() == *policyKind && IsPolicyAccepted(ctx, policy, syncMap) // Use enforced policies instead? + }) } diff --git a/controllers/httproute_policy_discoverability_reconciler.go b/controllers/httproute_policy_discoverability_reconciler.go index e1549723a..059e01b66 100644 --- a/controllers/httproute_policy_discoverability_reconciler.go +++ b/controllers/httproute_policy_discoverability_reconciler.go @@ -1,8 +1,27 @@ package controllers import ( + "context" + "sync" + + "github.com/go-logr/logr" "github.com/kuadrant/policy-machinery/controller" + "github.com/kuadrant/policy-machinery/machinery" + "github.com/samber/lo" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + + kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1" + kuadrantv1alpha1 "github.com/kuadrant/kuadrant-operator/api/v1alpha1" + kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" + "github.com/kuadrant/kuadrant-operator/pkg/library/utils" ) type HTTPRoutePolicyDiscoverabilityReconciler struct { @@ -14,5 +33,141 @@ func NewHTTPRoutePolicyDiscoverabilityReconciler(client *dynamic.DynamicClient) } func (r *HTTPRoutePolicyDiscoverabilityReconciler) Subscription() *controller.Subscription { - return &controller.Subscription{} + return &controller.Subscription{ + Events: []controller.ResourceEventMatcher{ + {Kind: &machinery.GatewayGroupKind}, + {Kind: &machinery.HTTPRouteGroupKind}, + {Kind: &kuadrantv1beta3.AuthPolicyGroupKind}, + {Kind: &kuadrantv1beta3.RateLimitPolicyGroupKind}, + {Kind: &kuadrantv1alpha1.TLSPolicyGroupKind}, + {Kind: &kuadrantv1alpha1.DNSPolicyGroupKind}, + }, + ReconcileFunc: r.reconcile, + } +} + +func (r *HTTPRoutePolicyDiscoverabilityReconciler) reconcile(ctx context.Context, _ []controller.ResourceEvent, topology *machinery.Topology, _ error, s *sync.Map) error { + logger := controller.LoggerFromContext(ctx).WithName("HTTPRoutePolicyDiscoverabilityReconciler").WithName("reconcile") + + httpRoutes := lo.FilterMap(topology.Targetables().Items(), func(item machinery.Targetable, index int) (*machinery.HTTPRoute, bool) { + ob, ok := item.(*machinery.HTTPRoute) + return ob, ok + }) + + policyKinds := policyGroupKinds() + + for _, route := range httpRoutes { + routeStatusParents := route.Status.DeepCopy().Parents + + for _, policyKind := range policyKinds { + path := getRoutePath(topology, route) + gateways := lo.FilterMap(path, func(item machinery.Targetable, index int) (*machinery.Gateway, bool) { + ob, ok := item.(*machinery.Gateway) + return ob, ok + }) + + policies := kuadrantv1.PoliciesInPath(path, func(policy machinery.Policy) bool { + return policy.GroupVersionKind().GroupKind() == *policyKind && IsPolicyAccepted(ctx, policy, s) + }) + + if len(policies) == 0 { + routeStatusParents = removePolicyConditions(routeStatusParents, gateways, policyKind, route, logger) + } else { + routeStatusParents = addPolicyConditions(routeStatusParents, gateways, policies, policyKind, route, logger) + } + } + + if !equality.Semantic.DeepEqual(routeStatusParents, route.Status.Parents) { + route.Status.Parents = routeStatusParents + if err := r.updateRouteStatus(ctx, route, logger); err != nil { + logger.Error(err, "unable to update route status", "name", route.GetName(), "namespace", route.GetNamespace(), "uid", route.GetUID()) + } + } + } + return nil +} + +func (r *HTTPRoutePolicyDiscoverabilityReconciler) updateRouteStatus(ctx context.Context, route *machinery.HTTPRoute, logger logr.Logger) error { + obj, err := controller.Destruct(route.HTTPRoute) + if err != nil { + logger.Error(err, "unable to destruct route", "name", route.GetName(), "namespace", route.GetNamespace(), "uid", route.GetUID()) + return err + } + _, err = r.Client.Resource(controller.HTTPRoutesResource).Namespace(route.GetNamespace()).UpdateStatus(ctx, obj, metav1.UpdateOptions{}) + return err +} + +func getRoutePath(topology *machinery.Topology, route *machinery.HTTPRoute) []machinery.Targetable { + path := []machinery.Targetable{route} + for _, listener := range topology.Targetables().Parents(route) { + path = append(path, listener) + path = append(path, topology.Targetables().Parents(listener)...) + } + return lo.UniqBy(path, func(item machinery.Targetable) string { + return item.GetLocator() + }) +} + +func removePolicyConditions(routeStatusParents []gatewayapiv1.RouteParentStatus, gateways []*machinery.Gateway, policyKind *schema.GroupKind, route *machinery.HTTPRoute, logger logr.Logger) []gatewayapiv1.RouteParentStatus { + conditionType := PolicyAffectedConditionType(policyKind.Kind) + for _, gw := range gateways { + i := utils.Index(routeStatusParents, FindRouteParentStatusFunc(route.HTTPRoute, client.ObjectKey{Namespace: gw.GetNamespace(), Name: gw.GetName()}, kuadrant.ControllerName)) + if i < 0 { + logger.V(1).Info("cannot find parent status, skipping") + continue + } + if c := meta.FindStatusCondition(routeStatusParents[i].Conditions, conditionType); c == nil { + logger.V(1).Info("condition already absent, skipping") + continue + } + logger.V(1).Info("removing condition from route") + meta.RemoveStatusCondition(&(routeStatusParents[i].Conditions), conditionType) + if len(routeStatusParents[i].Conditions) == 0 { + routeStatusParents = append(routeStatusParents[:i], routeStatusParents[i+1:]...) + } + } + return routeStatusParents +} + +func addPolicyConditions(routeStatusParents []gatewayapiv1.RouteParentStatus, gateways []*machinery.Gateway, policies []machinery.Policy, policyKind *schema.GroupKind, route *machinery.HTTPRoute, logger logr.Logger) []gatewayapiv1.RouteParentStatus { + condition := PolicyAffectedCondition(policyKind.Kind, policies) + for _, gw := range gateways { + i := ensureRouteParentStatus(&routeStatusParents, route, gw) + if currentCondition := meta.FindStatusCondition(routeStatusParents[i].Conditions, condition.Type); currentCondition != nil && + currentCondition.Status == condition.Status && currentCondition.Reason == condition.Reason && + currentCondition.Message == condition.Message && currentCondition.ObservedGeneration == route.GetGeneration() { + logger.V(1).Info("condition already up-to-date, skipping", "condition", condition.Type, "status", condition.Status, "name", route.GetName(), "namespace", route.GetNamespace(), "uid", route.GetUID()) + continue + } + condition.ObservedGeneration = route.GetGeneration() + meta.SetStatusCondition(&(routeStatusParents[i].Conditions), condition) + logger.V(1).Info("adding condition", "condition", condition.Type, "status", condition.Status, "name", route.GetName(), "namespace", route.GetNamespace(), "uid", route.GetUID()) + } + return routeStatusParents +} + +func ensureRouteParentStatus(routeStatusParents *[]gatewayapiv1.RouteParentStatus, route *machinery.HTTPRoute, gw *machinery.Gateway) int { + i := utils.Index(*routeStatusParents, FindRouteParentStatusFunc(route.HTTPRoute, client.ObjectKey{Namespace: gw.GetNamespace(), Name: gw.GetName()}, kuadrant.ControllerName)) + if i < 0 { + *routeStatusParents = append(*routeStatusParents, gatewayapiv1.RouteParentStatus{ + ControllerName: kuadrant.ControllerName, + ParentRef: gatewayapiv1.ParentReference{ + Kind: ptr.To[gatewayapiv1.Kind]("Gateway"), + Name: gatewayapiv1.ObjectName(gw.GetName()), + Namespace: ptr.To(gatewayapiv1.Namespace(gw.GetNamespace())), + }, + Conditions: []metav1.Condition{}, + }) + i = utils.Index(*routeStatusParents, FindRouteParentStatusFunc(route.HTTPRoute, client.ObjectKey{Namespace: gw.GetNamespace(), Name: gw.GetName()}, kuadrant.ControllerName)) + } + return i +} + +func FindRouteParentStatusFunc(route *gatewayapiv1.HTTPRoute, gatewayKey client.ObjectKey, controllerName gatewayapiv1.GatewayController) func(gatewayapiv1.RouteParentStatus) bool { + return func(p gatewayapiv1.RouteParentStatus) bool { + return *p.ParentRef.Kind == "Gateway" && + p.ControllerName == controllerName && + ((p.ParentRef.Namespace == nil && route.GetNamespace() == gatewayKey.Namespace) || string(*p.ParentRef.Namespace) == gatewayKey.Namespace) && + string(p.ParentRef.Name) == gatewayKey.Name + } } diff --git a/controllers/target_status_controller.go b/controllers/target_status_controller.go deleted file mode 100644 index ba5035ec1..000000000 --- a/controllers/target_status_controller.go +++ /dev/null @@ -1,431 +0,0 @@ -package controllers - -/* -Copyright 2021 Red Hat, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import ( - "context" - "errors" - "fmt" - "reflect" - "sort" - - "github.com/go-logr/logr" - "github.com/google/uuid" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/ptr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/predicate" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - - kuadrantv1alpha1 "github.com/kuadrant/kuadrant-operator/api/v1alpha1" - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - "github.com/kuadrant/kuadrant-operator/pkg/library/fieldindexers" - kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" - "github.com/kuadrant/kuadrant-operator/pkg/library/mappers" - "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" - "github.com/kuadrant/kuadrant-operator/pkg/library/utils" -) - -const PolicyAffectedConditionPattern = "kuadrant.io/%sAffected" // Policy kinds are expected to be named XPolicy - -// TargetStatusReconciler reconciles a the status stanzas of objects targeted by Kuadrant policies -type TargetStatusReconciler struct { - *reconcilers.BaseReconciler -} - -func (r *TargetStatusReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := r.Logger().WithValues("gateway", req.NamespacedName, "request id", uuid.NewString()) - logger.Info("Reconciling target status") - ctx := logr.NewContext(eventCtx, logger) - - gw := &gatewayapiv1.Gateway{} - if err := r.Client().Get(ctx, req.NamespacedName, gw); err != nil { - if apierrors.IsNotFound(err) { - logger.Info("no gateway found") - return ctrl.Result{}, nil - } - logger.Error(err, "failed to get gateway") - return ctrl.Result{}, err - } - - if err := r.reconcileResources(ctx, gw); err != nil { - return ctrl.Result{}, err - } - - logger.Info("Target status reconciled successfully") - return ctrl.Result{}, nil -} - -func (r *TargetStatusReconciler) reconcileResources(ctx context.Context, gw *gatewayapiv1.Gateway) error { - policyKinds := map[kuadrantgatewayapi.Policy]client.ObjectList{ - &kuadrantv1beta3.AuthPolicy{TypeMeta: ctrl.TypeMeta{Kind: "AuthPolicy"}}: &kuadrantv1beta3.AuthPolicyList{}, - &kuadrantv1alpha1.DNSPolicy{TypeMeta: ctrl.TypeMeta{Kind: "DNSPolicy"}}: &kuadrantv1alpha1.DNSPolicyList{}, - &kuadrantv1alpha1.TLSPolicy{TypeMeta: ctrl.TypeMeta{Kind: "TLSPolicy"}}: &kuadrantv1alpha1.TLSPolicyList{}, - &kuadrantv1beta3.RateLimitPolicy{TypeMeta: ctrl.TypeMeta{Kind: "RateLimitPolicy"}}: &kuadrantv1beta3.RateLimitPolicyList{}, - } - - var errs error - - for policy, policyListKind := range policyKinds { - err := r.reconcileResourcesForPolicyKind(ctx, gw, policy, policyListKind) - if err != nil { - errs = errors.Join(errs, err) - } - } - - return errs -} - -func (r *TargetStatusReconciler) reconcileResourcesForPolicyKind(parentCtx context.Context, gw *gatewayapiv1.Gateway, policy kuadrantgatewayapi.Policy, listPolicyKind client.ObjectList) error { - logger, err := logr.FromContext(parentCtx) - if err != nil { - return err - } - gatewayKey := client.ObjectKeyFromObject(gw) - policyKind := policy.GetObjectKind().GroupVersionKind().Kind - ctx := logr.NewContext(parentCtx, logger.WithValues("kind", policyKind)) - - topology, err := r.buildTopology(ctx, gw, policyKind, listPolicyKind) - if err != nil { - return err - } - policies := topology.PoliciesFromGateway(gw) - - sort.Sort(kuadrantgatewayapi.PolicyByTargetRefKindAndAcceptedStatus(policies)) - - var errs error - - // if no policies of a kind affecting the gateway → remove condition from the gateway and routes - gatewayPolicyExists := len(policies) > 0 && utils.Index(policies, func(p kuadrantgatewayapi.Policy) bool { return kuadrantgatewayapi.IsTargetRefGateway(p.GetTargetRef()) }) >= 0 - if !gatewayPolicyExists { - // remove the condition from the gateway - conditionType := PolicyAffectedConditionType(policyKind) - if c := meta.FindStatusCondition(gw.Status.Conditions, conditionType); c == nil { - logger.V(1).Info("condition already absent, skipping", "condition", conditionType) - } else { - meta.RemoveStatusCondition(&gw.Status.Conditions, conditionType) - logger.V(1).Info("removing condition from gateway", "condition", conditionType) - if err := r.Client().Status().Update(ctx, gw); err != nil { - errs = errors.Join(errs, err) - } - } - - // remove the condition from the routes not targeted by any policy - if policy.PolicyClass() == kuadrantgatewayapi.InheritedPolicy { - if err := r.updateInheritedGatewayCondition(ctx, topology.GetUntargetedRoutes(gw), metav1.Condition{Type: conditionType}, gatewayKey, r.removeRouteCondition); err != nil { - errs = errors.Join(errs, err) - } - } - } - - reconciledTargets := make(map[string]struct{}) - - reconcileFunc := func(policy kuadrantgatewayapi.Policy) error { - targetRefKey := targetRefKey(policy) - - // update status of targeted route - if route := topology.GetPolicyHTTPRoute(policy); route != nil { - if _, updated := reconciledTargets[targetRefKey]; updated { // do not update the same route twice - return nil - } - if err := r.addRouteCondition(ctx, route, buildPolicyAffectedCondition(policy), gatewayKey, kuadrant.ControllerName); err != nil { - errs = errors.Join(errs, err) - } - reconciledTargets[targetRefKey] = struct{}{} - return nil - } - - // update status of targeted gateway and routes not targeted by any policy - if _, updated := reconciledTargets[targetRefKey]; updated { // do not update the same gateway twice - return nil - } - condition := buildPolicyAffectedCondition(policy) - if c := meta.FindStatusCondition(gw.Status.Conditions, condition.Type); c != nil && c.Status == condition.Status && c.Reason == condition.Reason && c.Message == condition.Message && c.ObservedGeneration == gw.GetGeneration() { - logger.V(1).Info("condition already up-to-date, skipping", "condition", condition.Type, "status", condition.Status) - } else { - gwCondition := condition.DeepCopy() - gwCondition.ObservedGeneration = gw.GetGeneration() - meta.SetStatusCondition(&gw.Status.Conditions, *gwCondition) - logger.V(1).Info("adding condition to gateway", "condition", condition.Type, "status", condition.Status) - if err := r.Client().Status().Update(ctx, gw); err != nil { - return err - } - } - // update status of all untargeted routes accepted by the gateway - if policy.PolicyClass() == kuadrantgatewayapi.InheritedPolicy { - if err := r.updateInheritedGatewayCondition(ctx, topology.GetUntargetedRoutes(gw), condition, gatewayKey, r.addRouteCondition); err != nil { - return err - } - } - reconciledTargets[targetRefKey] = struct{}{} - - return nil - } - - // update for policies with status condition Accepted: true - for i := range utils.Filter(policies, kuadrantgatewayapi.IsPolicyAccepted) { - policy := policies[i] - errs = errors.Join(errs, reconcileFunc(policy)) - } - - // update for policies with status condition Accepted: false - for i := range utils.Filter(policies, kuadrantgatewayapi.IsNotPolicyAccepted) { - policy := policies[i] - errs = errors.Join(errs, reconcileFunc(policy)) - } - - return errs -} - -func (r *TargetStatusReconciler) buildTopology(ctx context.Context, gw *gatewayapiv1.Gateway, policyKind string, listPolicyKind client.ObjectList) (*kuadrantgatewayapi.TopologyIndexes, error) { - logger, err := logr.FromContext(ctx) - if err != nil { - return nil, err - } - - routeList := &gatewayapiv1.HTTPRouteList{} - // Get all the routes having the gateway as parent - err = r.Client().List(ctx, routeList, client.MatchingFields{fieldindexers.HTTPRouteGatewayParentField: client.ObjectKeyFromObject(gw).String()}) - logger.V(1).Info("list routes by gateway", "#routes", len(routeList.Items), "err", err) - if err != nil { - return nil, err - } - - policies, err := r.getPoliciesByKind(ctx, policyKind, listPolicyKind) - if err != nil { - return nil, err - } - - t, err := kuadrantgatewayapi.NewTopology( - kuadrantgatewayapi.WithAcceptedRoutesLinkedOnly(), - kuadrantgatewayapi.WithProgrammedGatewaysOnly(), - kuadrantgatewayapi.WithGateways([]*gatewayapiv1.Gateway{gw}), - kuadrantgatewayapi.WithRoutes(utils.Map(routeList.Items, ptr.To[gatewayapiv1.HTTPRoute])), - kuadrantgatewayapi.WithPolicies(policies), - kuadrantgatewayapi.WithLogger(logger), - ) - if err != nil { - return nil, err - } - - return kuadrantgatewayapi.NewTopologyIndexes(t), nil -} - -func (r *TargetStatusReconciler) getPoliciesByKind(ctx context.Context, policyKind string, listKind client.ObjectList) ([]kuadrantgatewayapi.Policy, error) { - logger, _ := logr.FromContext(ctx) - logger = logger.WithValues("kind", policyKind) - - // Get all policies of the given kind - err := r.Client().List(ctx, listKind) - policyList, ok := listKind.(kuadrant.PolicyList) - if !ok { - return nil, fmt.Errorf("%T is not a kuadrant.PolicyList", listKind) - } - logger.V(1).Info("list policies by kind", "#policies", len(policyList.GetItems()), "err", err) - if err != nil { - return nil, err - } - - return utils.Map(policyList.GetItems(), func(p kuadrant.Policy) kuadrantgatewayapi.Policy { return p }), nil -} - -func (r *TargetStatusReconciler) updateInheritedGatewayCondition(ctx context.Context, routes []*gatewayapiv1.HTTPRoute, condition metav1.Condition, gatewayKey client.ObjectKey, update updateRouteConditionFunc) error { - logger, _ := logr.FromContext(ctx) - logger = logger.WithValues("condition", condition.Type, "status", condition.Status) - - logger.V(1).Info("update inherited gateway condition", "#routes", len(routes)) - - var errs error - - for i := range routes { - route := routes[i] - if err := update(ctx, route, condition, gatewayKey, kuadrant.ControllerName); err != nil { - errs = errors.Join(errs, err) - } - } - - return errs -} - -type updateRouteConditionFunc func(ctx context.Context, route *gatewayapiv1.HTTPRoute, condition metav1.Condition, gatewayKey client.ObjectKey, controllerName gatewayapiv1.GatewayController) error - -func (r *TargetStatusReconciler) addRouteCondition(ctx context.Context, route *gatewayapiv1.HTTPRoute, condition metav1.Condition, gatewayKey client.ObjectKey, controllerName gatewayapiv1.GatewayController) error { - logger, _ := logr.FromContext(ctx) - logger = logger.WithValues("route", client.ObjectKeyFromObject(route), "condition", condition.Type, "status", condition.Status) - - i := utils.Index(route.Status.RouteStatus.Parents, FindRouteParentStatusFunc(route, gatewayKey, controllerName)) - if i < 0 { - logger.V(1).Info("cannot find parent status, creating new one") - route.Status.RouteStatus.Parents = append(route.Status.RouteStatus.Parents, gatewayapiv1.RouteParentStatus{ - ControllerName: controllerName, - ParentRef: gatewayapiv1.ParentReference{ - Kind: ptr.To(gatewayapiv1.Kind("Gateway")), - Name: gatewayapiv1.ObjectName(gatewayKey.Name), - Namespace: ptr.To(gatewayapiv1.Namespace(gatewayKey.Namespace)), - }, - Conditions: []metav1.Condition{}, - }) - i = utils.Index(route.Status.RouteStatus.Parents, FindRouteParentStatusFunc(route, gatewayKey, controllerName)) - } - - if c := meta.FindStatusCondition(route.Status.RouteStatus.Parents[i].Conditions, condition.Type); c != nil && c.Status == condition.Status && c.Reason == condition.Reason && c.Message == condition.Message && c.ObservedGeneration == route.GetGeneration() { - logger.V(1).Info("condition already up-to-date, skipping") - return nil - } - - routeCondition := condition.DeepCopy() - routeCondition.ObservedGeneration = route.GetGeneration() - meta.SetStatusCondition(&(route.Status.RouteStatus.Parents[i].Conditions), *routeCondition) // Istio will merge the conditions from Kuadrant's parent status into its own parent status. See https://github.com/istio/istio/issues/50484 - logger.V(1).Info("adding condition to route") - return r.Client().Status().Update(ctx, route) -} - -func (r *TargetStatusReconciler) removeRouteCondition(ctx context.Context, route *gatewayapiv1.HTTPRoute, condition metav1.Condition, gatewayKey client.ObjectKey, controllerName gatewayapiv1.GatewayController) error { - logger, _ := logr.FromContext(ctx) - logger = logger.WithValues("route", client.ObjectKeyFromObject(route), "condition", condition.Type, "status", condition.Status) - - i := utils.Index(route.Status.RouteStatus.Parents, FindRouteParentStatusFunc(route, gatewayKey, controllerName)) - if i < 0 { - logger.V(1).Info("cannot find parent status, skipping") - return nil - } - - if c := meta.FindStatusCondition(route.Status.RouteStatus.Parents[i].Conditions, condition.Type); c == nil { - logger.V(1).Info("condition already absent, skipping") - return nil - } - - logger.V(1).Info("removing condition from route") - meta.RemoveStatusCondition(&(route.Status.RouteStatus.Parents[i].Conditions), condition.Type) - if len(route.Status.RouteStatus.Parents[i].Conditions) == 0 { - route.Status.RouteStatus.Parents = append(route.Status.RouteStatus.Parents[:i], route.Status.RouteStatus.Parents[i+1:]...) - } - return r.Client().Status().Update(ctx, route) -} - -func FindRouteParentStatusFunc(route *gatewayapiv1.HTTPRoute, gatewayKey client.ObjectKey, controllerName gatewayapiv1.GatewayController) func(gatewayapiv1.RouteParentStatus) bool { - return func(p gatewayapiv1.RouteParentStatus) bool { - return *p.ParentRef.Kind == ("Gateway") && - p.ControllerName == controllerName && - ((p.ParentRef.Namespace == nil && route.GetNamespace() == gatewayKey.Namespace) || string(*p.ParentRef.Namespace) == gatewayKey.Namespace) && - string(p.ParentRef.Name) == gatewayKey.Name - } -} - -// SetupWithManager sets up the controller with the Manager. -func (r *TargetStatusReconciler) SetupWithManager(mgr ctrl.Manager) error { - ok, err := kuadrantgatewayapi.IsGatewayAPIInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("TargetStatus controller disabled. GatewayAPI was not found") - return nil - } - - httpRouteToParentGatewaysEventMapper := mappers.NewHTTPRouteToParentGatewaysEventMapper( - mappers.WithLogger(r.Logger().WithName("httpRouteToParentGatewaysEventMapper")), - ) - - policyToParentGatewaysEventMapper := mappers.NewPolicyToParentGatewaysEventMapper( - mappers.WithLogger(r.Logger().WithName("policyToParentGatewaysEventMapper")), - mappers.WithClient(r.Client()), - ) - - policyStatusChangedPredicate := predicate.Funcs{ - UpdateFunc: func(ev event.UpdateEvent) bool { - oldPolicy, ok := ev.ObjectOld.(kuadrantgatewayapi.Policy) - if !ok { - return false - } - newPolicy, ok := ev.ObjectNew.(kuadrantgatewayapi.Policy) - if !ok { - return false - } - oldStatus := oldPolicy.GetStatus() - newStatus := newPolicy.GetStatus() - return !reflect.DeepEqual(oldStatus, newStatus) - }, - } - - return ctrl.NewControllerManagedBy(mgr). - For(&gatewayapiv1.Gateway{}). - Watches( - &gatewayapiv1.HTTPRoute{}, - handler.EnqueueRequestsFromMapFunc(httpRouteToParentGatewaysEventMapper.Map), - ). - Watches( - &kuadrantv1beta3.AuthPolicy{}, - handler.EnqueueRequestsFromMapFunc(policyToParentGatewaysEventMapper.Map), - builder.WithPredicates(policyStatusChangedPredicate), - ). - Watches( - &kuadrantv1alpha1.DNSPolicy{}, - handler.EnqueueRequestsFromMapFunc(policyToParentGatewaysEventMapper.Map), - builder.WithPredicates(policyStatusChangedPredicate), - ). - Watches( - &kuadrantv1beta3.RateLimitPolicy{}, - handler.EnqueueRequestsFromMapFunc(policyToParentGatewaysEventMapper.Map), - builder.WithPredicates(policyStatusChangedPredicate), - ). - Watches( - &kuadrantv1alpha1.TLSPolicy{}, - handler.EnqueueRequestsFromMapFunc(policyToParentGatewaysEventMapper.Map), - builder.WithPredicates(policyStatusChangedPredicate), - ). - Complete(r) -} - -func buildPolicyAffectedCondition(policy kuadrantgatewayapi.Policy) metav1.Condition { - policyKind := policy.GetObjectKind().GroupVersionKind().Kind - - condition := metav1.Condition{ - Type: PolicyAffectedConditionType(policyKind), - Status: metav1.ConditionTrue, - Reason: string(gatewayapiv1alpha2.PolicyReasonAccepted), - Message: fmt.Sprintf("Object affected by %s %s", policyKind, client.ObjectKeyFromObject(policy)), - } - - if c := meta.FindStatusCondition(policy.GetStatus().GetConditions(), string(gatewayapiv1alpha2.PolicyConditionAccepted)); c == nil || c.Status != metav1.ConditionTrue { // should we aim for 'Enforced' instead? - condition.Status = metav1.ConditionFalse - condition.Message = fmt.Sprintf("Object unaffected by %s %s, policy is not accepted", policyKind, client.ObjectKeyFromObject(policy)) - condition.Reason = PolicyReasonUnknown - if c != nil { - condition.Reason = c.Reason - } - } - - return condition -} - -func PolicyAffectedConditionType(policyKind string) string { - return fmt.Sprintf(PolicyAffectedConditionPattern, policyKind) -} - -func targetRefKey(policy kuadrantgatewayapi.Policy) string { - targetRef := policy.GetTargetRef() - return fmt.Sprintf("%s.%s/%s/%s", targetRef.Group, targetRef.Kind, gatewayapiv1.Namespace(policy.GetNamespace()), targetRef.Name) -} diff --git a/controllers/test_common.go b/controllers/test_common.go index 60d5b5c42..f3b4576a4 100644 --- a/controllers/test_common.go +++ b/controllers/test_common.go @@ -99,17 +99,6 @@ func SetupKuadrantOperatorForTest(s *runtime.Scheme, cfg *rest.Config) { Expect(err).NotTo(HaveOccurred()) - targetStatusBaseReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), - mgr.GetScheme(), - mgr.GetAPIReader(), - log.Log.WithName("targetstatus"), - ) - - err = (&TargetStatusReconciler{ - BaseReconciler: targetStatusBaseReconciler, - }).SetupWithManager(mgr) - Expect(err).NotTo(HaveOccurred()) dClient, err := dynamic.NewForConfig(mgr.GetConfig()) diff --git a/go.mod b/go.mod index 2595707cf..a6f39860e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/go-logr/logr v1.4.2 github.com/goccy/go-yaml v1.12.0 github.com/google/go-cmp v0.6.0 - github.com/google/uuid v1.6.0 github.com/kuadrant/authorino v0.18.0 github.com/kuadrant/authorino-operator v0.11.1 github.com/kuadrant/dns-operator v0.0.0-20241018131559-f2ce8b6aaaef @@ -92,6 +91,7 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect github.com/gosuri/uitable v0.0.4 // indirect diff --git a/go.sum b/go.sum index 535af7202..21395826a 100644 --- a/go.sum +++ b/go.sum @@ -262,16 +262,6 @@ github.com/kuadrant/dns-operator v0.0.0-20241018131559-f2ce8b6aaaef h1:6P2pC1kOP github.com/kuadrant/dns-operator v0.0.0-20241018131559-f2ce8b6aaaef/go.mod h1:LGG4R3KEz93Ep0CV1/tziCmRk+VtojWUHR9mXkOHZks= github.com/kuadrant/limitador-operator v0.9.0 h1:hTQ6CFPayf/sL7cIzwWjCoU8uTn6fzWdsJgKbDlnFts= github.com/kuadrant/limitador-operator v0.9.0/go.mod h1:DQOlg9qFOcnWPrwO529JRCMLLOEXJQxkmOes952S/Hw= -github.com/kuadrant/policy-machinery v0.6.3 h1:/v/kJPxDjvCdklnRk4bEMv+ENDQR3Q10NUJm/6ep1vE= -github.com/kuadrant/policy-machinery v0.6.3/go.mod h1:ZV4xS0CCxPgu/Xg6gz+YUaS9zqEXKOiAj33bZ67B6Lo= -github.com/kuadrant/policy-machinery v0.6.4-0.20241025143438-fc72729b1778 h1:nZP04Xd36GpmTeCvXOTq0cN1nPItBme3nzocljqKFWA= -github.com/kuadrant/policy-machinery v0.6.4-0.20241025143438-fc72729b1778/go.mod h1:ZV4xS0CCxPgu/Xg6gz+YUaS9zqEXKOiAj33bZ67B6Lo= -github.com/kuadrant/policy-machinery v0.6.4-0.20241025153216-2cf0e959bb59 h1:4UzVjdwTXzwE1sYjrnzwdf9YOoL+IqzBYX30W1t2F9g= -github.com/kuadrant/policy-machinery v0.6.4-0.20241025153216-2cf0e959bb59/go.mod h1:ZV4xS0CCxPgu/Xg6gz+YUaS9zqEXKOiAj33bZ67B6Lo= -github.com/kuadrant/policy-machinery v0.6.4-0.20241025154236-441b3616a4c2 h1:vBxdlbOvcHl+JJ8qLzD/KOAfikbClqcj5idRBSBmKds= -github.com/kuadrant/policy-machinery v0.6.4-0.20241025154236-441b3616a4c2/go.mod h1:ZV4xS0CCxPgu/Xg6gz+YUaS9zqEXKOiAj33bZ67B6Lo= -github.com/kuadrant/policy-machinery v0.6.4-0.20241025155033-3a07f46a26a5 h1:3LAfrCyso2zSZHyvKcDnXhUZM8covdywnj6/46Ni9SY= -github.com/kuadrant/policy-machinery v0.6.4-0.20241025155033-3a07f46a26a5/go.mod h1:ZV4xS0CCxPgu/Xg6gz+YUaS9zqEXKOiAj33bZ67B6Lo= github.com/kuadrant/policy-machinery v0.6.4 h1:UMdZ2p7WyUdOKcWlJA2w2MzJnB8/Nn4dT6hE9cUcbeg= github.com/kuadrant/policy-machinery v0.6.4/go.mod h1:ZV4xS0CCxPgu/Xg6gz+YUaS9zqEXKOiAj33bZ67B6Lo= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= diff --git a/main.go b/main.go index 77403382d..4cbfc0991 100644 --- a/main.go +++ b/main.go @@ -181,17 +181,6 @@ func main() { os.Exit(1) } - targetStatusBaseReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("targetstatus"), - ) - if err = (&controllers.TargetStatusReconciler{ - BaseReconciler: targetStatusBaseReconciler, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "TargetStatusReconciler") - os.Exit(1) - } - //+kubebuilder:scaffold:builder if err = mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/tests/common/targetstatus/target_status_controller_test.go b/tests/common/targetstatus/target_status_controller_test.go index d8fca4208..13b6dfe60 100644 --- a/tests/common/targetstatus/target_status_controller_test.go +++ b/tests/common/targetstatus/target_status_controller_test.go @@ -14,6 +14,7 @@ import ( kuadrantdnsv1alpha1 "github.com/kuadrant/dns-operator/api/v1alpha1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/samber/lo" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -51,12 +52,20 @@ var _ = Describe("Target status reconciler", func() { // create gateway gateway := tests.BuildBasicGateway(TestGatewayName, testNamespace, func(gateway *gatewayapiv1.Gateway) { - gateway.Spec.Listeners = []gatewayapiv1.Listener{{ - Name: gatewayapiv1.SectionName("test-listener-toystore-com"), - Hostname: ptr.To(gatewayapiv1.Hostname(gwHost)), - Port: gatewayapiv1.PortNumber(80), - Protocol: gatewayapiv1.HTTPProtocolType, - }} + gateway.Spec.Listeners = []gatewayapiv1.Listener{ + { + Name: "test-listener-toystore-com", + Hostname: ptr.To(gatewayapiv1.Hostname(gwHost)), + Port: gatewayapiv1.PortNumber(80), + Protocol: gatewayapiv1.HTTPProtocolType, + }, + { + Name: "test-listener-2", + Hostname: ptr.To(gatewayapiv1.Hostname(gwHost)), + Port: gatewayapiv1.PortNumber(88), + Protocol: gatewayapiv1.HTTPProtocolType, + }, + } }) err := k8sClient.Create(ctx, gateway) Expect(err).ToNot(HaveOccurred()) @@ -81,10 +90,16 @@ var _ = Describe("Target status reconciler", func() { return false } condition := meta.FindStatusCondition(gateway.Status.Conditions, conditionType) - return condition != nil && condition.Status == metav1.ConditionTrue && strings.Contains(condition.Message, policyKey.String()) + gatewayHasCondition := condition != nil && condition.Status == metav1.ConditionTrue && strings.Contains(condition.Message, policyKey.String()) + listenersHasCondition := lo.EveryBy(gateway.Status.Listeners, func(item gatewayapiv1.ListenerStatus) bool { + lCond := meta.FindStatusCondition(item.Conditions, conditionType) + return lCond != nil && lCond.Status == metav1.ConditionTrue && strings.Contains(lCond.Message, policyKey.String()) + }) + + return gatewayHasCondition && listenersHasCondition } - routeAffected := func(ctx context.Context, routeName, conditionType string, policyKey client.ObjectKey) bool { + routeAffected := func(ctx context.Context, routeName, conditionType string, policyKey ...client.ObjectKey) bool { route := &gatewayapiv1.HTTPRoute{} err := k8sClient.Get(ctx, client.ObjectKey{Name: routeName, Namespace: testNamespace}, route) if err != nil { @@ -95,7 +110,9 @@ var _ = Describe("Target status reconciler", func() { return false } condition := meta.FindStatusCondition(routeParentStatus.Conditions, conditionType) - return condition.Status == metav1.ConditionTrue && strings.Contains(condition.Message, policyKey.String()) + return condition.Status == metav1.ConditionTrue && lo.EveryBy(policyKey, func(item client.ObjectKey) bool { + return strings.Contains(condition.Message, item.String()) + }) } targetsAffected := func(ctx context.Context, policyKey client.ObjectKey, conditionType string, targetRef gatewayapiv1alpha2.LocalPolicyTargetReference, routeNames ...string) bool { @@ -296,6 +313,137 @@ var _ = Describe("Target status reconciler", func() { Expect(k8sClient.Delete(ctx, routePolicy)).To(Succeed()) Eventually(policyAcceptedAndTargetsAffected(ctx, gatewayPolicy, otherRouteName, TestHTTPRouteName)).WithContext(ctx).Should(BeTrue()) }, testTimeOut) + + It("adds section name polices only to specific listener status conditions", func(ctx SpecContext) { + policy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { + policy.Name = "section-ap" + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, + SectionName: ptr.To[gatewayapiv1.SectionName]("test-listener-toystore-com"), + } + }) + policyKey := client.ObjectKeyFromObject(policy) + Expect(k8sClient.Create(ctx, policy)).To(Succeed()) + Eventually(tests.IsAuthPolicyAccepted(ctx, testClient(), policy)).WithContext(ctx).Should(BeTrue()) + Eventually(func(g Gomega) { + gateway := &gatewayapiv1.Gateway{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: TestGatewayName, Namespace: testNamespace}, gateway)).To(Succeed()) + condition := meta.FindStatusCondition(gateway.Status.Conditions, policyAffectedCondition) + // Should not be in gw conditions + g.Expect(condition).To(BeNil()) + + g.Expect(lo.EveryBy(gateway.Status.Listeners, func(item gatewayapiv1.ListenerStatus) bool { + lCond := meta.FindStatusCondition(item.Conditions, policyAffectedCondition) + if item.Name == *policy.Spec.TargetRef.SectionName { + // Target section should include condition + return lCond != nil && lCond.Status == metav1.ConditionTrue && strings.Contains(lCond.Message, policyKey.String()) + } + + // all other sections should not have the condition + return lCond == nil + })).To(BeTrue()) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) + + It("gateway policy is also listed with section policy", func(ctx SpecContext) { + gwPolicy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { + policy.Name = "gateway-ap" + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, + } + }) + gwPolicyKey := client.ObjectKeyFromObject(gwPolicy) + Expect(k8sClient.Create(ctx, gwPolicy)).To(Succeed()) + Eventually(tests.IsAuthPolicyAccepted(ctx, testClient(), gwPolicy)).WithContext(ctx).Should(BeTrue()) + + lPolicy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { + policy.Name = "section-ap" + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, + SectionName: ptr.To[gatewayapiv1.SectionName]("test-listener-toystore-com"), + } + }) + lPolicyKey := client.ObjectKeyFromObject(lPolicy) + Expect(k8sClient.Create(ctx, lPolicy)).To(Succeed()) + Eventually(tests.IsAuthPolicyAccepted(ctx, testClient(), lPolicy)).WithContext(ctx).Should(BeTrue()) + + Eventually(func(g Gomega) { + gateway := &gatewayapiv1.Gateway{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: TestGatewayName, Namespace: testNamespace}, gateway)).To(Succeed()) + condition := meta.FindStatusCondition(gateway.Status.Conditions, policyAffectedCondition) + // Gateway should list the condition with only the gw policy + g.Expect(condition).ToNot(BeNil()) + g.Expect(condition.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(condition.Message).To(ContainSubstring(gwPolicyKey.String())) + g.Expect(condition.Message).ToNot(ContainSubstring(lPolicyKey.String())) + + // Listeners + g.Expect(lo.EveryBy(gateway.Status.Listeners, func(item gatewayapiv1.ListenerStatus) bool { + lCond := meta.FindStatusCondition(item.Conditions, policyAffectedCondition) + if item.Name == *lPolicy.Spec.TargetRef.SectionName { + // Target section should include condition + return lCond != nil && lCond.Status == metav1.ConditionTrue && strings.Contains(lCond.Message, lPolicyKey.String()) && strings.Contains(lCond.Message, gwPolicyKey.String()) + } + + // all other sections list only the gw policy + return lCond != nil && lCond.Status == metav1.ConditionTrue && !strings.Contains(lCond.Message, lPolicyKey.String()) && strings.Contains(lCond.Message, gwPolicyKey.String()) + })).To(BeTrue()) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) + + It("route should list it's own policy and the parent policies", func(ctx SpecContext) { + gwPolicy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { + policy.Name = "gateway-ap" + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, + } + }) + gwPolicyKey := client.ObjectKeyFromObject(gwPolicy) + Expect(k8sClient.Create(ctx, gwPolicy)).To(Succeed()) + Eventually(tests.IsAuthPolicyAccepted(ctx, testClient(), gwPolicy)).WithContext(ctx).Should(BeTrue()) + + lPolicy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { + policy.Name = "section-ap" + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, + SectionName: ptr.To[gatewayapiv1.SectionName]("test-listener-toystore-com"), + } + }) + lPolicyKey := client.ObjectKeyFromObject(lPolicy) + Expect(k8sClient.Create(ctx, lPolicy)).To(Succeed()) + Eventually(tests.IsAuthPolicyAccepted(ctx, testClient(), lPolicy)).WithContext(ctx).Should(BeTrue()) + + rPolicy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { + policy.Name = "route-ap" + }) + rPolicyKey := client.ObjectKeyFromObject(rPolicy) + Expect(k8sClient.Create(ctx, rPolicy)).To(Succeed()) + Eventually(tests.IsAuthPolicyAccepted(ctx, testClient(), rPolicy)).WithContext(ctx).Should(BeTrue()) + + Eventually(func(g Gomega) { + g.Expect(routeAffected(ctx, TestHTTPRouteName, policyAffectedCondition, gwPolicyKey, lPolicyKey, rPolicyKey)).To(BeTrue()) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) }) Context("RateLimitPolicy", func() { @@ -404,7 +552,7 @@ var _ = Describe("Target status reconciler", func() { } }) Expect(k8sClient.Create(ctx, policy)).To(Succeed()) - Eventually(policyAcceptedAndTargetsAffected(ctx, policy)).WithContext(ctx).Should(BeTrue()) + Eventually(policyAcceptedAndTargetsAffected(ctx, policy, TestHTTPRouteName)).WithContext(ctx).Should(BeTrue()) Expect(k8sClient.Delete(ctx, policy)).To(Succeed()) @@ -449,13 +597,144 @@ var _ = Describe("Target status reconciler", func() { Eventually(func() bool { return tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(otherRoute))() && policyAcceptedAndTargetsAffected(ctx, routePolicy)() && - policyAcceptedAndTargetsAffected(ctx, gatewayPolicy, otherRouteName)() + policyAcceptedAndTargetsAffected(ctx, gatewayPolicy, TestHTTPRouteName, otherRouteName)() }).WithContext(ctx).Should(BeTrue()) // remove route policy and check if the gateway policy has been rolled out to the status of the newly non-targeted route Expect(k8sClient.Delete(ctx, routePolicy)).To(Succeed()) Eventually(policyAcceptedAndTargetsAffected(ctx, gatewayPolicy, otherRouteName, TestHTTPRouteName)).WithContext(ctx).Should(BeTrue()) }, testTimeOut) + + It("adds section name polices only to specific listener status conditions", func(ctx SpecContext) { + policy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { + policy.Name = "section-rlp" + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, + SectionName: ptr.To[gatewayapiv1.SectionName]("test-listener-toystore-com"), + } + }) + policyKey := client.ObjectKeyFromObject(policy) + Expect(k8sClient.Create(ctx, policy)).To(Succeed()) + Eventually(tests.RLPIsAccepted(ctx, testClient(), policyKey)).WithContext(ctx).Should(BeTrue()) + Eventually(func(g Gomega) { + gateway := &gatewayapiv1.Gateway{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: TestGatewayName, Namespace: testNamespace}, gateway)).To(Succeed()) + condition := meta.FindStatusCondition(gateway.Status.Conditions, policyAffectedCondition) + // Should not be in gw conditions + g.Expect(condition).To(BeNil()) + + g.Expect(lo.EveryBy(gateway.Status.Listeners, func(item gatewayapiv1.ListenerStatus) bool { + lCond := meta.FindStatusCondition(item.Conditions, policyAffectedCondition) + if item.Name == *policy.Spec.TargetRef.SectionName { + // Target section should include condition + return lCond != nil && lCond.Status == metav1.ConditionTrue && strings.Contains(lCond.Message, policyKey.String()) + } + + // all other sections should not have the condition + return lCond == nil + })).To(BeTrue()) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) + + It("gateway policy is also listed with section policy", func(ctx SpecContext) { + gwPolicy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { + policy.Name = "gateway-rlp" + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, + } + }) + gwPolicyKey := client.ObjectKeyFromObject(gwPolicy) + Expect(k8sClient.Create(ctx, gwPolicy)).To(Succeed()) + Eventually(tests.RLPIsAccepted(ctx, testClient(), gwPolicyKey)).WithContext(ctx).Should(BeTrue()) + + lPolicy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { + policy.Name = "section-rlp" + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, + SectionName: ptr.To[gatewayapiv1.SectionName]("test-listener-toystore-com"), + } + }) + lPolicyKey := client.ObjectKeyFromObject(lPolicy) + Expect(k8sClient.Create(ctx, lPolicy)).To(Succeed()) + Eventually(tests.RLPIsAccepted(ctx, testClient(), lPolicyKey)).WithContext(ctx).Should(BeTrue()) + + Eventually(func(g Gomega) { + gateway := &gatewayapiv1.Gateway{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: TestGatewayName, Namespace: testNamespace}, gateway)).To(Succeed()) + condition := meta.FindStatusCondition(gateway.Status.Conditions, policyAffectedCondition) + // Gateway should list the condition with only the gw policy + g.Expect(condition).ToNot(BeNil()) + g.Expect(condition.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(condition.Message).To(ContainSubstring(gwPolicyKey.String())) + g.Expect(condition.Message).ToNot(ContainSubstring(lPolicyKey.String())) + + // Listeners + g.Expect(lo.EveryBy(gateway.Status.Listeners, func(item gatewayapiv1.ListenerStatus) bool { + lCond := meta.FindStatusCondition(item.Conditions, policyAffectedCondition) + if item.Name == *lPolicy.Spec.TargetRef.SectionName { + // Target section should include condition + return lCond != nil && lCond.Status == metav1.ConditionTrue && strings.Contains(lCond.Message, lPolicyKey.String()) && strings.Contains(lCond.Message, gwPolicyKey.String()) + } + + // all other sections list only the gw policy + return lCond != nil && lCond.Status == metav1.ConditionTrue && !strings.Contains(lCond.Message, lPolicyKey.String()) && strings.Contains(lCond.Message, gwPolicyKey.String()) + })).To(BeTrue()) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) + + It("route should list it's own policy and the parent policies", func(ctx SpecContext) { + gwPolicy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { + policy.Name = "gateway-rlp" + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, + } + }) + gwPolicyKey := client.ObjectKeyFromObject(gwPolicy) + Expect(k8sClient.Create(ctx, gwPolicy)).To(Succeed()) + Eventually(tests.RLPIsAccepted(ctx, testClient(), gwPolicyKey)).WithContext(ctx).Should(BeTrue()) + + lPolicy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { + policy.Name = "section-rlp" + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, + SectionName: ptr.To[gatewayapiv1.SectionName]("test-listener-toystore-com"), + } + }) + lPolicyKey := client.ObjectKeyFromObject(lPolicy) + Expect(k8sClient.Create(ctx, lPolicy)).To(Succeed()) + Eventually(tests.RLPIsAccepted(ctx, testClient(), lPolicyKey)).WithContext(ctx).Should(BeTrue()) + + rPolicy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { + policy.Name = "route-rlp" + }) + rPolicyKey := client.ObjectKeyFromObject(rPolicy) + Expect(k8sClient.Create(ctx, rPolicy)).To(Succeed()) + Eventually(tests.RLPIsAccepted(ctx, testClient(), rPolicyKey)).WithContext(ctx).Should(BeTrue()) + + Eventually(func(g Gomega) { + g.Expect(routeAffected(ctx, TestHTTPRouteName, policyAffectedCondition, gwPolicyKey, lPolicyKey, rPolicyKey)).To(BeTrue()) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) }) Context("DNSPolicy", func() { @@ -490,10 +769,10 @@ var _ = Describe("Target status reconciler", func() { // policyAcceptedAndTargetsAffected returns an assertion function that checks if a DNSPolicy is accepted // and the statuses of its target object has been all updated as affected by the policy - policyAcceptedAndTargetsAffected := func(ctx context.Context, policy *kuadrantv1alpha1.DNSPolicy) func() bool { + policyAcceptedAndTargetsAffected := func(ctx context.Context, policy *kuadrantv1alpha1.DNSPolicy, routeNames ...string) func() bool { return func() bool { policyKey := client.ObjectKeyFromObject(policy) - return isDNSPolicyAccepted(ctx, policyKey) && targetsAffected(ctx, policyKey, policyAffectedCondition, policy.Spec.TargetRef.LocalPolicyTargetReference) + return isDNSPolicyAccepted(ctx, policyKey) && targetsAffected(ctx, policyKey, policyAffectedCondition, policy.Spec.TargetRef.LocalPolicyTargetReference, routeNames...) } } @@ -514,7 +793,7 @@ var _ = Describe("Target status reconciler", func() { }).WithContext(ctx).Should(Succeed()) }, afterEachTimeOut) - It("adds PolicyAffected status condition to the targeted gateway", func(ctx SpecContext) { + It("adds PolicyAffected status condition to the targeted gateway and child routes", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1alpha1.DNSPolicy) { policy.Spec.ProviderRefs = append(policy.Spec.ProviderRefs, kuadrantdnsv1alpha1.ProviderRef{ Name: dnsProviderSecret.Name, @@ -523,11 +802,11 @@ var _ = Describe("Target status reconciler", func() { defer k8sClient.Delete(ctx, policy) Expect(k8sClient.Create(ctx, policy)).To(Succeed()) // policy should not be enforced since DNS Record is not ready because of the missing secret on the MZ - Eventually(isDNSPolicyEnforced(ctx, client.ObjectKeyFromObject(policy))).ShouldNot(BeTrue()) - Eventually(policyAcceptedAndTargetsAffected(ctx, policy)).WithContext(ctx).Should(BeTrue()) + Eventually(isDNSPolicyEnforced(ctx, client.ObjectKeyFromObject(policy))).WithContext(ctx).ShouldNot(BeTrue()) + Eventually(policyAcceptedAndTargetsAffected(ctx, policy, TestHTTPRouteName)).WithContext(ctx).Should(BeTrue()) }, testTimeOut) - It("removes PolicyAffected status condition from the targeted gateway when the policy is deleted", func(ctx SpecContext) { + It("removes PolicyAffected status condition from the targeted gateway and child routes when the policy is deleted", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1alpha1.DNSPolicy) { policy.Spec.ProviderRefs = append(policy.Spec.ProviderRefs, kuadrantdnsv1alpha1.ProviderRef{ Name: dnsProviderSecret.Name, @@ -548,7 +827,92 @@ var _ = Describe("Target status reconciler", func() { } condition := meta.FindStatusCondition(gateway.Status.Conditions, TestGatewayName) return condition == nil || !strings.Contains(condition.Message, policyKey.String()) || condition.Status == metav1.ConditionFalse + }).WithContext(ctx).Should(BeTrue()) + + Eventually(func(g Gomega) { + g.Expect(routeAffected(ctx, TestHTTPRouteName, policyAffectedCondition, policyKey)).Should(BeFalse()) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) + + It("adds section name polices only to specific listener status conditions", func(ctx SpecContext) { + policy := policyFactory(func(policy *kuadrantv1alpha1.DNSPolicy) { + policy.Name = "section-dns" + policy.Spec.TargetRef.SectionName = ptr.To[gatewayapiv1.SectionName]("test-listener-toystore-com") + policy.Spec.ProviderRefs = append(policy.Spec.ProviderRefs, kuadrantdnsv1alpha1.ProviderRef{ + Name: dnsProviderSecret.Name, + }) }) + policyKey := client.ObjectKeyFromObject(policy) + defer k8sClient.Delete(ctx, policy) + Expect(k8sClient.Create(ctx, policy)).To(Succeed()) + Eventually(func(g Gomega) { + gateway := &gatewayapiv1.Gateway{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: TestGatewayName, Namespace: testNamespace}, gateway)).To(Succeed()) + condition := meta.FindStatusCondition(gateway.Status.Conditions, policyAffectedCondition) + // Should not be in gw conditions + g.Expect(condition).To(BeNil()) + + g.Expect(lo.EveryBy(gateway.Status.Listeners, func(item gatewayapiv1.ListenerStatus) bool { + lCond := meta.FindStatusCondition(item.Conditions, policyAffectedCondition) + if item.Name == *policy.Spec.TargetRef.SectionName { + // Target section should include condition + return lCond != nil && lCond.Status == metav1.ConditionTrue && strings.Contains(lCond.Message, policyKey.String()) + } + + // all other sections should not have the condition + return lCond == nil + })).To(BeTrue()) + }).WithContext(ctx).Should(Succeed()) + }, testTimeOut) + + It("gateway policy is also listed with section policy", func(ctx SpecContext) { + gwPolicy := policyFactory(func(policy *kuadrantv1alpha1.DNSPolicy) { + policy.Name = "gateway-dns" + policy.Spec.ProviderRefs = append(policy.Spec.ProviderRefs, kuadrantdnsv1alpha1.ProviderRef{ + Name: dnsProviderSecret.Name, + }) + }) + gwPolicyKey := client.ObjectKeyFromObject(gwPolicy) + defer k8sClient.Delete(ctx, gwPolicy) + Expect(k8sClient.Create(ctx, gwPolicy)).To(Succeed()) + + lPolicy := policyFactory(func(policy *kuadrantv1alpha1.DNSPolicy) { + policy.Name = "section-dns" + policy.Spec.TargetRef.SectionName = ptr.To[gatewayapiv1.SectionName]("test-listener-toystore-com") + policy.Spec.ProviderRefs = append(policy.Spec.ProviderRefs, kuadrantdnsv1alpha1.ProviderRef{ + Name: dnsProviderSecret.Name, + }) + }) + + lPolicyKey := client.ObjectKeyFromObject(lPolicy) + defer k8sClient.Delete(ctx, lPolicy) + Expect(k8sClient.Create(ctx, lPolicy)).To(Succeed()) + + Eventually(func(g Gomega) { + gateway := &gatewayapiv1.Gateway{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: TestGatewayName, Namespace: testNamespace}, gateway)).To(Succeed()) + condition := meta.FindStatusCondition(gateway.Status.Conditions, policyAffectedCondition) + // Gateway should list the condition with only the gw policy + g.Expect(condition).ToNot(BeNil()) + g.Expect(condition.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(condition.Message).To(ContainSubstring(gwPolicyKey.String())) + g.Expect(condition.Message).ToNot(ContainSubstring(lPolicyKey.String())) + + // Listeners + g.Expect(lo.EveryBy(gateway.Status.Listeners, func(item gatewayapiv1.ListenerStatus) bool { + lCond := meta.FindStatusCondition(item.Conditions, policyAffectedCondition) + if item.Name == *lPolicy.Spec.TargetRef.SectionName { + // Target section should include condition + return lCond != nil && lCond.Status == metav1.ConditionTrue && strings.Contains(lCond.Message, lPolicyKey.String()) && strings.Contains(lCond.Message, gwPolicyKey.String()) + } + + // all other sections list only the gw policy + return lCond != nil && lCond.Status == metav1.ConditionTrue && !strings.Contains(lCond.Message, lPolicyKey.String()) && strings.Contains(lCond.Message, gwPolicyKey.String()) + })).To(BeTrue()) + + // Route + g.Expect(routeAffected(ctx, TestHTTPRouteName, policyAffectedCondition, gwPolicyKey, lPolicyKey)).To(BeTrue()) + }).WithContext(ctx).Should(Succeed()) }, testTimeOut) }) @@ -578,13 +942,13 @@ var _ = Describe("Target status reconciler", func() { // policyAcceptedAndTargetsAffected returns an assertion function that checks if a TLSPolicy is accepted // and the statuses of its target object has been all updated as affected by the policy - policyAcceptedAndTargetsAffected := func(ctx context.Context, policy *kuadrantv1alpha1.TLSPolicy) func() bool { + policyAcceptedAndTargetsAffected := func(ctx context.Context, policy *kuadrantv1alpha1.TLSPolicy, routeNames ...string) func() bool { return func() bool { policyKey := client.ObjectKeyFromObject(policy) if !isTLSPolicyAccepted(ctx, policyKey) { return false } - return targetsAffected(ctx, policyKey, policyAffectedCondition, policy.Spec.TargetRef) + return targetsAffected(ctx, policyKey, policyAffectedCondition, policy.Spec.TargetRef, routeNames...) } } @@ -600,17 +964,17 @@ var _ = Describe("Target status reconciler", func() { } }, afterEachTimeOut) - It("adds PolicyAffected status condition to the targeted gateway", func(ctx SpecContext) { + It("adds PolicyAffected status condition to the targeted gateway and child routes", func(ctx SpecContext) { policy := policyFactory() Expect(k8sClient.Create(ctx, policy)).To(Succeed()) - Eventually(policyAcceptedAndTargetsAffected(ctx, policy)).WithContext(ctx).Should(BeTrue()) + Eventually(policyAcceptedAndTargetsAffected(ctx, policy, TestHTTPRouteName)).WithContext(ctx).Should(BeTrue()) }, testTimeOut) - It("removes PolicyAffected status condition from the targeted gateway when the policy is deleted", func(ctx SpecContext) { + It("removes PolicyAffected status condition from the targeted gateway and child routes when the policy is deleted", func(ctx SpecContext) { policy := policyFactory() policyKey := client.ObjectKeyFromObject(policy) Expect(k8sClient.Create(ctx, policy)).To(Succeed()) - Eventually(policyAcceptedAndTargetsAffected(ctx, policy)).WithContext(ctx).Should(BeTrue()) + Eventually(policyAcceptedAndTargetsAffected(ctx, policy, TestHTTPRouteName)).WithContext(ctx).Should(BeTrue()) Expect(k8sClient.Delete(ctx, policy)).To(Succeed()) @@ -622,7 +986,11 @@ var _ = Describe("Target status reconciler", func() { } condition := meta.FindStatusCondition(gateway.Status.Conditions, TestGatewayName) return condition == nil || !strings.Contains(condition.Message, policyKey.String()) || condition.Status == metav1.ConditionFalse - }) + }).WithContext(ctx).Should(BeTrue()) + + Eventually(func(g Gomega) { + g.Expect(routeAffected(ctx, TestHTTPRouteName, policyAffectedCondition, policyKey)).Should(BeFalse()) + }).WithContext(ctx).Should(Succeed()) }, testTimeOut) }) })