| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you 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. |
| |
| package controller |
| |
| import ( |
| "cmp" |
| "context" |
| "slices" |
| |
| networkingv1 "k8s.io/api/networking/v1" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/types" |
| "k8s.io/utils/ptr" |
| "sigs.k8s.io/controller-runtime/pkg/client" |
| gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" |
| "sigs.k8s.io/gateway-api/apis/v1alpha2" |
| |
| "github.com/apache/apisix-ingress-controller/api/v1alpha1" |
| "github.com/apache/apisix-ingress-controller/internal/controller/indexer" |
| "github.com/apache/apisix-ingress-controller/internal/controller/status" |
| "github.com/apache/apisix-ingress-controller/internal/provider" |
| internaltypes "github.com/apache/apisix-ingress-controller/internal/types" |
| "github.com/apache/apisix-ingress-controller/internal/utils" |
| ) |
| |
| func (r *HTTPRouteReconciler) processHTTPRoutePolicies(tctx *provider.TranslateContext, httpRoute *gatewayv1.HTTPRoute) error { |
| // list HTTPRoutePolicies, which sectionName is not specified |
| var ( |
| list v1alpha1.HTTPRoutePolicyList |
| key = indexer.GenIndexKeyWithGK(gatewayv1.GroupName, internaltypes.KindHTTPRoute, httpRoute.GetNamespace(), httpRoute.GetName()) |
| ) |
| if err := r.List(context.Background(), &list, client.MatchingFields{indexer.PolicyTargetRefs: key}); err != nil { |
| return err |
| } |
| |
| if len(list.Items) == 0 { |
| return nil |
| } |
| |
| // set namespace in prentRef if it is not set |
| var parentRefs = make([]gatewayv1.ParentReference, len(httpRoute.Spec.ParentRefs)) |
| for i := range httpRoute.Spec.ParentRefs { |
| ref := httpRoute.Spec.ParentRefs[i] |
| if ref.Namespace == nil || *ref.Namespace == "" { |
| ref.Namespace = (*gatewayv1.Namespace)(&httpRoute.Namespace) |
| } |
| parentRefs[i] = ref |
| } |
| |
| var conflicts = make(map[types.NamespacedName]v1alpha1.HTTPRoutePolicy) |
| for _, rule := range httpRoute.Spec.Rules { |
| var policies = findPoliciesWhichTargetRefTheRule(rule.Name, internaltypes.KindHTTPRoute, list) |
| if conflict := checkPoliciesConflict(policies); conflict { |
| for _, policy := range policies { |
| namespacedName := types.NamespacedName{Namespace: policy.GetNamespace(), Name: policy.GetName()} |
| conflicts[namespacedName] = policy |
| } |
| } |
| } |
| |
| for i := range list.Items { |
| var ( |
| policy = list.Items[i] |
| namespacedName = types.NamespacedName{Namespace: policy.GetNamespace(), Name: policy.GetName()} |
| condition metav1.Condition |
| ) |
| if _, conflict := conflicts[namespacedName]; conflict { |
| condition.Status = metav1.ConditionFalse |
| condition.Reason = string(v1alpha2.PolicyReasonConflicted) |
| condition.Message = "HTTPRoutePolicy conflict with others target to the HTTPRoute" |
| } else { |
| tctx.HTTPRoutePolicies = append(tctx.HTTPRoutePolicies, policy) |
| } |
| |
| if updated := setAncestorsForHTTPRoutePolicyStatus(parentRefs, &policy, condition); updated { |
| tctx.StatusUpdaters = append(tctx.StatusUpdaters, status.Update{ |
| NamespacedName: utils.NamespacedName(&policy), |
| Resource: policy.DeepCopy(), |
| Mutator: status.MutatorFunc(func(obj client.Object) client.Object { |
| cp := obj.(*v1alpha1.HTTPRoutePolicy).DeepCopy() |
| cp.Status = policy.Status |
| return cp |
| }), |
| }) |
| } |
| } |
| |
| return nil |
| } |
| |
| func (r *HTTPRouteReconciler) updateHTTPRoutePolicyStatusOnDeleting(ctx context.Context, nn types.NamespacedName) error { |
| var ( |
| list v1alpha1.HTTPRoutePolicyList |
| key = indexer.GenIndexKeyWithGK(gatewayv1.GroupName, internaltypes.KindHTTPRoute, nn.Namespace, nn.Name) |
| ) |
| if err := r.List(ctx, &list, client.MatchingFields{indexer.PolicyTargetRefs: key}); err != nil { |
| return err |
| } |
| var ( |
| httpRoutes = make(map[types.NamespacedName]gatewayv1.HTTPRoute) |
| ) |
| for _, policy := range list.Items { |
| // collect all parentRefs for the HTTPRoutePolicy |
| var parentRefs []gatewayv1.ParentReference |
| for _, ref := range policy.Spec.TargetRefs { |
| var namespacedName = types.NamespacedName{Namespace: policy.GetNamespace(), Name: string(ref.Name)} |
| httpRoute, ok := httpRoutes[namespacedName] |
| if !ok { |
| if err := r.Get(ctx, namespacedName, &httpRoute); err != nil { |
| continue |
| } |
| httpRoutes[namespacedName] = httpRoute |
| } |
| parentRefs = append(parentRefs, httpRoute.Spec.ParentRefs...) |
| } |
| // delete AncestorRef which is not exist in the all parentRefs for each policy |
| updateDeleteAncestors(r.Updater, policy, parentRefs) |
| } |
| |
| return nil |
| } |
| |
| func (r *IngressReconciler) processHTTPRoutePolicies(tctx *provider.TranslateContext, ingress *networkingv1.Ingress) error { |
| var ( |
| list v1alpha1.HTTPRoutePolicyList |
| key = indexer.GenIndexKeyWithGK(networkingv1.GroupName, internaltypes.KindIngress, ingress.GetNamespace(), ingress.GetName()) |
| ) |
| if err := r.List(context.Background(), &list, client.MatchingFields{indexer.PolicyTargetRefs: key}); err != nil { |
| return err |
| } |
| |
| if len(list.Items) == 0 { |
| return nil |
| } |
| |
| var ( |
| condition metav1.Condition |
| ) |
| if conflict := checkPoliciesConflict(list.Items); conflict { |
| condition.Status = metav1.ConditionFalse |
| condition.Reason = string(v1alpha2.PolicyReasonConflicted) |
| condition.Message = "HTTPRoutePolicy conflict with others target to the Ingress" |
| } else { |
| tctx.HTTPRoutePolicies = list.Items |
| } |
| |
| for i := range list.Items { |
| policy := list.Items[i] |
| if updated := setAncestorsForHTTPRoutePolicyStatus(tctx.RouteParentRefs, &policy, condition); updated { |
| tctx.StatusUpdaters = append(tctx.StatusUpdaters, status.Update{ |
| NamespacedName: utils.NamespacedName(&policy), |
| Resource: policy.DeepCopy(), |
| Mutator: status.MutatorFunc(func(obj client.Object) client.Object { |
| cp := obj.(*v1alpha1.HTTPRoutePolicy).DeepCopy() |
| cp.Status = policy.Status |
| return cp |
| }), |
| }) |
| } |
| } |
| |
| return nil |
| } |
| |
| func (r *IngressReconciler) updateHTTPRoutePolicyStatusOnDeleting(ctx context.Context, nn types.NamespacedName) error { |
| var ( |
| list v1alpha1.HTTPRoutePolicyList |
| key = indexer.GenIndexKeyWithGK(networkingv1.GroupName, internaltypes.KindIngress, nn.Namespace, nn.Name) |
| ) |
| if err := r.List(ctx, &list, client.MatchingFields{indexer.PolicyTargetRefs: key}); err != nil { |
| return err |
| } |
| var ( |
| ingress2ParentRef = make(map[types.NamespacedName]gatewayv1.ParentReference) |
| ) |
| for _, policy := range list.Items { |
| // collect all parentRefs for the HTTPRoutePolicy |
| var parentRefs []gatewayv1.ParentReference |
| for _, ref := range policy.Spec.TargetRefs { |
| var namespacedName = types.NamespacedName{Namespace: policy.GetNamespace(), Name: string(ref.Name)} |
| parentRef, ok := ingress2ParentRef[namespacedName] |
| if !ok { |
| var ingress networkingv1.Ingress |
| if err := r.Get(ctx, namespacedName, &ingress); err != nil { |
| continue |
| } |
| ingressClass, err := FindMatchingIngressClass(ctx, r.Client, r.Log, &ingress) |
| if err != nil { |
| continue |
| } |
| parentRef = gatewayv1.ParentReference{ |
| Group: ptr.To(gatewayv1.Group(ingressClass.GroupVersionKind().Group)), |
| Kind: ptr.To(gatewayv1.Kind(KindIngressClass)), |
| Name: gatewayv1.ObjectName(ingressClass.Name), |
| } |
| ingress2ParentRef[namespacedName] = parentRef |
| } |
| parentRefs = append(parentRefs, parentRef) |
| } |
| // delete AncestorRef which is not exist in the all parentRefs |
| updateDeleteAncestors(r.Updater, policy, parentRefs) |
| } |
| |
| return nil |
| } |
| |
| func setAncestorsForHTTPRoutePolicyStatus(parentRefs []gatewayv1.ParentReference, policy *v1alpha1.HTTPRoutePolicy, condition metav1.Condition) bool { |
| return SetAncestors(&policy.Status, parentRefs, metav1.Condition{ |
| Type: cmp.Or(condition.Type, string(v1alpha2.PolicyConditionAccepted)), |
| Status: cmp.Or(condition.Status, metav1.ConditionTrue), |
| ObservedGeneration: policy.GetGeneration(), |
| LastTransitionTime: metav1.Now(), |
| Reason: cmp.Or(condition.Reason, string(v1alpha2.PolicyReasonAccepted)), |
| Message: condition.Message, |
| }) |
| } |
| |
| // checkPoliciesConflict determines if there is a conflict among the given HTTPRoutePolicy objects based on their priority values. |
| // It returns true if any policy has a different priority than the first policy in the list, otherwise false. |
| // An empty or single-element policies slice is considered non-conflicting. |
| // The function assumes all policies have a valid Spec.Priority field for comparison. |
| func checkPoliciesConflict(policies []v1alpha1.HTTPRoutePolicy) bool { |
| if len(policies) == 0 { |
| return false |
| } |
| priority := policies[0].Spec.Priority |
| for _, policy := range policies { |
| if !ptr.Equal(policy.Spec.Priority, priority) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // findPoliciesWhichTargetRefTheRule filters HTTPRoutePolicy objects whose TargetRefs match the given ruleName and kind. |
| // A match occurs if the TargetRef's Kind equals the provided kind and its SectionName is nil, empty, or equal to ruleName. |
| func findPoliciesWhichTargetRefTheRule(ruleName *gatewayv1.SectionName, kind string, list v1alpha1.HTTPRoutePolicyList) (policies []v1alpha1.HTTPRoutePolicy) { |
| for _, policy := range list.Items { |
| for _, ref := range policy.Spec.TargetRefs { |
| if string(ref.Kind) == kind && (ref.SectionName == nil || *ref.SectionName == "" || ptr.Equal(ref.SectionName, ruleName)) { |
| policies = append(policies, policy) |
| break |
| } |
| } |
| } |
| return |
| } |
| |
| // updateDeleteAncestors removes ancestor references from HTTPRoutePolicy statuses that are no longer present in the provided parentRefs. |
| func updateDeleteAncestors(updater status.Updater, policy v1alpha1.HTTPRoutePolicy, parentRefs []gatewayv1.ParentReference) { |
| length := len(policy.Status.Ancestors) |
| policy.Status.Ancestors = slices.DeleteFunc(policy.Status.Ancestors, func(ancestor v1alpha2.PolicyAncestorStatus) bool { |
| return !slices.ContainsFunc(parentRefs, func(ref gatewayv1.ParentReference) bool { |
| return parentRefValueEqual(ancestor.AncestorRef, ref) |
| }) |
| }) |
| if length != len(policy.Status.Ancestors) { |
| updater.Update(status.Update{ |
| NamespacedName: utils.NamespacedName(&policy), |
| Resource: policy.DeepCopy(), |
| Mutator: status.MutatorFunc(func(obj client.Object) client.Object { |
| cp := obj.(*v1alpha1.HTTPRoutePolicy).DeepCopy() |
| cp.Status = policy.Status |
| return cp |
| }), |
| }) |
| } |
| } |