Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions api/v1alpha1/httproutepolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ type HTTPRoutePolicySpec struct {
// TargetRef identifies an API object (enum: HTTPRoute, Ingress) to apply HTTPRoutePolicy to.
//
// target references.
// +listType=map
// +listMapKey=group
// +listMapKey=kind
// +listMapKey=name
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=16
TargetRefs []gatewayv1alpha2.LocalPolicyTargetReferenceWithSectionName `json:"targetRefs"`
Expand All @@ -47,8 +43,8 @@ type HTTPRoutePolicy struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec HTTPRoutePolicySpec `json:"spec,omitempty"`
Status gatewayv1alpha2.PolicyStatus `json:"status,omitempty"`
Spec HTTPRoutePolicySpec `json:"spec,omitempty"`
Status PolicyStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down
5 changes: 0 additions & 5 deletions config/crd/bases/gateway.apisix.io_httproutepolicies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,6 @@ spec:
maxItems: 16
minItems: 1
type: array
x-kubernetes-list-map-keys:
- group
- kind
- name
x-kubernetes-list-type: map
vars:
items:
x-kubernetes-preserve-unknown-fields: true
Expand Down
140 changes: 138 additions & 2 deletions internal/controller/httproute_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/go-logr/logr"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -18,7 +19,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
"sigs.k8s.io/gateway-api/apis/v1alpha2"

"github.com/api7/api7-ingress-controller/api/v1alpha1"
"github.com/api7/api7-ingress-controller/internal/controller/indexer"
Expand All @@ -33,10 +36,14 @@ type HTTPRouteReconciler struct { //nolint:revive
Log logr.Logger

Provider provider.Provider

genericEvent chan event.GenericEvent
}

// SetupWithManager sets up the controller with the Manager.
func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.genericEvent = make(chan event.GenericEvent, 100)

return ctrl.NewControllerManagedBy(mgr).
For(&gatewayv1.HTTPRoute{}).
WithEventFilter(predicate.GenerationChangedPredicate{}).
Expand Down Expand Up @@ -65,6 +72,29 @@ func (r *HTTPRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
},
),
).
Watches(&v1alpha1.HTTPRoutePolicy{},
handler.EnqueueRequestsFromMapFunc(r.listHTTPRouteByHTTPRoutePolicy),
builder.WithPredicates(
predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
return true
},
DeleteFunc: func(e event.DeleteEvent) bool {
return true
},
UpdateFunc: r.httpRoutePolicyPredicateOnUpdate,
GenericFunc: func(e event.GenericEvent) bool {
return false
},
},
),
).
WatchesRawSource(
source.Channel(
r.genericEvent,
handler.EnqueueRequestsFromMapFunc(r.listHTTPRouteForGenericEvent),
),
).
Complete(r)
}

Expand Down Expand Up @@ -131,6 +161,11 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
acceptStatus.msg = err.Error()
}

if err := r.processHTTPRoutePolicies(tctx, hr); err != nil {
acceptStatus.status = false
acceptStatus.msg = err.Error()
}

if err := r.processHTTPRouteBackendRefs(tctx); err != nil {
resolveRefStatus = status{
status: false,
Expand Down Expand Up @@ -161,6 +196,7 @@ func (r *HTTPRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
if err := r.Status().Update(ctx, hr); err != nil {
return ctrl.Result{}, err
}
UpdateStatus(r.Client, r.Log, tctx)
return ctrl.Result{}, nil
}

Expand Down Expand Up @@ -244,6 +280,80 @@ func (r *HTTPRouteReconciler) listHTTPRoutesForGateway(ctx context.Context, obj
return requests
}

func (r *HTTPRouteReconciler) listHTTPRouteByHTTPRoutePolicy(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
httpRoutePolicy, ok := obj.(*v1alpha1.HTTPRoutePolicy)
if !ok {
r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to HTTPRoutePolicy")
return nil
}

var keys = make(map[targetRefKey]struct{})
for _, ref := range httpRoutePolicy.Spec.TargetRefs {
if ref.Kind == "HTTPRoute" {
key := targetRefKey{
Group: gatewayv1.GroupName,
Namespace: gatewayv1.Namespace(obj.GetNamespace()),
Name: ref.Name,
}
if ref.SectionName != nil {
key.SectionName = *ref.SectionName
}
keys[key] = struct{}{}
}
}
for key := range keys {
var httpRoute gatewayv1.HTTPRoute
if err := r.Get(ctx, client.ObjectKey{Namespace: string(key.Namespace), Name: string(key.Name)}, &httpRoute); err != nil {
r.Log.Error(err, "failed to get HTTPRoute by HTTPRoutePolicy targetRef", "namespace", key.Namespace, "name", key.Name)
continue
}
if key.SectionName != "" {
var matchRuleName bool
for _, rule := range httpRoute.Spec.Rules {
if rule.Name != nil && *rule.Name == key.SectionName {
matchRuleName = true
break
}
}
if !matchRuleName {
r.Log.Error(errors.Errorf("failed to get HTTPRoute rule by HTTPRoutePolicy targetRef"), "namespace", key.Namespace, "name", key.Name, "sectionName", key.SectionName)
continue
}
}
requests = append(requests, reconcile.Request{
NamespacedName: types.NamespacedName{
Namespace: string(key.Namespace),
Name: string(key.Name),
},
})
}

return requests
}

func (r *HTTPRouteReconciler) listHTTPRouteForGenericEvent(ctx context.Context, obj client.Object) []reconcile.Request {
switch v := obj.(type) {
case *v1alpha1.HTTPRoutePolicy:
var (
namespacedNames = make(map[types.NamespacedName]struct{})
requests []reconcile.Request
)
for _, ref := range v.Spec.TargetRefs {
namespacedName := types.NamespacedName{Namespace: v.GetNamespace(), Name: string(ref.Name)}
if _, ok := namespacedNames[namespacedName]; !ok {
namespacedNames[namespacedName] = struct{}{}
if err := r.Get(ctx, namespacedName, new(gatewayv1.HTTPRoute)); err != nil {
r.Log.Info("failed to Get HTTPRoute", "namespace", namespacedName.Namespace, "name", namespacedName.Name)
continue
}
requests = append(requests, reconcile.Request{NamespacedName: namespacedName})
}
}
return requests
}
return nil
}

func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx *provider.TranslateContext) error {
var terr error
for _, backend := range tctx.BackendRefs {
Expand Down Expand Up @@ -306,7 +416,7 @@ func (r *HTTPRouteReconciler) processHTTPRouteBackendRefs(tctx *provider.Transla
return terr
}

func (t *HTTPRouteReconciler) processHTTPRoute(tctx *provider.TranslateContext, httpRoute *gatewayv1.HTTPRoute) error {
func (r *HTTPRouteReconciler) processHTTPRoute(tctx *provider.TranslateContext, httpRoute *gatewayv1.HTTPRoute) error {
var terror error
for _, rule := range httpRoute.Spec.Rules {
for _, filter := range rule.Filters {
Expand All @@ -315,7 +425,7 @@ func (t *HTTPRouteReconciler) processHTTPRoute(tctx *provider.TranslateContext,
}
if filter.ExtensionRef.Kind == "PluginConfig" {
pluginconfig := new(v1alpha1.PluginConfig)
if err := t.Get(context.Background(), client.ObjectKey{
if err := r.Get(context.Background(), client.ObjectKey{
Namespace: httpRoute.GetNamespace(),
Name: string(filter.ExtensionRef.Name),
}, pluginconfig); err != nil {
Expand Down Expand Up @@ -360,3 +470,29 @@ func (t *HTTPRouteReconciler) processHTTPRoute(tctx *provider.TranslateContext,

return terror
}

func (r *HTTPRouteReconciler) httpRoutePolicyPredicateOnUpdate(e event.UpdateEvent) bool {
oldPolicy, ok0 := e.ObjectOld.(*v1alpha1.HTTPRoutePolicy)
newPolicy, ok1 := e.ObjectNew.(*v1alpha1.HTTPRoutePolicy)
if !ok0 || !ok1 {
return false
}
var discardsRefs = make(map[string]v1alpha2.LocalPolicyTargetReferenceWithSectionName)
for _, ref := range oldPolicy.Spec.TargetRefs {
key := indexer.GenHTTPRoutePolicyIndexKey(string(ref.Group), string(ref.Kind), e.ObjectOld.GetNamespace(), string(ref.Name), "")
discardsRefs[key] = ref
}
for _, ref := range newPolicy.Spec.TargetRefs {
key := indexer.GenHTTPRoutePolicyIndexKey(string(ref.Group), string(ref.Kind), e.ObjectOld.GetNamespace(), string(ref.Name), "")
delete(discardsRefs, key)
}
if len(discardsRefs) > 0 {
dump := oldPolicy.DeepCopy()
dump.Spec.TargetRefs = make([]v1alpha2.LocalPolicyTargetReferenceWithSectionName, 0, len(discardsRefs))
for _, ref := range discardsRefs {
dump.Spec.TargetRefs = append(dump.Spec.TargetRefs, ref)
}
r.genericEvent <- event.GenericEvent{Object: dump}
}
return true
}
128 changes: 128 additions & 0 deletions internal/controller/httproutepolicy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package controller

import (
"context"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
"sigs.k8s.io/gateway-api/apis/v1alpha2"

"github.com/api7/api7-ingress-controller/api/v1alpha1"
"github.com/api7/api7-ingress-controller/internal/controller/indexer"
"github.com/api7/api7-ingress-controller/internal/provider"
)

func (r *HTTPRouteReconciler) processHTTPRoutePolicies(tctx *provider.TranslateContext, httpRoute *gatewayv1.HTTPRoute) error {
// list HTTPRoutePolices which sectionName is not specified
var (
checker = conflictChecker{
httpRoute: httpRoute,
policies: make(map[targetRefKey][]v1alpha1.HTTPRoutePolicy),
}
listForAllRules v1alpha1.HTTPRoutePolicyList
key = indexer.GenHTTPRoutePolicyIndexKey(v1alpha1.GroupVersion.Group, "HTTPRoute", httpRoute.GetNamespace(), httpRoute.GetName(), "")
)
if err := r.List(context.Background(), &listForAllRules, client.MatchingFields{indexer.PolicyTargetRefs: key}); err != nil {
return err
}

for _, item := range listForAllRules.Items {
checker.append("", item)
tctx.HTTPRoutePolicies["*"] = append(tctx.HTTPRoutePolicies["*"], item)
}

for _, rule := range httpRoute.Spec.Rules {
if rule.Name == nil {
continue
}

var (
ruleName = string(*rule.Name)
listForSectionRules v1alpha1.HTTPRoutePolicyList
key = indexer.GenHTTPRoutePolicyIndexKey(v1alpha1.GroupVersion.Group, "HTTPRoute", httpRoute.GetNamespace(), httpRoute.GetName(), ruleName)
)
if err := r.List(context.Background(), &listForSectionRules, client.MatchingFields{indexer.PolicyTargetRefs: key}); err != nil {
continue
}
for _, item := range listForSectionRules.Items {
checker.append(ruleName, item)
tctx.HTTPRoutePolicies[ruleName] = append(tctx.HTTPRoutePolicies[ruleName], item)
}
}

var (
status = true
reason = string(v1alpha2.PolicyReasonAccepted)
message string
)
if checker.conflict {
status = false
reason = string(v1alpha2.PolicyReasonConflicted)
message = "HTTPRoutePolicy conflict with others target to the HTTPRoute"

// clear HTTPRoutePolices from TranslateContext
tctx.HTTPRoutePolicies = make(map[string][]v1alpha1.HTTPRoutePolicy)
}

for _, policies := range checker.policies {
for i := range policies {
policy := policies[i]
r.modifyHTTPRoutePolicyStatus(httpRoute, &policy, status, reason, message)
tctx.StatusUpdaters = append(tctx.StatusUpdaters, &policy)
}
}

return nil
}

func (r *HTTPRouteReconciler) modifyHTTPRoutePolicyStatus(httpRoute *gatewayv1.HTTPRoute, policy *v1alpha1.HTTPRoutePolicy, status bool, reason, message string) {
condition := metav1.Condition{
Type: string(v1alpha2.PolicyConditionAccepted),
Status: metav1.ConditionTrue,
ObservedGeneration: policy.GetGeneration(),
LastTransitionTime: metav1.Time{Time: time.Now()},
Reason: reason,
Message: message,
}
if !status {
condition.Status = metav1.ConditionFalse
}
_ = SetAncestors(&policy.Status, httpRoute.Spec.ParentRefs, condition)
}

type conflictChecker struct {
httpRoute *gatewayv1.HTTPRoute
policies map[targetRefKey][]v1alpha1.HTTPRoutePolicy
conflict bool
}

type targetRefKey struct {
Group gatewayv1.Group
Namespace gatewayv1.Namespace
Name gatewayv1.ObjectName
SectionName gatewayv1.SectionName
}

func (c *conflictChecker) append(sectionName string, policy v1alpha1.HTTPRoutePolicy) {
key := targetRefKey{
Group: gatewayv1.GroupName,
Namespace: gatewayv1.Namespace(c.httpRoute.GetNamespace()),
Name: gatewayv1.ObjectName(c.httpRoute.GetName()),
SectionName: gatewayv1.SectionName(sectionName),
}
c.policies[key] = append(c.policies[key], policy)

if !c.conflict {
Loop:
for _, items := range c.policies {
for _, item := range items {
if item.Spec.Priority != policy.Spec.Priority || *item.Spec.Priority != *policy.Spec.Priority {
Copy link

Copilot AI Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition combining a direct pointer comparison and a dereferenced value comparison is redundant and could potentially lead to a nil pointer dereference if either Spec.Priority is nil. Consider adding explicit nil checks and simplifying the comparison logic.

Suggested change
if item.Spec.Priority != policy.Spec.Priority || *item.Spec.Priority != *policy.Spec.Priority {
if item.Spec.Priority == nil || policy.Spec.Priority == nil {
if item.Spec.Priority != policy.Spec.Priority { // One is nil, the other is not
c.conflict = true
break Loop
}
} else if *item.Spec.Priority != *policy.Spec.Priority {

Copilot uses AI. Check for mistakes.
c.conflict = true
break Loop
}
}
}
}
}
Loading
Loading