blob: 11484dbd41b994b093430cd1024c812310cb47cd [file] [log] [blame]
// 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
}),
})
}
}