From 6436530ef26dd4b71be54cc7bae23ef63ba8a190 Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Fri, 27 Sep 2024 10:28:30 +0200 Subject: [PATCH] sotw: rlp workflow Signed-off-by: Guilherme Cassolato --- api/v1/merge_strategies.go | 220 ++++++++ api/v1beta3/groupversion_info.go | 32 +- api/v1beta3/ratelimitpolicy_types.go | 505 ++++++++++-------- api/v1beta3/ratelimitpolicy_types_test.go | 102 ---- api/v1beta3/topology.go | 38 -- api/v1beta3/zz_generated.deepcopy.go | 69 ++- ...adrant-operator.clusterserviceversion.yaml | 2 +- .../kuadrant.io_ratelimitpolicies.yaml | 66 ++- .../templates/manifests.yaml | 66 ++- .../bases/kuadrant.io_ratelimitpolicies.yaml | 66 ++- .../effective_ratelimitpolicies_reconciler.go | 90 ++++ controllers/envoygateway_wasm_controller.go | 221 -------- ...imitador_cluster_envoyfilter_controller.go | 194 ------- controllers/limitador_limits_reconciler.go | 145 +++++ ...te_limiting_istio_wasmplugin_controller.go | 223 -------- controllers/ratelimit_workflow.go | 89 ++- controllers/ratelimitpolicies_validator.go | 48 ++ controllers/ratelimitpolicy_controller.go | 253 --------- ...elimitpolicy_enforced_status_controller.go | 311 ----------- controllers/ratelimitpolicy_limits.go | 198 ------- controllers/ratelimitpolicy_status.go | 59 -- controllers/ratelimitpolicy_status_test.go | 70 --- controllers/ratelimitpolicy_status_updater.go | 117 ++++ controllers/state_of_the_world.go | 29 +- controllers/target_status_controller.go | 8 +- controllers/test_common.go | 60 --- go.mod | 2 +- go.sum | 2 + main.go | 64 --- pkg/library/mappers/gateway_test.go | 50 +- pkg/library/mappers/utils_test.go | 2 +- pkg/rlptools/overrides.go | 2 +- pkg/rlptools/utils.go | 32 +- pkg/rlptools/utils_test.go | 10 +- pkg/rlptools/wasm/utils.go | 3 +- pkg/rlptools/wasm/utils_test.go | 2 +- .../ratelimitpolicy_controller_test.go | 188 ++++--- .../target_status_controller_test.go | 56 +- ...teway_limitador_cluster_controller_test.go | 14 +- tests/envoygateway/wasm_controller_test.go | 28 +- ...dor_cluster_envoyfilter_controller_test.go | 12 +- ...miting_istio_wasmplugin_controller_test.go | 180 ++++--- 42 files changed, 1575 insertions(+), 2353 deletions(-) create mode 100644 api/v1/merge_strategies.go delete mode 100644 api/v1beta3/topology.go create mode 100644 controllers/effective_ratelimitpolicies_reconciler.go delete mode 100644 controllers/envoygateway_wasm_controller.go delete mode 100644 controllers/limitador_cluster_envoyfilter_controller.go create mode 100644 controllers/limitador_limits_reconciler.go delete mode 100644 controllers/rate_limiting_istio_wasmplugin_controller.go create mode 100644 controllers/ratelimitpolicies_validator.go delete mode 100644 controllers/ratelimitpolicy_controller.go delete mode 100644 controllers/ratelimitpolicy_enforced_status_controller.go delete mode 100644 controllers/ratelimitpolicy_limits.go delete mode 100644 controllers/ratelimitpolicy_status.go delete mode 100644 controllers/ratelimitpolicy_status_test.go create mode 100644 controllers/ratelimitpolicy_status_updater.go diff --git a/api/v1/merge_strategies.go b/api/v1/merge_strategies.go new file mode 100644 index 000000000..b3860e639 --- /dev/null +++ b/api/v1/merge_strategies.go @@ -0,0 +1,220 @@ +/* +Copyright 2024. + +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. +*/ + +package v1 + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "sort" + "strings" + + "github.com/samber/lo" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" + + "github.com/kuadrant/policy-machinery/machinery" +) + +const ( + AtomicMergeStrategy = "atomic" + PolicyRuleMergeStrategy = "merge" +) + +type MergeableRule struct { + Spec any + Source string +} + +// +kubebuilder:object:generate=false +type MergeablePolicy interface { + machinery.Policy + + Rules() map[string]MergeableRule + SetRules(map[string]MergeableRule) + Empty() bool + + DeepCopyObject() runtime.Object +} + +type SortablePolicy interface { + machinery.Policy + GetCreationTimestamp() metav1.Time +} + +type PolicyByCreationTimestamp []SortablePolicy + +func (a PolicyByCreationTimestamp) Len() int { return len(a) } +func (a PolicyByCreationTimestamp) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a PolicyByCreationTimestamp) Less(i, j int) bool { + p1Time := ptr.To(a[i].GetCreationTimestamp()) + p2Time := ptr.To(a[j].GetCreationTimestamp()) + if !p1Time.Equal(p2Time) { + return p1Time.Before(p2Time) + } + // The object appearing first in alphabetical order by "{namespace}/{name}". + return fmt.Sprintf("%s/%s", a[i].GetNamespace(), a[i].GetName()) < fmt.Sprintf("%s/%s", a[j].GetNamespace(), a[j].GetName()) +} + +// AtomicDefaultsMergeStrategy implements a merge strategy that returns the target Policy if it exists, +// otherwise it returns the source Policy. +func AtomicDefaultsMergeStrategy(source, target machinery.Policy) machinery.Policy { + if source == nil { + return target + } + if target == nil { + return source + } + + mergeableTargetPolicy := target.(MergeablePolicy) + + if !mergeableTargetPolicy.Empty() { + return mergeableTargetPolicy.DeepCopyObject().(machinery.Policy) + } + + return source.(MergeablePolicy).DeepCopyObject().(machinery.Policy) +} + +var _ machinery.MergeStrategy = AtomicDefaultsMergeStrategy + +// AtomicOverridesMergeStrategy implements a merge strategy that overrides a target Policy with +// a source one. +func AtomicOverridesMergeStrategy(source, _ machinery.Policy) machinery.Policy { + if source == nil { + return nil + } + return source.(MergeablePolicy).DeepCopyObject().(machinery.Policy) +} + +var _ machinery.MergeStrategy = AtomicOverridesMergeStrategy + +// PolicyRuleDefaultsMergeStrategy implements a merge strategy that merges a source Policy into a target one +// by keeping the policy rules from the target and adding the ones from the source that do not exist in the target. +func PolicyRuleDefaultsMergeStrategy(source, target machinery.Policy) machinery.Policy { + if source == nil { + return target + } + if target == nil { + return source + } + + sourceMergeablePolicy := source.(MergeablePolicy) + targetMergeablePolicy := target.(MergeablePolicy) + + // copy rules from the target + rules := targetMergeablePolicy.Rules() + + // add extra rules from the source + for ruleID, rule := range sourceMergeablePolicy.Rules() { + if _, ok := targetMergeablePolicy.Rules()[ruleID]; !ok { + rules[ruleID] = MergeableRule{ + Spec: rule.Spec, + Source: source.GetLocator(), + } + } + } + + mergedPolicy := targetMergeablePolicy.DeepCopyObject().(MergeablePolicy) + mergedPolicy.SetRules(rules) + return mergedPolicy +} + +var _ machinery.MergeStrategy = PolicyRuleDefaultsMergeStrategy + +// PolicyRuleOverridesMergeStrategy implements a merge strategy that merges a source Policy into a target one +// by using the policy rules from the source and keeping from the target only the policy rules that do not exist in +// the source. +func PolicyRuleOverridesMergeStrategy(source, target machinery.Policy) machinery.Policy { + sourceMergeablePolicy := source.(MergeablePolicy) + targetMergeablePolicy := target.(MergeablePolicy) + + // copy rules from the source + rules := sourceMergeablePolicy.Rules() + + // add extra rules from the target + for ruleID, rule := range targetMergeablePolicy.Rules() { + if _, ok := sourceMergeablePolicy.Rules()[ruleID]; !ok { + rules[ruleID] = rule + } + } + + mergedPolicy := targetMergeablePolicy.DeepCopyObject().(MergeablePolicy) + mergedPolicy.SetRules(rules) + return mergedPolicy +} + +var _ machinery.MergeStrategy = PolicyRuleOverridesMergeStrategy + +func DefaultsMergeStrategy(strategy string) machinery.MergeStrategy { + switch strategy { + case AtomicMergeStrategy: + return AtomicDefaultsMergeStrategy + case PolicyRuleMergeStrategy: + return PolicyRuleDefaultsMergeStrategy + default: + return AtomicDefaultsMergeStrategy + } +} + +func OverridesMergeStrategy(strategy string) machinery.MergeStrategy { + switch strategy { + case AtomicMergeStrategy: + return AtomicOverridesMergeStrategy + case PolicyRuleMergeStrategy: + return PolicyRuleOverridesMergeStrategy + default: + return AtomicOverridesMergeStrategy + } +} + +// EffectivePolicyForPath returns the effective policy for a given path, merging all policies in the path. +// The policies in the path are sorted from the least specific to the most specific. +// Only policies whose predicate returns true are considered. +func EffectivePolicyForPath[T machinery.Policy](path []machinery.Targetable, predicate func(machinery.Policy) bool) *T { + policies := PoliciesInPath(path, predicate) + if len(policies) == 0 { + return nil + } + + // map reduces the policies from most specific to least specific, merging them into one effective policy + effectivePolicy := lo.ReduceRight(policies, func(effectivePolicy machinery.Policy, policy machinery.Policy, _ int) machinery.Policy { + return effectivePolicy.Merge(policy) + }, policies[len(policies)-1]) + + concreteEffectivePolicy, _ := effectivePolicy.(T) + return &concreteEffectivePolicy +} + +// OrderedPoliciesForPath gathers all policies in a path sorted from the least specific to the most specific. +// Only policies whose predicate returns true are considered. +func PoliciesInPath(path []machinery.Targetable, predicate func(machinery.Policy) bool) []machinery.Policy { + return lo.FlatMap(path, func(targetable machinery.Targetable, _ int) []machinery.Policy { + policies := lo.FilterMap(targetable.Policies(), func(policy machinery.Policy, _ int) (SortablePolicy, bool) { + p, sortable := policy.(SortablePolicy) + return p, sortable && predicate(p) + }) + sort.Sort(PolicyByCreationTimestamp(policies)) + return lo.Map(policies, func(policy SortablePolicy, _ int) machinery.Policy { return policy }) + }) +} + +func PathID(path []machinery.Targetable) string { + s := strings.Join(lo.Map(path, machinery.MapTargetableToLocatorFunc), ">") + hash := sha256.Sum256([]byte(s)) + return hex.EncodeToString(hash[:8]) +} diff --git a/api/v1beta3/groupversion_info.go b/api/v1beta3/groupversion_info.go index 518ca630c..bca312b26 100644 --- a/api/v1beta3/groupversion_info.go +++ b/api/v1beta3/groupversion_info.go @@ -1,5 +1,5 @@ /* -Copyright 2021. +Copyright 2024. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,23 +14,33 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package v1beta3 contains API Schema definitions for the kuadrant v1beta3 API group +// API schema definitions for the Kuadrant v1beta3 API group // +kubebuilder:object:generate=true // +groupName=kuadrant.io package v1beta3 import ( "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" + ctrl "sigs.k8s.io/controller-runtime/pkg/scheme" ) -var ( - // GroupVersion is group version used to register these objects - GroupVersion = schema.GroupVersion{Group: "kuadrant.io", Version: "v1beta3"} +// GroupName specifies the group name used to register the objects. +const GroupName = "kuadrant.io" - // SchemeBuilder is used to add go types to the GroupVersionKind scheme - SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} +// GroupVersion specifies the group and the version used to register the objects. +var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta3"} - // AddToScheme adds the types in this group-version to the given scheme. - AddToScheme = SchemeBuilder.AddToScheme -) +// SchemeGroupVersion is group version used to register these objects +// Deprecated: use GroupVersion instead. +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta3"} + +// SchemeBuilder is used to add go types to the GroupVersionKind scheme +var SchemeBuilder = &ctrl.Builder{GroupVersion: GroupVersion} + +// AddToScheme adds the types in this group-version to the given scheme. +var AddToScheme = SchemeBuilder.AddToScheme + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} diff --git a/api/v1beta3/ratelimitpolicy_types.go b/api/v1beta3/ratelimitpolicy_types.go index 587ae0dbf..bc7b29a2d 100644 --- a/api/v1beta3/ratelimitpolicy_types.go +++ b/api/v1beta3/ratelimitpolicy_types.go @@ -1,5 +1,5 @@ /* -Copyright 2021 Red Hat, Inc. +Copyright 2024. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,47 +17,20 @@ limitations under the License. package v1beta3 import ( - "context" - "fmt" + "encoding/json" - "github.com/go-logr/logr" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "github.com/kuadrant/policy-machinery/machinery" + + kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1" kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" - "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" "github.com/kuadrant/kuadrant-operator/pkg/library/utils" ) -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. - -var ( - RateLimitPolicyGVK schema.GroupVersionKind = schema.GroupVersionKind{ - Group: GroupVersion.Group, - Version: GroupVersion.Version, - Kind: "RateLimitPolicy", - } -) - -// ContextSelector defines one item from the well known attributes -// Attributes: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes -// Well-known selectors: https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors -// They are named by a dot-separated path (e.g. request.path) -// Example: "request.path" -> The path portion of the URL -// +kubebuilder:validation:MinLength=1 -// +kubebuilder:validation:MaxLength=253 -type ContextSelector string - -// +kubebuilder:validation:Enum:=eq;neq;startswith;endswith;incl;excl;matches -type WhenConditionOperator string - const ( EqualOperator WhenConditionOperator = "eq" NotEqualOperator WhenConditionOperator = "neq" @@ -67,271 +40,365 @@ const ( ExcludeOperator WhenConditionOperator = "excl" MatchesOperator WhenConditionOperator = "matches" + // TODO: remove after fixing the integration tests that still depend on these RateLimitPolicyBackReferenceAnnotationName = "kuadrant.io/ratelimitpolicies" RateLimitPolicyDirectReferenceAnnotationName = "kuadrant.io/ratelimitpolicy" ) -// +kubebuilder:validation:Enum:=second;minute;hour;day -type TimeUnit string +var ( + RateLimitPolicyGroupKind = schema.GroupKind{Group: SchemeGroupVersion.Group, Kind: "RateLimitPolicy"} + RateLimitPoliciesResource = SchemeGroupVersion.WithResource("ratelimitpolicies") +) -// Rate defines the actual rate limit that will be used when there is a match -type Rate struct { - // Limit defines the max value allowed for a given period of time - Limit int `json:"limit"` +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:metadata:labels="gateway.networking.k8s.io/policy=inherited" +// +kubebuilder:printcolumn:name="Accepted",type=string,JSONPath=`.status.conditions[?(@.type=="Accepted")].status`,description="RateLimitPolicy Accepted",priority=2 +// +kubebuilder:printcolumn:name="Enforced",type=string,JSONPath=`.status.conditions[?(@.type=="Enforced")].status`,description="RateLimitPolicy Enforced",priority=2 +// +kubebuilder:printcolumn:name="TargetKind",type="string",JSONPath=".spec.targetRef.kind",description="Kind of the object to which the policy aaplies",priority=2 +// +kubebuilder:printcolumn:name="TargetName",type="string",JSONPath=".spec.targetRef.name",description="Name of the object to which the policy applies",priority=2 +// +kubebuilder:printcolumn:name="TargetSection",type="string",JSONPath=".spec.targetRef.sectionName",description="Name of the section within the object to which the policy applies ",priority=2 +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" - // Duration defines the time period for which the Limit specified above applies. - Duration int `json:"duration"` +// RateLimitPolicy enables rate limiting for service workloads in a Gateway API network +type RateLimitPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` - // Duration defines the time uni - // Possible values are: "second", "minute", "hour", "day" - Unit TimeUnit `json:"unit"` + Spec RateLimitPolicySpec `json:"spec,omitempty"` + Status RateLimitPolicyStatus `json:"status,omitempty"` } -// WhenCondition defines semantics for matching an HTTP request based on conditions -// https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec -type WhenCondition struct { - // Selector defines one item from the well known selectors - // TODO Document properly "Well-known selector" https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors - Selector ContextSelector `json:"selector"` - - // The binary operator to be applied to the content fetched from the selector - // Possible values are: "eq" (equal to), "neq" (not equal to) - Operator WhenConditionOperator `json:"operator"` +var _ machinery.Policy = &RateLimitPolicy{} - // The value of reference for the comparison. - Value string `json:"value"` +func (p *RateLimitPolicy) GetNamespace() string { + return p.Namespace } -// Limit represents a complete rate limit configuration -type Limit struct { - // When holds the list of conditions for the policy to be enforced. - // Called also "soft" conditions as route selectors must also match - // +optional - When []WhenCondition `json:"when,omitempty"` +func (p *RateLimitPolicy) GetName() string { + return p.Name +} - // Counters defines additional rate limit counters based on context qualifiers and well known selectors - // TODO Document properly "Well-known selector" https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors - // +optional - Counters []ContextSelector `json:"counters,omitempty"` +func (p *RateLimitPolicy) GetLocator() string { + return machinery.LocatorFromObject(p) +} - // Rates holds the list of limit rates - // +optional - Rates []Rate `json:"rates,omitempty"` +// DEPRECATED: Use GetTargetRefs instead +func (p *RateLimitPolicy) GetTargetRef() gatewayapiv1alpha2.LocalPolicyTargetReference { + return p.Spec.TargetRef.LocalPolicyTargetReference } -func (l Limit) CountersAsStringList() []string { - if len(l.Counters) == 0 { - return nil +func (p *RateLimitPolicy) GetTargetRefs() []machinery.PolicyTargetReference { + return []machinery.PolicyTargetReference{ + machinery.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReferenceWithSectionName: p.Spec.TargetRef, + PolicyNamespace: p.Namespace, + }, } - return utils.Map(l.Counters, func(counter ContextSelector) string { return string(counter) }) } -// RateLimitPolicySpec defines the desired state of RateLimitPolicy -// +kubebuilder:validation:XValidation:rule="!(has(self.defaults) && has(self.limits))",message="Implicit and explicit defaults are mutually exclusive" -// +kubebuilder:validation:XValidation:rule="!(has(self.defaults) && has(self.overrides))",message="Overrides and explicit defaults are mutually exclusive" -// +kubebuilder:validation:XValidation:rule="!(has(self.overrides) && has(self.limits))",message="Overrides and implicit defaults are mutually exclusive" -// +kubebuilder:validation:XValidation:rule="!(has(self.overrides) && self.targetRef.kind != 'Gateway')",message="Overrides are only allowed for policies targeting a Gateway resource" -type RateLimitPolicySpec struct { - // TargetRef identifies an API object to apply policy to. - // +kubebuilder:validation:XValidation:rule="self.group == 'gateway.networking.k8s.io'",message="Invalid targetRef.group. The only supported value is 'gateway.networking.k8s.io'" - // +kubebuilder:validation:XValidation:rule="self.kind == 'HTTPRoute' || self.kind == 'Gateway'",message="Invalid targetRef.kind. The only supported values are 'HTTPRoute' and 'Gateway'" - TargetRef gatewayapiv1alpha2.LocalPolicyTargetReference `json:"targetRef"` - - // Defaults define explicit default values for this policy and for policies inheriting this policy. - // Defaults are mutually exclusive with implicit defaults defined by RateLimitPolicyCommonSpec. - // +optional - Defaults *RateLimitPolicyCommonSpec `json:"defaults,omitempty"` - - // Overrides define override values for this policy and for policies inheriting this policy. - // Overrides are mutually exclusive with implicit defaults and explicit Defaults defined by RateLimitPolicyCommonSpec. - // +optional - Overrides *RateLimitPolicyCommonSpec `json:"overrides,omitempty"` - - // RateLimitPolicyCommonSpec defines implicit default values for this policy and for policies inheriting this policy. - // RateLimitPolicyCommonSpec is mutually exclusive with explicit defaults defined by Defaults. - RateLimitPolicyCommonSpec `json:""` +func (p *RateLimitPolicy) GetMergeStrategy() machinery.MergeStrategy { + if spec := p.Spec.Defaults; spec != nil { + return kuadrantv1.DefaultsMergeStrategy(spec.Strategy) + } + if spec := p.Spec.Overrides; spec != nil { + return kuadrantv1.OverridesMergeStrategy(spec.Strategy) + } + return kuadrantv1.AtomicDefaultsMergeStrategy } -// RateLimitPolicyCommonSpec contains common shared fields. -type RateLimitPolicyCommonSpec struct { - // Limits holds the struct of limits indexed by a unique name - // +optional - // +kubebuilder:validation:MaxProperties=14 - Limits map[string]Limit `json:"limits,omitempty"` +func (p *RateLimitPolicy) Merge(other machinery.Policy) machinery.Policy { + source, ok := other.(*RateLimitPolicy) + if !ok { + return p + } + return source.GetMergeStrategy()(source, p) } -// RateLimitPolicyStatus defines the observed state of RateLimitPolicy -type RateLimitPolicyStatus struct { - reconcilers.StatusMeta `json:",inline"` +var _ kuadrantv1.MergeablePolicy = &RateLimitPolicy{} - // Represents the observations of a foo's current state. - // Known .status.conditions.type are: "Available" - // +patchMergeKey=type - // +patchStrategy=merge - // +listType=map - // +listMapKey=type - Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +func (p *RateLimitPolicy) Empty() bool { + return len(p.Spec.Proper().Limits) == 0 } -func RateLimitPolicyStatusMutator(desiredStatus *RateLimitPolicyStatus, logger logr.Logger) reconcilers.StatusMutatorFunc { - return func(obj client.Object) (bool, error) { - existingRLP, ok := obj.(*RateLimitPolicy) - if !ok { - return false, fmt.Errorf("unsupported object type %T", obj) - } +func (p *RateLimitPolicy) Rules() map[string]kuadrantv1.MergeableRule { + rules := make(map[string]kuadrantv1.MergeableRule) - opts := cmp.Options{ - cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime"), - cmpopts.IgnoreMapEntries(func(k string, _ any) bool { - return k == "lastTransitionTime" - }), + for ruleId := range p.Spec.Proper().Limits { + limit := p.Spec.Proper().Limits[ruleId] + origin := limit.Origin + if origin == "" { + origin = p.GetLocator() } - - if cmp.Equal(*desiredStatus, existingRLP.Status, opts) { - return false, nil + rules[ruleId] = kuadrantv1.MergeableRule{ + Spec: limit, + Source: origin, } + } - if logger.V(1).Enabled() { - diff := cmp.Diff(*desiredStatus, existingRLP.Status, opts) - logger.V(1).Info("status not equal", "difference", diff) - } + return rules +} - existingRLP.Status = *desiredStatus +func (p *RateLimitPolicy) SetRules(rules map[string]kuadrantv1.MergeableRule) { + if len(rules) > 0 && p.Spec.Proper().Limits == nil { + p.Spec.Proper().Limits = make(map[string]Limit) + } - return true, nil + for ruleId := range rules { + rule := rules[ruleId] + limit := rule.Spec.(Limit) + limit.Origin = rule.Source + p.Spec.Proper().Limits[ruleId] = limit } } -func (s *RateLimitPolicyStatus) GetConditions() []metav1.Condition { - return s.Conditions +// DEPRECATED. impl: kuadrant.Policy +func (p *RateLimitPolicy) GetStatus() kuadrantgatewayapi.PolicyStatus { + return &p.Status } -var _ kuadrant.Policy = &RateLimitPolicy{} -var _ kuadrant.Referrer = &RateLimitPolicy{} -var _ kuadrantgatewayapi.Policy = &RateLimitPolicy{} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:metadata:labels="gateway.networking.k8s.io/policy=inherited" -// +kubebuilder:printcolumn:name="Accepted",type=string,JSONPath=`.status.conditions[?(@.type=="Accepted")].status`,description="RateLimitPolicy Accepted",priority=2 -// +kubebuilder:printcolumn:name="Enforced",type=string,JSONPath=`.status.conditions[?(@.type=="Enforced")].status`,description="RateLimitPolicy Enforced",priority=2 -// +kubebuilder:printcolumn:name="TargetRefKind",type="string",JSONPath=".spec.targetRef.kind",description="Type of the referenced Gateway API resource",priority=2 -// +kubebuilder:printcolumn:name="TargetRefName",type="string",JSONPath=".spec.targetRef.name",description="Name of the referenced Gateway API resource",priority=2 -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" - -// RateLimitPolicy enables rate limiting for service workloads in a Gateway API network -type RateLimitPolicy struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` +// DEPRECATED. impl: kuadrant.Policy +func (p *RateLimitPolicy) PolicyClass() kuadrantgatewayapi.PolicyClass { + return kuadrantgatewayapi.InheritedPolicy +} - Spec RateLimitPolicySpec `json:"spec,omitempty"` - Status RateLimitPolicyStatus `json:"status,omitempty"` +// DEPRECATED. impl: kuadrant.Policy +func (p *RateLimitPolicy) GetWrappedNamespace() gatewayapiv1.Namespace { + return gatewayapiv1.Namespace(p.GetNamespace()) } -func (r *RateLimitPolicy) GetObservedGeneration() int64 { return r.Status.GetObservedGeneration() } -func (r *RateLimitPolicy) SetObservedGeneration(o int64) { r.Status.SetObservedGeneration(o) } +// DEPRECATED. impl: kuadrant.Policy +func (p *RateLimitPolicy) GetRulesHostnames() []string { + return []string{} +} -//+kubebuilder:object:root=true +// DEPRECATED. impl: kuadrant.Policy +func (p *RateLimitPolicy) Kind() string { + return RateLimitPolicyGroupKind.Kind +} -// RateLimitPolicyList contains a list of RateLimitPolicy -type RateLimitPolicyList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []RateLimitPolicy `json:"items"` +// TODO: remove +func (p *RateLimitPolicy) DirectReferenceAnnotationName() string { + return RateLimitPolicyDirectReferenceAnnotationName } -func (l *RateLimitPolicyList) GetItems() []kuadrant.Policy { - return utils.Map(l.Items, func(item RateLimitPolicy) kuadrant.Policy { - return &item - }) +// TODO: remove +func (p *RateLimitPolicy) BackReferenceAnnotationName() string { + return RateLimitPolicyBackReferenceAnnotationName } -func (r *RateLimitPolicy) GetTargetRef() gatewayapiv1alpha2.LocalPolicyTargetReference { - return r.Spec.TargetRef +// +kubebuilder:validation:XValidation:rule="!(has(self.defaults) && has(self.limits))",message="Implicit and explicit defaults are mutually exclusive" +// +kubebuilder:validation:XValidation:rule="!(has(self.defaults) && has(self.overrides))",message="Overrides and explicit defaults are mutually exclusive" +// +kubebuilder:validation:XValidation:rule="!(has(self.overrides) && has(self.limits))",message="Overrides and implicit defaults are mutually exclusive" +type RateLimitPolicySpec struct { + // Reference to the object to which this policy applies. + // +kubebuilder:validation:XValidation:rule="self.group == 'gateway.networking.k8s.io'",message="Invalid targetRef.group. The only supported value is 'gateway.networking.k8s.io'" + // +kubebuilder:validation:XValidation:rule="self.kind == 'HTTPRoute' || self.kind == 'Gateway'",message="Invalid targetRef.kind. The only supported values are 'HTTPRoute' and 'Gateway'" + TargetRef gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName `json:"targetRef"` + + // Rules to apply as defaults. Can be overridden by more specific policiy rules lower in the hierarchy and by less specific policy overrides. + // Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). + // +optional + Defaults *MergeableRateLimitPolicySpec `json:"defaults,omitempty"` + + // Rules to apply as overrides. Override all policy rules lower in the hierarchy. Can be overridden by less specific policy overrides. + // Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). + // +optional + Overrides *MergeableRateLimitPolicySpec `json:"overrides,omitempty"` + + // Bare set of policy rules (implicit defaults). + // Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). + RateLimitPolicySpecProper `json:""` } -func (r *RateLimitPolicy) GetStatus() kuadrantgatewayapi.PolicyStatus { - return &r.Status +// UnmarshalJSON unmarshals the RateLimitPolicySpec from JSON byte array. +// This should not be needed, but runtime.DefaultUnstructuredConverter.FromUnstructured does not work well with embedded structs. +func (s *RateLimitPolicySpec) UnmarshalJSON(j []byte) error { + targetRef := struct { + gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName `json:"targetRef"` + }{} + if err := json.Unmarshal(j, &targetRef); err != nil { + return err + } + s.TargetRef = targetRef.LocalPolicyTargetReferenceWithSectionName + + defaults := &struct { + *MergeableRateLimitPolicySpec `json:"defaults,omitempty"` + }{} + if err := json.Unmarshal(j, defaults); err != nil { + return err + } + s.Defaults = defaults.MergeableRateLimitPolicySpec + + overrides := &struct { + *MergeableRateLimitPolicySpec `json:"overrides,omitempty"` + }{} + if err := json.Unmarshal(j, overrides); err != nil { + return err + } + s.Overrides = overrides.MergeableRateLimitPolicySpec + + proper := struct { + RateLimitPolicySpecProper `json:""` + }{} + if err := json.Unmarshal(j, &proper); err != nil { + return err + } + s.RateLimitPolicySpecProper = proper.RateLimitPolicySpecProper + + return nil } -func (r *RateLimitPolicy) GetWrappedNamespace() gatewayapiv1.Namespace { - return gatewayapiv1.Namespace(r.Namespace) +func (s *RateLimitPolicySpec) Proper() *RateLimitPolicySpecProper { + if s.Defaults != nil { + return &s.Defaults.RateLimitPolicySpecProper + } + + if s.Overrides != nil { + return &s.Overrides.RateLimitPolicySpecProper + } + + return &s.RateLimitPolicySpecProper } -func (r *RateLimitPolicy) GetRulesHostnames() (ruleHosts []string) { - ruleHosts = make([]string, 0) - return +type MergeableRateLimitPolicySpec struct { + // Strategy defines the merge strategy to apply when merging this policy with other policies. + // +kubebuilder:validation:Enum=atomic;merge + // +kubebuilder:default=atomic + Strategy string `json:"strategy,omitempty"` + + RateLimitPolicySpecProper `json:""` } -func (r *RateLimitPolicy) Kind() string { - return NewRateLimitPolicyType().GetGVK().Kind +// RateLimitPolicySpecProper contains common shared fields for defaults and overrides +type RateLimitPolicySpecProper struct { + // Limits holds the struct of limits indexed by a unique name + // +optional + Limits map[string]Limit `json:"limits,omitempty"` } -func (r *RateLimitPolicy) TargetProgrammedGatewaysOnly() bool { - return true +// Limit represents a complete rate limit configuration +type Limit struct { + // When holds the list of conditions for the policy to be enforced. + // Called also "soft" conditions as route selectors must also match + // +optional + When []WhenCondition `json:"when,omitempty"` + + // Counters defines additional rate limit counters based on context qualifiers and well known selectors + // TODO Document properly "Well-known selector" https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors + // +optional + Counters []ContextSelector `json:"counters,omitempty"` + + // Rates holds the list of limit rates + // +optional + Rates []Rate `json:"rates,omitempty"` + + // origin stores the resource where the limit is originally defined (internal use) + Origin string `json:"-"` } -func (r *RateLimitPolicy) PolicyClass() kuadrantgatewayapi.PolicyClass { - return kuadrantgatewayapi.InheritedPolicy +func (l Limit) CountersAsStringList() []string { + if len(l.Counters) == 0 { + return nil + } + return utils.Map(l.Counters, func(counter ContextSelector) string { return string(counter) }) } -func (r *RateLimitPolicy) BackReferenceAnnotationName() string { - return NewRateLimitPolicyType().BackReferenceAnnotationName() +// +kubebuilder:validation:Enum:=second;minute;hour;day +type TimeUnit string + +var timeUnitMap = map[TimeUnit]int{ + TimeUnit("second"): 1, + TimeUnit("minute"): 60, + TimeUnit("hour"): 60 * 60, + TimeUnit("day"): 60 * 60 * 24, } -func (r *RateLimitPolicy) DirectReferenceAnnotationName() string { - return NewRateLimitPolicyType().DirectReferenceAnnotationName() +// Rate defines the actual rate limit that will be used when there is a match +type Rate struct { + // Limit defines the max value allowed for a given period of time + Limit int `json:"limit"` + + // Duration defines the time period for which the Limit specified above applies. + Duration int `json:"duration"` + + // Duration defines the time uni + // Possible values are: "second", "minute", "hour", "day" + Unit TimeUnit `json:"unit"` } -// CommonSpec returns the Default RateLimitPolicyCommonSpec if it is defined. -// Otherwise, it returns the RateLimitPolicyCommonSpec from the spec. -// This function should be used instead of accessing the fields directly, so that either the explicit or implicit default -// is returned. -func (r *RateLimitPolicySpec) CommonSpec() *RateLimitPolicyCommonSpec { - if r.Defaults != nil { - return r.Defaults +// ToSeconds converts the rate to to Limitador's Limit format (maxValue, seconds) +func (r Rate) ToSeconds() (maxValue, seconds int) { + maxValue = r.Limit + seconds = 0 + + if tmpSecs, ok := timeUnitMap[r.Unit]; ok && r.Duration > 0 { + seconds = tmpSecs * r.Duration + } + + if r.Duration < 0 { + seconds = 0 } - if r.Overrides != nil { - return r.Overrides + if r.Limit < 0 { + maxValue = 0 } - return &r.RateLimitPolicyCommonSpec + return } -type rateLimitPolicyType struct{} +// WhenCondition defines semantics for matching an HTTP request based on conditions +// https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteSpec +type WhenCondition struct { + // Selector defines one item from the well known selectors + // TODO Document properly "Well-known selector" https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors + Selector ContextSelector `json:"selector"` -func NewRateLimitPolicyType() kuadrantgatewayapi.PolicyType { - return &rateLimitPolicyType{} -} + // The binary operator to be applied to the content fetched from the selector + // Possible values are: "eq" (equal to), "neq" (not equal to) + Operator WhenConditionOperator `json:"operator"` -func (r rateLimitPolicyType) GetGVK() schema.GroupVersionKind { - return RateLimitPolicyGVK -} -func (r rateLimitPolicyType) GetInstance() client.Object { - return &RateLimitPolicy{ - TypeMeta: metav1.TypeMeta{ - Kind: RateLimitPolicyGVK.Kind, - APIVersion: GroupVersion.String(), - }, - } + // The value of reference for the comparison. + Value string `json:"value"` } -func (r rateLimitPolicyType) GetList(ctx context.Context, cl client.Client, listOpts ...client.ListOption) ([]kuadrantgatewayapi.Policy, error) { - rlpList := &RateLimitPolicyList{} - err := cl.List(ctx, rlpList, listOpts...) - if err != nil { - return nil, err - } - return utils.Map(rlpList.Items, func(p RateLimitPolicy) kuadrantgatewayapi.Policy { return &p }), nil +// ContextSelector defines one item from the well known attributes +// Attributes: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes +// Well-known selectors: https://github.com/Kuadrant/architecture/blob/main/rfcs/0001-rlp-v2.md#well-known-selectors +// They are named by a dot-separated path (e.g. request.path) +// Example: "request.path" -> The path portion of the URL +// +kubebuilder:validation:MinLength=1 +// +kubebuilder:validation:MaxLength=253 +type ContextSelector string + +// +kubebuilder:validation:Enum:=eq;neq;startswith;endswith;incl;excl;matches +type WhenConditionOperator string + +type RateLimitPolicyStatus struct { + // ObservedGeneration reflects the generation of the most recently observed spec. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // Represents the observations of a foo's current state. + // Known .status.conditions.type are: "Available" + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` } -func (r rateLimitPolicyType) BackReferenceAnnotationName() string { - return RateLimitPolicyBackReferenceAnnotationName +func (s *RateLimitPolicyStatus) GetConditions() []metav1.Condition { + return s.Conditions } -func (r rateLimitPolicyType) DirectReferenceAnnotationName() string { - return RateLimitPolicyDirectReferenceAnnotationName +//+kubebuilder:object:root=true + +// RateLimitPolicyList contains a list of RateLimitPolicy +type RateLimitPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RateLimitPolicy `json:"items"` } func init() { diff --git a/api/v1beta3/ratelimitpolicy_types_test.go b/api/v1beta3/ratelimitpolicy_types_test.go index 4e5e28781..d487bc1b8 100644 --- a/api/v1beta3/ratelimitpolicy_types_test.go +++ b/api/v1beta3/ratelimitpolicy_types_test.go @@ -1,105 +1,3 @@ //go:build unit package v1beta3 - -import ( - "testing" - - "gotest.tools/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" -) - -func testBuildBasicRLP(name string, kind gatewayapiv1.Kind, mutateFn func(*RateLimitPolicy)) *RateLimitPolicy { - p := &RateLimitPolicy{ - TypeMeta: metav1.TypeMeta{ - Kind: "RateLimitPolicy", - APIVersion: GroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: "testNS", - }, - Spec: RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: kind, - Name: "some-name", - }, - }, - } - - if mutateFn != nil { - mutateFn(p) - } - - return p -} - -func testBuildBasicHTTPRouteRLP(name string, mutateFn func(*RateLimitPolicy)) *RateLimitPolicy { - return testBuildBasicRLP(name, "HTTPRoute", mutateFn) -} - -func TestRateLimitPolicyListGetItems(t *testing.T) { - list := &RateLimitPolicyList{} - if len(list.GetItems()) != 0 { - t.Errorf("Expected empty list of items") - } - policy := RateLimitPolicy{} - list.Items = []RateLimitPolicy{policy} - result := list.GetItems() - if len(result) != 1 { - t.Errorf("Expected 1 item, got %d", len(result)) - } - _, ok := result[0].(kuadrant.Policy) - if !ok { - t.Errorf("Expected item to be a Policy") - } -} - -func TestRateLimitPolicy_GetLimits(t *testing.T) { - const name = "policy" - var ( - defaultLimits = map[string]Limit{ - "default": { - Rates: []Rate{{Limit: 10, Duration: 1, Unit: "seconds"}}, - }, - } - implicitLimits = map[string]Limit{ - "implicit": { - Rates: []Rate{{Limit: 20, Duration: 2, Unit: "minutes"}}, - }, - } - ) - - t.Run("No limits defined", func(subT *testing.T) { - r := testBuildBasicHTTPRouteRLP(name, nil) - assert.DeepEqual(subT, r.Spec.CommonSpec().Limits, map[string]Limit(nil)) - }) - t.Run("Defaults defined", func(subT *testing.T) { - r := testBuildBasicHTTPRouteRLP(name, func(policy *RateLimitPolicy) { - policy.Spec.Defaults = &RateLimitPolicyCommonSpec{ - Limits: defaultLimits, - } - }) - assert.DeepEqual(subT, r.Spec.CommonSpec().Limits, defaultLimits) - }) - t.Run("Implicit rules defined", func(subT *testing.T) { - r := testBuildBasicHTTPRouteRLP(name, func(policy *RateLimitPolicy) { - policy.Spec.Limits = implicitLimits - }) - assert.DeepEqual(subT, r.Spec.CommonSpec().Limits, implicitLimits) - }) - t.Run("Default rules takes precedence over implicit rules if validation is somehow bypassed", func(subT *testing.T) { - r := testBuildBasicHTTPRouteRLP(name, func(policy *RateLimitPolicy) { - policy.Spec.Defaults = &RateLimitPolicyCommonSpec{ - Limits: defaultLimits, - } - policy.Spec.Limits = implicitLimits - }) - assert.DeepEqual(subT, r.Spec.CommonSpec().Limits, defaultLimits) - }) -} diff --git a/api/v1beta3/topology.go b/api/v1beta3/topology.go deleted file mode 100644 index aa6672910..000000000 --- a/api/v1beta3/topology.go +++ /dev/null @@ -1,38 +0,0 @@ -package v1beta3 - -// Contains of this file allow the AuthPolicy and RateLimitPolicy to adhere to the machinery.Policy interface - -import ( - "github.com/kuadrant/policy-machinery/machinery" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -var ( - RateLimitPoliciesResource = GroupVersion.WithResource("ratelimitpolicies") - RateLimitPolicyGroupKind = schema.GroupKind{Group: GroupVersion.Group, Kind: "RateLimitPolicy"} -) - -var _ machinery.Policy = &RateLimitPolicy{} - -func (r *RateLimitPolicy) GetTargetRefs() []machinery.PolicyTargetReference { - return []machinery.PolicyTargetReference{ - machinery.LocalPolicyTargetReference{ - LocalPolicyTargetReference: r.Spec.TargetRef, - PolicyNamespace: r.Namespace, - }, - } -} - -func (r *RateLimitPolicy) GetMergeStrategy() machinery.MergeStrategy { - return func(policy machinery.Policy, _ machinery.Policy) machinery.Policy { - return policy - } -} - -func (r *RateLimitPolicy) Merge(other machinery.Policy) machinery.Policy { - return other -} - -func (r *RateLimitPolicy) GetLocator() string { - return machinery.LocatorFromObject(r) -} diff --git a/api/v1beta3/zz_generated.deepcopy.go b/api/v1beta3/zz_generated.deepcopy.go index 1af900e7b..2b2265ee7 100644 --- a/api/v1beta3/zz_generated.deepcopy.go +++ b/api/v1beta3/zz_generated.deepcopy.go @@ -55,6 +55,22 @@ func (in *Limit) DeepCopy() *Limit { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeableRateLimitPolicySpec) DeepCopyInto(out *MergeableRateLimitPolicySpec) { + *out = *in + in.RateLimitPolicySpecProper.DeepCopyInto(&out.RateLimitPolicySpecProper) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeableRateLimitPolicySpec. +func (in *MergeableRateLimitPolicySpec) DeepCopy() *MergeableRateLimitPolicySpec { + if in == nil { + return nil + } + out := new(MergeableRateLimitPolicySpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Rate) DeepCopyInto(out *Rate) { *out = *in @@ -97,28 +113,6 @@ func (in *RateLimitPolicy) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RateLimitPolicyCommonSpec) DeepCopyInto(out *RateLimitPolicyCommonSpec) { - *out = *in - if in.Limits != nil { - in, out := &in.Limits, &out.Limits - *out = make(map[string]Limit, len(*in)) - for key, val := range *in { - (*out)[key] = *val.DeepCopy() - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicyCommonSpec. -func (in *RateLimitPolicyCommonSpec) DeepCopy() *RateLimitPolicyCommonSpec { - if in == nil { - return nil - } - out := new(RateLimitPolicyCommonSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RateLimitPolicyList) DeepCopyInto(out *RateLimitPolicyList) { *out = *in @@ -154,18 +148,18 @@ func (in *RateLimitPolicyList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RateLimitPolicySpec) DeepCopyInto(out *RateLimitPolicySpec) { *out = *in - out.TargetRef = in.TargetRef + in.TargetRef.DeepCopyInto(&out.TargetRef) if in.Defaults != nil { in, out := &in.Defaults, &out.Defaults - *out = new(RateLimitPolicyCommonSpec) + *out = new(MergeableRateLimitPolicySpec) (*in).DeepCopyInto(*out) } if in.Overrides != nil { in, out := &in.Overrides, &out.Overrides - *out = new(RateLimitPolicyCommonSpec) + *out = new(MergeableRateLimitPolicySpec) (*in).DeepCopyInto(*out) } - in.RateLimitPolicyCommonSpec.DeepCopyInto(&out.RateLimitPolicyCommonSpec) + in.RateLimitPolicySpecProper.DeepCopyInto(&out.RateLimitPolicySpecProper) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicySpec. @@ -178,10 +172,31 @@ func (in *RateLimitPolicySpec) DeepCopy() *RateLimitPolicySpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RateLimitPolicySpecProper) DeepCopyInto(out *RateLimitPolicySpecProper) { + *out = *in + if in.Limits != nil { + in, out := &in.Limits, &out.Limits + *out = make(map[string]Limit, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitPolicySpecProper. +func (in *RateLimitPolicySpecProper) DeepCopy() *RateLimitPolicySpecProper { + if in == nil { + return nil + } + out := new(RateLimitPolicySpecProper) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RateLimitPolicyStatus) DeepCopyInto(out *RateLimitPolicyStatus) { *out = *in - out.StatusMeta = in.StatusMeta if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]v1.Condition, len(*in)) diff --git a/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml b/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml index 25b2b124d..8277c4265 100644 --- a/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml +++ b/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml @@ -106,7 +106,7 @@ metadata: capabilities: Basic Install categories: Integration & Delivery containerImage: quay.io/kuadrant/kuadrant-operator:latest - createdAt: "2024-10-04T10:37:57Z" + createdAt: "2024-10-07T11:57:26Z" description: A Kubernetes Operator to manage the lifecycle of the Kuadrant system operators.operatorframework.io/builder: operator-sdk-v1.32.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 diff --git a/bundle/manifests/kuadrant.io_ratelimitpolicies.yaml b/bundle/manifests/kuadrant.io_ratelimitpolicies.yaml index 043dec0fa..01c303782 100644 --- a/bundle/manifests/kuadrant.io_ratelimitpolicies.yaml +++ b/bundle/manifests/kuadrant.io_ratelimitpolicies.yaml @@ -28,14 +28,19 @@ spec: name: Enforced priority: 2 type: string - - description: Type of the referenced Gateway API resource + - description: Kind of the object to which the policy aaplies jsonPath: .spec.targetRef.kind - name: TargetRefKind + name: TargetKind priority: 2 type: string - - description: Name of the referenced Gateway API resource + - description: Name of the object to which the policy applies jsonPath: .spec.targetRef.name - name: TargetRefName + name: TargetName + priority: 2 + type: string + - description: 'Name of the section within the object to which the policy applies ' + jsonPath: .spec.targetRef.sectionName + name: TargetSection priority: 2 type: string - jsonPath: .metadata.creationTimestamp @@ -65,12 +70,11 @@ spec: metadata: type: object spec: - description: RateLimitPolicySpec defines the desired state of RateLimitPolicy properties: defaults: description: |- - Defaults define explicit default values for this policy and for policies inheriting this policy. - Defaults are mutually exclusive with implicit defaults defined by RateLimitPolicyCommonSpec. + Rules to apply as defaults. Can be overridden by more specific policiy rules lower in the hierarchy and by less specific policy overrides. + Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). properties: limits: additionalProperties: @@ -162,8 +166,15 @@ spec: type: object description: Limits holds the struct of limits indexed by a unique name - maxProperties: 14 type: object + strategy: + default: atomic + description: Strategy defines the merge strategy to apply when + merging this policy with other policies. + enum: + - atomic + - merge + type: string type: object limits: additionalProperties: @@ -255,12 +266,11 @@ spec: type: object description: Limits holds the struct of limits indexed by a unique name - maxProperties: 14 type: object overrides: description: |- - Overrides define override values for this policy and for policies inheriting this policy. - Overrides are mutually exclusive with implicit defaults and explicit Defaults defined by RateLimitPolicyCommonSpec. + Rules to apply as overrides. Override all policy rules lower in the hierarchy. Can be overridden by less specific policy overrides. + Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). properties: limits: additionalProperties: @@ -352,11 +362,18 @@ spec: type: object description: Limits holds the struct of limits indexed by a unique name - maxProperties: 14 type: object + strategy: + default: atomic + description: Strategy defines the merge strategy to apply when + merging this policy with other policies. + enum: + - atomic + - merge + type: string type: object targetRef: - description: TargetRef identifies an API object to apply policy to. + description: Reference to the object to which this policy applies. properties: group: description: Group is the group of the target resource. @@ -374,6 +391,25 @@ spec: maxLength: 253 minLength: 1 type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string required: - group - kind @@ -395,11 +431,7 @@ spec: rule: '!(has(self.defaults) && has(self.overrides))' - message: Overrides and implicit defaults are mutually exclusive rule: '!(has(self.overrides) && has(self.limits))' - - message: Overrides are only allowed for policies targeting a Gateway - resource - rule: '!(has(self.overrides) && self.targetRef.kind != ''Gateway'')' status: - description: RateLimitPolicyStatus defines the observed state of RateLimitPolicy properties: conditions: description: |- diff --git a/charts/kuadrant-operator/templates/manifests.yaml b/charts/kuadrant-operator/templates/manifests.yaml index 2dc8b8be0..417ad4a88 100644 --- a/charts/kuadrant-operator/templates/manifests.yaml +++ b/charts/kuadrant-operator/templates/manifests.yaml @@ -13921,14 +13921,19 @@ spec: name: Enforced priority: 2 type: string - - description: Type of the referenced Gateway API resource + - description: Kind of the object to which the policy aaplies jsonPath: .spec.targetRef.kind - name: TargetRefKind + name: TargetKind priority: 2 type: string - - description: Name of the referenced Gateway API resource + - description: Name of the object to which the policy applies jsonPath: .spec.targetRef.name - name: TargetRefName + name: TargetName + priority: 2 + type: string + - description: 'Name of the section within the object to which the policy applies ' + jsonPath: .spec.targetRef.sectionName + name: TargetSection priority: 2 type: string - jsonPath: .metadata.creationTimestamp @@ -13958,12 +13963,11 @@ spec: metadata: type: object spec: - description: RateLimitPolicySpec defines the desired state of RateLimitPolicy properties: defaults: description: |- - Defaults define explicit default values for this policy and for policies inheriting this policy. - Defaults are mutually exclusive with implicit defaults defined by RateLimitPolicyCommonSpec. + Rules to apply as defaults. Can be overridden by more specific policiy rules lower in the hierarchy and by less specific policy overrides. + Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). properties: limits: additionalProperties: @@ -14055,8 +14059,15 @@ spec: type: object description: Limits holds the struct of limits indexed by a unique name - maxProperties: 14 type: object + strategy: + default: atomic + description: Strategy defines the merge strategy to apply when + merging this policy with other policies. + enum: + - atomic + - merge + type: string type: object limits: additionalProperties: @@ -14148,12 +14159,11 @@ spec: type: object description: Limits holds the struct of limits indexed by a unique name - maxProperties: 14 type: object overrides: description: |- - Overrides define override values for this policy and for policies inheriting this policy. - Overrides are mutually exclusive with implicit defaults and explicit Defaults defined by RateLimitPolicyCommonSpec. + Rules to apply as overrides. Override all policy rules lower in the hierarchy. Can be overridden by less specific policy overrides. + Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). properties: limits: additionalProperties: @@ -14245,11 +14255,18 @@ spec: type: object description: Limits holds the struct of limits indexed by a unique name - maxProperties: 14 type: object + strategy: + default: atomic + description: Strategy defines the merge strategy to apply when + merging this policy with other policies. + enum: + - atomic + - merge + type: string type: object targetRef: - description: TargetRef identifies an API object to apply policy to. + description: Reference to the object to which this policy applies. properties: group: description: Group is the group of the target resource. @@ -14267,6 +14284,25 @@ spec: maxLength: 253 minLength: 1 type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string required: - group - kind @@ -14288,11 +14324,7 @@ spec: rule: '!(has(self.defaults) && has(self.overrides))' - message: Overrides and implicit defaults are mutually exclusive rule: '!(has(self.overrides) && has(self.limits))' - - message: Overrides are only allowed for policies targeting a Gateway - resource - rule: '!(has(self.overrides) && self.targetRef.kind != ''Gateway'')' status: - description: RateLimitPolicyStatus defines the observed state of RateLimitPolicy properties: conditions: description: |- diff --git a/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml b/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml index 88b591b84..4b1b4b76b 100644 --- a/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml +++ b/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml @@ -27,14 +27,19 @@ spec: name: Enforced priority: 2 type: string - - description: Type of the referenced Gateway API resource + - description: Kind of the object to which the policy aaplies jsonPath: .spec.targetRef.kind - name: TargetRefKind + name: TargetKind priority: 2 type: string - - description: Name of the referenced Gateway API resource + - description: Name of the object to which the policy applies jsonPath: .spec.targetRef.name - name: TargetRefName + name: TargetName + priority: 2 + type: string + - description: 'Name of the section within the object to which the policy applies ' + jsonPath: .spec.targetRef.sectionName + name: TargetSection priority: 2 type: string - jsonPath: .metadata.creationTimestamp @@ -64,12 +69,11 @@ spec: metadata: type: object spec: - description: RateLimitPolicySpec defines the desired state of RateLimitPolicy properties: defaults: description: |- - Defaults define explicit default values for this policy and for policies inheriting this policy. - Defaults are mutually exclusive with implicit defaults defined by RateLimitPolicyCommonSpec. + Rules to apply as defaults. Can be overridden by more specific policiy rules lower in the hierarchy and by less specific policy overrides. + Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). properties: limits: additionalProperties: @@ -161,8 +165,15 @@ spec: type: object description: Limits holds the struct of limits indexed by a unique name - maxProperties: 14 type: object + strategy: + default: atomic + description: Strategy defines the merge strategy to apply when + merging this policy with other policies. + enum: + - atomic + - merge + type: string type: object limits: additionalProperties: @@ -254,12 +265,11 @@ spec: type: object description: Limits holds the struct of limits indexed by a unique name - maxProperties: 14 type: object overrides: description: |- - Overrides define override values for this policy and for policies inheriting this policy. - Overrides are mutually exclusive with implicit defaults and explicit Defaults defined by RateLimitPolicyCommonSpec. + Rules to apply as overrides. Override all policy rules lower in the hierarchy. Can be overridden by less specific policy overrides. + Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). properties: limits: additionalProperties: @@ -351,11 +361,18 @@ spec: type: object description: Limits holds the struct of limits indexed by a unique name - maxProperties: 14 type: object + strategy: + default: atomic + description: Strategy defines the merge strategy to apply when + merging this policy with other policies. + enum: + - atomic + - merge + type: string type: object targetRef: - description: TargetRef identifies an API object to apply policy to. + description: Reference to the object to which this policy applies. properties: group: description: Group is the group of the target resource. @@ -373,6 +390,25 @@ spec: maxLength: 253 minLength: 1 type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string required: - group - kind @@ -394,11 +430,7 @@ spec: rule: '!(has(self.defaults) && has(self.overrides))' - message: Overrides and implicit defaults are mutually exclusive rule: '!(has(self.overrides) && has(self.limits))' - - message: Overrides are only allowed for policies targeting a Gateway - resource - rule: '!(has(self.overrides) && self.targetRef.kind != ''Gateway'')' status: - description: RateLimitPolicyStatus defines the observed state of RateLimitPolicy properties: conditions: description: |- diff --git a/controllers/effective_ratelimitpolicies_reconciler.go b/controllers/effective_ratelimitpolicies_reconciler.go new file mode 100644 index 000000000..ef69c5ad4 --- /dev/null +++ b/controllers/effective_ratelimitpolicies_reconciler.go @@ -0,0 +1,90 @@ +package controllers + +import ( + "context" + "encoding/json" + "sync" + + "github.com/samber/lo" + "k8s.io/client-go/dynamic" + + "github.com/kuadrant/policy-machinery/controller" + "github.com/kuadrant/policy-machinery/machinery" + + kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1" + kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" +) + +type EffectiveRateLimitPolicy struct { + Path []machinery.Targetable + Spec kuadrantv1beta3.RateLimitPolicy +} + +type EffectiveRateLimitPolicies map[string]EffectiveRateLimitPolicy + +type effectiveRateLimitPolicyReconciler struct { + client *dynamic.DynamicClient +} + +func (r *effectiveRateLimitPolicyReconciler) Subscription() controller.Subscription { + return controller.Subscription{ + ReconcileFunc: r.Reconcile, + Events: rateLimitEventMatchers, + } +} + +func (r *effectiveRateLimitPolicyReconciler) Reconcile(ctx context.Context, _ []controller.ResourceEvent, topology *machinery.Topology, _ error, state *sync.Map) error { + logger := controller.LoggerFromContext(ctx).WithName("effectiveRateLimitPolicyReconciler") + + kuadrant, err := GetKuadrantFromTopology(topology) + if err != nil { + logger.Error(err, "failed to get kuadrant from topology") + return nil + } + + effectivePolicies := r.calculateEffectivePolicies(ctx, topology, kuadrant, state) + + state.Store(StateEffectiveRateLimitPolicies, effectivePolicies) + + return nil +} + +func (r *effectiveRateLimitPolicyReconciler) calculateEffectivePolicies(ctx context.Context, topology *machinery.Topology, kuadrant machinery.Object, state *sync.Map) EffectiveRateLimitPolicies { + logger := controller.LoggerFromContext(ctx).WithName("effectiveRateLimitPolicyReconciler").WithName("calculateEffectivePolicies") + + targetables := topology.Targetables() + gatewayClasses := targetables.Children(kuadrant) // assumes only and all valid gateway classes are linked to kuadrant in the topology + httpRouteRules := targetables.Items(func(o machinery.Object) bool { + _, ok := o.(*machinery.HTTPRouteRule) + return ok + }) + + logger.V(1).Info("calculating effective rate limit policies", "httpRouteRules", len(httpRouteRules)) + + effectivePolicies := EffectiveRateLimitPolicies{} + + for _, gatewayClass := range gatewayClasses { + for _, httpRouteRule := range httpRouteRules { + paths := targetables.Paths(gatewayClass, httpRouteRule) // this may be expensive in clusters with many gateway classes - an alternative is to deep search the topology for httprouterules from each gatewayclass, keeping record of the paths + // TODO: skip for gateways and routes that are not in a valid state (?) + for i := range paths { + if effectivePolicy := kuadrantv1.EffectivePolicyForPath[*kuadrantv1beta3.RateLimitPolicy](paths[i], acceptedRateLimitPolicyFunc(state)); effectivePolicy != nil { + pathID := kuadrantv1.PathID(paths[i]) + effectivePolicies[pathID] = EffectiveRateLimitPolicy{ + Path: paths[i], + Spec: **effectivePolicy, + } + if logger.V(1).Enabled() { + jsonEffectivePolicy, _ := json.Marshal(effectivePolicy) + pathLocators := lo.Map(paths[i], machinery.MapTargetableToLocatorFunc) + logger.V(1).Info("effective policy", "kind", kuadrantv1beta3.RateLimitPolicyGroupKind.Kind, "pathID", pathID, "path", pathLocators, "effectivePolicy", string(jsonEffectivePolicy)) + } + } + } + } + } + + logger.V(1).Info("finished calculating effective rate limit policies", "effectivePolicies", len(effectivePolicies)) + + return effectivePolicies +} diff --git a/controllers/envoygateway_wasm_controller.go b/controllers/envoygateway_wasm_controller.go deleted file mode 100644 index ce2881995..000000000 --- a/controllers/envoygateway_wasm_controller.go +++ /dev/null @@ -1,221 +0,0 @@ -package controllers - -import ( - "context" - "fmt" - - egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" - "github.com/go-logr/logr" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/ptr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/handler" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - kuadrantenvoygateway "github.com/kuadrant/kuadrant-operator/pkg/envoygateway" - "github.com/kuadrant/kuadrant-operator/pkg/kuadranttools" - 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" - "github.com/kuadrant/kuadrant-operator/pkg/rlptools" - "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" -) - -// EnvoyGatewayWasmReconciler reconciles an EnvoyGateway EnvoyExtensionPolicy object for the kuadrant's wasm module -type EnvoyGatewayWasmReconciler struct { - *reconcilers.BaseReconciler -} - -//+kubebuilder:rbac:groups=gateway.envoyproxy.io,resources=envoyextensionpolicies,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways,verbs=get;list;watch;update;patch -//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes,verbs=get;list;watch;update;patch -//+kubebuilder:rbac:groups=kuadrant.io,resources=ratelimitpolicies,verbs=get;list;watch;update;patch - -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile -func (r *EnvoyGatewayWasmReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := r.Logger().WithValues("kuadrant", req.NamespacedName) - logger.V(1).Info("Reconciling envoygateway wasm attachment") - ctx := logr.NewContext(eventCtx, logger) - - kObj := &kuadrantv1beta1.Kuadrant{} - if err := r.Client().Get(ctx, req.NamespacedName, kObj); err != nil { - if apierrors.IsNotFound(err) { - logger.Info("no kuadrant object found") - return ctrl.Result{}, nil - } - logger.Error(err, "failed to get kuadrant object") - return ctrl.Result{}, err - } - - rawTopology, err := kuadranttools.TopologyForPolicies(ctx, r.Client(), kuadrantv1beta3.NewRateLimitPolicyType()) - if err != nil { - return ctrl.Result{}, err - } - - for _, gw := range rawTopology.Gateways() { - topology, err := rlptools.ApplyOverrides(rawTopology, gw.GetGateway()) - if err != nil { - return ctrl.Result{}, err - } - envoyPolicy, err := r.desiredEnvoyExtensionPolicy(ctx, gw, kObj, topology) - if err != nil { - return ctrl.Result{}, err - } - err = r.ReconcileResource(ctx, &egv1alpha1.EnvoyExtensionPolicy{}, envoyPolicy, kuadrantenvoygateway.EnvoyExtensionPolicyMutator) - if err != nil { - return ctrl.Result{}, err - } - } - - logger.V(1).Info("Envoygateway wasm attachment reconciled successfully") - return ctrl.Result{}, nil -} - -func (r *EnvoyGatewayWasmReconciler) desiredEnvoyExtensionPolicy( - ctx context.Context, gw kuadrantgatewayapi.GatewayNode, - kObj *kuadrantv1beta1.Kuadrant, - topology *kuadrantgatewayapi.Topology) (*egv1alpha1.EnvoyExtensionPolicy, error) { - baseLogger, err := logr.FromContext(ctx) - if err != nil { - return nil, err - } - envoyPolicy := &egv1alpha1.EnvoyExtensionPolicy{ - TypeMeta: metav1.TypeMeta{ - Kind: egv1alpha1.KindEnvoyExtensionPolicy, - APIVersion: egv1alpha1.GroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: EnvoyExtensionPolicyName(gw.GetName()), - Namespace: gw.GetNamespace(), - }, - Spec: egv1alpha1.EnvoyExtensionPolicySpec{ - PolicyTargetReferences: egv1alpha1.PolicyTargetReferences{ - TargetRefs: []gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ - { - LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.Group(gatewayapiv1.GroupVersion.Group), - Kind: gatewayapiv1.Kind("Gateway"), - Name: gatewayapiv1.ObjectName(gw.GetName()), - }, - }, - }, - }, - Wasm: []egv1alpha1.Wasm{ - { - Name: ptr.To("kuadrant-wasm-shim"), - RootID: ptr.To("kuadrant_wasm_shim"), - Code: egv1alpha1.WasmCodeSource{ - Type: egv1alpha1.ImageWasmCodeSourceType, - Image: &egv1alpha1.ImageWasmCodeSource{ - URL: WASMFilterImageURL, - }, - }, - Config: nil, - // When a fatal error accurs during the initialization or the execution of the - // Wasm extension, if FailOpen is set to false the system blocks the traffic and returns - // an HTTP 5xx error. - FailOpen: ptr.To(false), - }, - }, - }, - } - - logger := baseLogger.WithValues("envoyextensionpolicy", client.ObjectKeyFromObject(envoyPolicy)) - - config, err := wasm.ConfigForGateway(ctx, gw.GetGateway(), topology) - if err != nil { - return nil, err - } - - if config == nil || len(config.Policies) == 0 { - logger.V(1).Info("config is empty. EnvoyExtensionPolicy will be deleted if it exists") - utils.TagObjectToDelete(envoyPolicy) - return envoyPolicy, nil - } - - configJSON, err := config.ToJSON() - if err != nil { - return nil, err - } - - envoyPolicy.Spec.Wasm[0].Config = configJSON - - kuadrant.AnnotateObject(envoyPolicy, kObj.GetNamespace()) - - // controller reference - if err := r.SetOwnerReference(gw.GetGateway(), envoyPolicy); err != nil { - return nil, err - } - - return envoyPolicy, nil -} - -func EnvoyExtensionPolicyName(targetName string) string { - return fmt.Sprintf("kuadrant-wasm-for-%s", targetName) -} - -// SetupWithManager sets up the controller with the Manager. -func (r *EnvoyGatewayWasmReconciler) SetupWithManager(mgr ctrl.Manager) error { - ok, err := kuadrantenvoygateway.IsEnvoyGatewayInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("EnvoyGateway Wasm controller disabled. EnvoyGateway API was not found") - return nil - } - - ok, err = kuadrantgatewayapi.IsGatewayAPIInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("EnvoyGateway Wasm controller disabled. GatewayAPI was not found") - return nil - } - - kuadrantListEventMapper := mappers.NewKuadrantListEventMapper( - mappers.WithLogger(r.Logger().WithName("envoyExtensionPolicyToKuadrantEventMapper")), - mappers.WithClient(r.Client()), - ) - policyToKuadrantEventMapper := mappers.NewPolicyToKuadrantEventMapper( - mappers.WithLogger(r.Logger().WithName("policyToKuadrantEventMapper")), - mappers.WithClient(r.Client()), - ) - gatewayToKuadrantEventMapper := mappers.NewGatewayToKuadrantEventMapper( - mappers.WithLogger(r.Logger().WithName("gatewayToKuadrantEventMapper")), - mappers.WithClient(r.Client()), - ) - httpRouteToKuadrantEventMapper := mappers.NewHTTPRouteToKuadrantEventMapper( - mappers.WithLogger(r.Logger().WithName("httpRouteToKuadrantEventMapper")), - mappers.WithClient(r.Client()), - ) - - return ctrl.NewControllerManagedBy(mgr). - For(&kuadrantv1beta1.Kuadrant{}). - Watches( - &egv1alpha1.EnvoyExtensionPolicy{}, - handler.EnqueueRequestsFromMapFunc(kuadrantListEventMapper.Map), - ). - Watches( - &kuadrantv1beta3.RateLimitPolicy{}, - handler.EnqueueRequestsFromMapFunc(policyToKuadrantEventMapper.Map), - ). - Watches( - &gatewayapiv1.Gateway{}, - handler.EnqueueRequestsFromMapFunc(gatewayToKuadrantEventMapper.Map), - ). - Watches( - &gatewayapiv1.HTTPRoute{}, - handler.EnqueueRequestsFromMapFunc(httpRouteToKuadrantEventMapper.Map), - ). - Complete(r) -} diff --git a/controllers/limitador_cluster_envoyfilter_controller.go b/controllers/limitador_cluster_envoyfilter_controller.go deleted file mode 100644 index 689221dcf..000000000 --- a/controllers/limitador_cluster_envoyfilter_controller.go +++ /dev/null @@ -1,194 +0,0 @@ -/* -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. -*/ - -package controllers - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/go-logr/logr" - limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" - istioapinetworkingv1alpha3 "istio.io/api/networking/v1alpha3" - istioclientnetworkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - 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/predicate" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - "github.com/kuadrant/kuadrant-operator/pkg/common" - kuadrantistioutils "github.com/kuadrant/kuadrant-operator/pkg/istio" - kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" - "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" - "github.com/kuadrant/kuadrant-operator/pkg/library/utils" -) - -// LimitadorClusterEnvoyFilterReconciler reconciles a EnvoyFilter object with limitador's cluster -type LimitadorClusterEnvoyFilterReconciler struct { - *reconcilers.BaseReconciler -} - -//+kubebuilder:rbac:groups=networking.istio.io,resources=envoyfilters,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways,verbs=get -//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways/finalizers,verbs=update - -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile -func (r *LimitadorClusterEnvoyFilterReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := r.Logger().WithValues("Gateway", req.NamespacedName) - logger.Info("Reconciling EnvoyFilter") - 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 logger.V(1).Enabled() { - jsonData, err := json.MarshalIndent(gw, "", " ") - if err != nil { - return ctrl.Result{}, err - } - logger.V(1).Info(string(jsonData)) - } - - err := r.reconcileRateLimitingClusterEnvoyFilter(ctx, gw) - - if err != nil { - return ctrl.Result{}, err - } - - logger.Info("EnvoyFilter reconciled successfully") - return ctrl.Result{}, nil -} - -func (r *LimitadorClusterEnvoyFilterReconciler) reconcileRateLimitingClusterEnvoyFilter(ctx context.Context, gw *gatewayapiv1.Gateway) error { - desired, err := r.desiredRateLimitingClusterEnvoyFilter(ctx, gw) - if err != nil { - return err - } - - err = r.ReconcileResource(ctx, &istioclientnetworkingv1alpha3.EnvoyFilter{}, desired, kuadrantistioutils.AlwaysUpdateEnvoyFilter) - if err != nil { - return err - } - - return nil -} - -func (r *LimitadorClusterEnvoyFilterReconciler) desiredRateLimitingClusterEnvoyFilter(ctx context.Context, gw *gatewayapiv1.Gateway) (*istioclientnetworkingv1alpha3.EnvoyFilter, error) { - logger, err := logr.FromContext(ctx) - if err != nil { - return nil, err - } - - ef := &istioclientnetworkingv1alpha3.EnvoyFilter{ - TypeMeta: metav1.TypeMeta{ - Kind: "EnvoyFilter", - APIVersion: "networking.istio.io/v1alpha3", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("kuadrant-ratelimiting-cluster-%s", gw.Name), - Namespace: gw.Namespace, - }, - Spec: istioapinetworkingv1alpha3.EnvoyFilter{ - WorkloadSelector: &istioapinetworkingv1alpha3.WorkloadSelector{ - Labels: kuadrantistioutils.WorkloadSelectorFromGateway(ctx, r.Client(), gw).MatchLabels, - }, - ConfigPatches: nil, - }, - } - - gateway := kuadrant.GatewayWrapper{Gateway: gw, Referrer: &kuadrantv1beta3.RateLimitPolicy{}} - rlpRefs := gateway.PolicyRefs() - logger.V(1).Info("desiredRateLimitingClusterEnvoyFilter", "rlpRefs", rlpRefs) - - if len(rlpRefs) < 1 { - utils.TagObjectToDelete(ef) - return ef, nil - } - - kuadrantNamespace, err := kuadrant.GetKuadrantNamespace(gw) - if err != nil { - return nil, err - } - - limitadorKey := client.ObjectKey{Name: common.LimitadorName, Namespace: kuadrantNamespace} - limitador := &limitadorv1alpha1.Limitador{} - err = r.Client().Get(ctx, limitadorKey, limitador) - logger.V(1).Info("desiredRateLimitingClusterEnvoyFilter", "get limitador", limitadorKey, "err", err) - if err != nil { - return nil, err - } - - if !meta.IsStatusConditionTrue(limitador.Status.Conditions, "Ready") { - return nil, fmt.Errorf("limitador Status not ready") - } - - configPatches, err := kuadrantistioutils.LimitadorClusterPatch(limitador.Status.Service.Host, int(limitador.Status.Service.Ports.GRPC)) - if err != nil { - return nil, err - } - ef.Spec.ConfigPatches = configPatches - - // controller reference - if err := r.SetOwnerReference(gw, ef); err != nil { - return nil, err - } - - return ef, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *LimitadorClusterEnvoyFilterReconciler) SetupWithManager(mgr ctrl.Manager) error { - ok, err := kuadrantistioutils.IsEnvoyFilterInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("Istio EnvoyFilter controller disabled. Istio was not found") - return nil - } - - ok, err = kuadrantgatewayapi.IsGatewayAPIInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("Istio EnvoyFilter controller disabled. GatewayAPI was not found") - return nil - } - - return ctrl.NewControllerManagedBy(mgr). - // Limitador cluster EnvoyFilter controller only cares about - // the annotation having references to RLP's - // kuadrant.io/ratelimitpolicies - For(&gatewayapiv1.Gateway{}, builder.WithPredicates(predicate.AnnotationChangedPredicate{})). - Owns(&istioclientnetworkingv1alpha3.EnvoyFilter{}). - Complete(r) -} diff --git a/controllers/limitador_limits_reconciler.go b/controllers/limitador_limits_reconciler.go new file mode 100644 index 000000000..9f015fa20 --- /dev/null +++ b/controllers/limitador_limits_reconciler.go @@ -0,0 +1,145 @@ +package controllers + +import ( + "context" + "fmt" + "sync" + + limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" + "github.com/samber/lo" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8stypes "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + + "github.com/kuadrant/policy-machinery/controller" + "github.com/kuadrant/policy-machinery/machinery" + + kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1" + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" + "github.com/kuadrant/kuadrant-operator/pkg/library/utils" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" +) + +type limitadorLimitsReconciler struct { + client *dynamic.DynamicClient +} + +func (r *limitadorLimitsReconciler) Subscription() controller.Subscription { + return controller.Subscription{ + ReconcileFunc: r.Reconcile, + Events: rateLimitEventMatchers, + } +} + +func (r *limitadorLimitsReconciler) Reconcile(ctx context.Context, _ []controller.ResourceEvent, topology *machinery.Topology, _ error, state *sync.Map) error { + logger := controller.LoggerFromContext(ctx).WithName("limitadorLimitsReconciler") + + kuadrant, err := GetKuadrantFromTopology(topology) + if err != nil { + logger.Error(err, "failed to get kuadrant from topology") + return nil + } + + limitadorObj, found := lo.Find(topology.Objects().Children(kuadrant), func(child machinery.Object) bool { + return child.GroupVersionKind().GroupKind() == kuadrantv1beta1.LimitadorGroupKind + }) + if !found { + logger.Error(ErrMissingLimitador, "failed to get limitador from topology") + return nil + } + + limitador := limitadorObj.(*controller.RuntimeObject).Object.(*limitadorv1alpha1.Limitador) + + desiredLimits, err := r.buildLimitadorLimits(ctx, state) + if err != nil { + logger.Error(err, "failed to build limitador limits") + return nil + } + + if len(limitador.Spec.Limits) == len(desiredLimits) && lo.EveryBy(limitador.Spec.Limits, func(existingLimit limitadorv1alpha1.RateLimit) bool { + return lo.ContainsBy(desiredLimits, func(desiredLimit limitadorv1alpha1.RateLimit) bool { + return existingLimit.Namespace == desiredLimit.Namespace && + existingLimit.Name == desiredLimit.Name && + existingLimit.MaxValue == desiredLimit.MaxValue && + existingLimit.Seconds == desiredLimit.Seconds && + len(existingLimit.Conditions) == len(desiredLimit.Conditions) && lo.Every(existingLimit.Conditions, desiredLimit.Conditions) && + len(existingLimit.Variables) == len(desiredLimit.Variables) && lo.Every(existingLimit.Variables, desiredLimit.Variables) + }) + }) { + logger.Info("limitador object is up to date, nothing to do") + return nil + } + + limitador.Spec.Limits = desiredLimits + + obj, err := controller.Destruct(limitador) + if err != nil { + return err // should never happen + } + + logger.V(1).Info("updating limitador object", "limitador", obj.Object) + + if _, err := r.client.Resource(kuadrantv1beta1.LimitadorsResource).Namespace(limitador.GetNamespace()).Update(ctx, obj, metav1.UpdateOptions{}); err != nil { + logger.Error(err, "failed to update limitador object") + // TODO: handle error + } + + logger.V(1).Info("finished updating limitador object", "limitador", (k8stypes.NamespacedName{Name: limitador.GetName(), Namespace: limitador.GetNamespace()}).String()) + + return nil +} + +func (r *limitadorLimitsReconciler) buildLimitadorLimits(ctx context.Context, state *sync.Map) ([]limitadorv1alpha1.RateLimit, error) { + logger := controller.LoggerFromContext(ctx).WithName("limitadorLimitsReconciler").WithName("buildLimitadorLimits") + + effectivePolicies, ok := state.Load(StateEffectiveRateLimitPolicies) + if !ok { + return nil, ErrMissingStateEffectiveRateLimitPolicies + } + + logger.V(1).Info("building limitador limits", "effectivePolicies", len(effectivePolicies.(EffectiveRateLimitPolicies))) + + rateLimitIndex := rlptools.NewRateLimitIndex() + + for pathID, effectivePolicy := range effectivePolicies.(EffectiveRateLimitPolicies) { + gateway := effectivePolicy.Path[1].(*machinery.Gateway) // assumes the path is always [gatewayclass, gateway, listener, httproute, httprouterule] + gatewayKey := k8stypes.NamespacedName{Name: gateway.GetName(), Namespace: gateway.GetNamespace()} + logger.V(1).Info("building limitador limits", "pathID", pathID, "gateway", gatewayKey.String(), "limits", len(effectivePolicy.Spec.Rules())) + for limitKey, mergeableLimit := range effectivePolicy.Spec.Rules() { + policies := kuadrantv1.PoliciesInPath(effectivePolicy.Path, acceptedRateLimitPolicyFunc(state)) + origin, found := lo.Find(policies, func(policy machinery.Policy) bool { + return policy.GetLocator() == mergeableLimit.Source + }) + if !found { // should never happen + logger.Error(fmt.Errorf("origin policy %s not found in path %s", mergeableLimit.Source, pathID), "failed to build limitador limit definition") + continue + } + originKey := k8stypes.NamespacedName{Name: origin.GetName(), Namespace: origin.GetNamespace()} + key := rlptools.RateLimitIndexKey{ + RateLimitPolicyKey: originKey, + GatewayKey: gatewayKey, + } + limitsNamespace := wasm.LimitsNamespaceFromRLP(origin.(*kuadrantv1beta3.RateLimitPolicy)) + limitIdentifier := wasm.LimitNameToLimitadorIdentifier(originKey, limitKey) + rateLimits := make([]limitadorv1alpha1.RateLimit, 0) + limit := mergeableLimit.Spec.(kuadrantv1beta3.Limit) + for _, rate := range limit.Rates { + maxValue, seconds := rate.ToSeconds() + rateLimits = append(rateLimits, limitadorv1alpha1.RateLimit{ + Namespace: limitsNamespace, + MaxValue: maxValue, + Seconds: seconds, + Conditions: []string{fmt.Sprintf("%s == \"1\"", limitIdentifier)}, + Variables: utils.GetEmptySliceIfNil(limit.CountersAsStringList()), + }) + } + rateLimitIndex.Set(key, rateLimits) + } + } + + logger.V(1).Info("finished building limitador limits", "limits", rateLimitIndex.Len()) + + return rateLimitIndex.ToRateLimits(), nil +} diff --git a/controllers/rate_limiting_istio_wasmplugin_controller.go b/controllers/rate_limiting_istio_wasmplugin_controller.go deleted file mode 100644 index f0b84e54f..000000000 --- a/controllers/rate_limiting_istio_wasmplugin_controller.go +++ /dev/null @@ -1,223 +0,0 @@ -/* -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. -*/ - -package controllers - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/go-logr/logr" - "github.com/google/uuid" - istioextensionsv1alpha1 "istio.io/api/extensions/v1alpha1" - istioclientgoextensionv1alpha1 "istio.io/client-go/pkg/apis/extensions/v1alpha1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/env" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/handler" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - kuadrantistioutils "github.com/kuadrant/kuadrant-operator/pkg/istio" - "github.com/kuadrant/kuadrant-operator/pkg/kuadranttools" - kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" - "github.com/kuadrant/kuadrant-operator/pkg/library/mappers" - "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" - "github.com/kuadrant/kuadrant-operator/pkg/library/utils" - "github.com/kuadrant/kuadrant-operator/pkg/rlptools" - "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" -) - -var ( - WASMFilterImageURL = env.GetString("RELATED_IMAGE_WASMSHIM", "oci://quay.io/kuadrant/wasm-shim:latest") -) - -func WASMPluginName(gw *gatewayapiv1.Gateway) string { - return fmt.Sprintf("kuadrant-%s", gw.Name) -} - -// RateLimitingIstioWASMPluginReconciler reconciles a WASMPlugin object for rate limiting -type RateLimitingIstioWASMPluginReconciler struct { - *reconcilers.BaseReconciler -} - -//+kubebuilder:rbac:groups=extensions.istio.io,resources=wasmplugins,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways,verbs=get;list;watch;update;patch -//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes,verbs=get;list;watch;update;patch -//+kubebuilder:rbac:groups=kuadrant.io,resources=ratelimitpolicies,verbs=get;list;watch;update;patch - -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile -func (r *RateLimitingIstioWASMPluginReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := r.Logger().WithValues("Gateway", req.NamespacedName, "request id", uuid.NewString()) - logger.Info("Reconciling rate limiting WASMPlugin") - 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 logger.V(1).Enabled() { - jsonData, err := json.MarshalIndent(gw, "", " ") - if err != nil { - return ctrl.Result{}, err - } - logger.V(1).Info(string(jsonData)) - } - - desired, err := r.desiredRateLimitingWASMPlugin(ctx, gw) - if err != nil { - return ctrl.Result{}, err - } - - err = r.ReconcileResource(ctx, &istioclientgoextensionv1alpha1.WasmPlugin{}, desired, kuadrantistioutils.WASMPluginMutator) - if err != nil { - return ctrl.Result{}, err - } - - logger.Info("Rate limiting WASMPlugin reconciled successfully") - return ctrl.Result{}, nil -} - -func (r *RateLimitingIstioWASMPluginReconciler) desiredRateLimitingWASMPlugin(ctx context.Context, gw *gatewayapiv1.Gateway) (*istioclientgoextensionv1alpha1.WasmPlugin, error) { - baseLogger, err := logr.FromContext(ctx) - if err != nil { - return nil, err - } - - wasmPlugin := &istioclientgoextensionv1alpha1.WasmPlugin{ - TypeMeta: metav1.TypeMeta{ - Kind: "WasmPlugin", - APIVersion: "extensions.istio.io/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: WASMPluginName(gw), - Namespace: gw.Namespace, - }, - Spec: istioextensionsv1alpha1.WasmPlugin{ - TargetRef: kuadrantistioutils.PolicyTargetRefFromGateway(gw), - Url: WASMFilterImageURL, - PluginConfig: nil, - // Insert plugin before Istio stats filters and after Istio authorization filters. - Phase: istioextensionsv1alpha1.PluginPhase_STATS, - }, - } - - logger := baseLogger.WithValues("wasmplugin", client.ObjectKeyFromObject(wasmPlugin)) - - pluginConfig, err := r.wasmPluginConfig(ctx, gw) - if err != nil { - return nil, err - } - - if pluginConfig == nil || len(pluginConfig.Policies) == 0 { - logger.V(1).Info("pluginConfig is empty. Wasmplugin will be deleted if it exists") - utils.TagObjectToDelete(wasmPlugin) - return wasmPlugin, nil - } - - pluginConfigStruct, err := pluginConfig.ToStruct() - if err != nil { - return nil, err - } - - wasmPlugin.Spec.PluginConfig = pluginConfigStruct - - // controller reference - if err := r.SetOwnerReference(gw, wasmPlugin); err != nil { - return nil, err - } - - return wasmPlugin, nil -} - -func (r *RateLimitingIstioWASMPluginReconciler) wasmPluginConfig(ctx context.Context, gw *gatewayapiv1.Gateway) (*wasm.Config, error) { - rawTopology, err := kuadranttools.TopologyFromGateway(ctx, r.Client(), gw, kuadrantv1beta3.NewRateLimitPolicyType()) - if err != nil { - return nil, err - } - - topology, err := rlptools.ApplyOverrides(rawTopology, gw) - if err != nil { - return nil, err - } - - config, err := wasm.ConfigForGateway(ctx, gw, topology) - if err != nil { - return nil, err - } - - return config, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *RateLimitingIstioWASMPluginReconciler) SetupWithManager(mgr ctrl.Manager) error { - ok, err := kuadrantistioutils.IsWASMPluginInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("Istio WasmPlugin controller disabled. Istio was not found") - return nil - } - - ok, err = kuadrantgatewayapi.IsGatewayAPIInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("Istio WasmPlugin controller disabled. GatewayAPI was not found") - return nil - } - - httpRouteToParentGatewaysEventMapper := mappers.NewHTTPRouteToParentGatewaysEventMapper( - mappers.WithLogger(r.Logger().WithName("httpRouteToParentGatewaysEventMapper")), - ) - - rlpToParentGatewaysEventMapper := mappers.NewPolicyToParentGatewaysEventMapper( - mappers.WithLogger(r.Logger().WithName("ratelimitpolicyToParentGatewaysEventMapper")), - mappers.WithClient(r.Client()), - ) - - return ctrl.NewControllerManagedBy(mgr). - // Rate limiting WASMPlugin controller only cares about - // Gateway API Gateway - // Gateway API HTTPRoutes - // Kuadrant RateLimitPolicies - - // The type of object being *reconciled* is the Gateway. - // TODO(eguzki): consider having the WasmPlugin as the type of object being *reconciled* - For(&gatewayapiv1.Gateway{}). - Owns(&istioclientgoextensionv1alpha1.WasmPlugin{}). - Watches( - &gatewayapiv1.HTTPRoute{}, - handler.EnqueueRequestsFromMapFunc(httpRouteToParentGatewaysEventMapper.Map), - ). - Watches( - &kuadrantv1beta3.RateLimitPolicy{}, - handler.EnqueueRequestsFromMapFunc(rlpToParentGatewaysEventMapper.Map), - ). - Complete(r) -} diff --git a/controllers/ratelimit_workflow.go b/controllers/ratelimit_workflow.go index eeb4be09b..23abf495a 100644 --- a/controllers/ratelimit_workflow.go +++ b/controllers/ratelimit_workflow.go @@ -1,7 +1,90 @@ package controllers -import "github.com/kuadrant/policy-machinery/controller" +import ( + "fmt" + "sync" -func NewRateLimitWorkflow() *controller.Workflow { - return &controller.Workflow{} + "k8s.io/client-go/dynamic" + "k8s.io/utils/env" + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + + "github.com/kuadrant/policy-machinery/controller" + "github.com/kuadrant/policy-machinery/machinery" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" + kuadrantenvoygateway "github.com/kuadrant/kuadrant-operator/pkg/envoygateway" + kuadrantistio "github.com/kuadrant/kuadrant-operator/pkg/istio" + kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" +) + +var ( + StateRateLimitPolicyValid = "RateLimitPolicyValid" + StateEffectiveRateLimitPolicies = "EffectiveRateLimitPolicies" + + ErrMissingTarget = fmt.Errorf("target not found") + ErrMissingLimitador = fmt.Errorf("missing limitador object in the topology") + ErrMissingStateEffectiveRateLimitPolicies = fmt.Errorf("missing rate limit effective policies stored in the reconciliation state") + + rateLimitEventMatchers = []controller.ResourceEventMatcher{ // matches reconciliation events that change the rate limit definitions or status of rate limit policies + {Kind: &kuadrantv1beta1.KuadrantGroupKind}, + {Kind: &machinery.GatewayClassGroupKind}, + {Kind: &machinery.GatewayGroupKind}, + {Kind: &machinery.HTTPRouteGroupKind}, + {Kind: &kuadrantv1beta3.RateLimitPolicyGroupKind}, + {Kind: &kuadrantv1beta1.LimitadorGroupKind}, + {Kind: &kuadrantistio.EnvoyFilterGroupKind}, + {Kind: &kuadrantistio.WasmPluginGroupKind}, + {Kind: &kuadrantenvoygateway.EnvoyPatchPolicyGroupKind}, + {Kind: &kuadrantenvoygateway.EnvoyExtensionPolicyGroupKind}, + } +) + +//+kubebuilder:rbac:groups=kuadrant.io,resources=ratelimitpolicies,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=kuadrant.io,resources=ratelimitpolicies/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=kuadrant.io,resources=ratelimitpolicies/finalizers,verbs=update +//+kubebuilder:rbac:groups=limitador.kuadrant.io,resources=limitadors,verbs=get;list;watch;create;update;patch;delete + +func NewRateLimitWorkflow(client *dynamic.DynamicClient) *controller.Workflow { + return &controller.Workflow{ + Precondition: (&rateLimitPolicyValidator{}).Subscription().Reconcile, + Tasks: []controller.ReconcileFunc{(&controller.Workflow{ + Precondition: (&effectiveRateLimitPolicyReconciler{client: client}).Subscription().Reconcile, + Tasks: []controller.ReconcileFunc{ + (&limitadorLimitsReconciler{client: client}).Subscription().Reconcile, + // TODO: reconcile istio cluster (EnvoyFilter) + // TODO: reconcile istio extension (WasmPlugin) + // TODO: reconcile envoy cluster (EnvoyPatchPolicy) + // TODO: reconcile envoy extension (EnvoyExtensionPolicy) + }, + }).Run}, + Postcondition: (&rateLimitPolicyStatusUpdater{client: client}).Subscription().Reconcile, + } +} + +func isRateLimitPolicyAcepted(policy machinery.Policy) bool { + p, ok := policy.(*kuadrantv1beta3.RateLimitPolicy) + return ok && kuadrantgatewayapi.IsPolicyAccepted(p) && p.GetDeletionTimestamp() == nil +} + +func acceptedRateLimitPolicyFunc(state *sync.Map) func(machinery.Policy) bool { + validatedPolicies, validated := state.Load(StateRateLimitPolicyValid) + if !validated { + return isRateLimitPolicyAcepted + } + return func(policy machinery.Policy) bool { + err, validated := validatedPolicies.(map[string]error)[policy.GetLocator()] + return (validated && err == nil) || isRateLimitPolicyAcepted(policy) + } +} + +// Used in the tests + +var WASMFilterImageURL = env.GetString("RELATED_IMAGE_WASMSHIM", "oci://quay.io/kuadrant/wasm-shim:latest") + +func WASMPluginName(gw *gatewayapiv1.Gateway) string { + return fmt.Sprintf("kuadrant-%s", gw.Name) +} +func EnvoyExtensionPolicyName(targetName string) string { + return fmt.Sprintf("kuadrant-wasm-for-%s", targetName) } diff --git a/controllers/ratelimitpolicies_validator.go b/controllers/ratelimitpolicies_validator.go new file mode 100644 index 000000000..55fd0ad7f --- /dev/null +++ b/controllers/ratelimitpolicies_validator.go @@ -0,0 +1,48 @@ +package controllers + +import ( + "context" + "sync" + + "github.com/samber/lo" + + "github.com/kuadrant/policy-machinery/controller" + "github.com/kuadrant/policy-machinery/machinery" + + kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" +) + +type rateLimitPolicyValidator struct{} + +func (r *rateLimitPolicyValidator) Subscription() controller.Subscription { + return controller.Subscription{ + ReconcileFunc: r.Validate, + Events: []controller.ResourceEventMatcher{ + {Kind: &machinery.GatewayGroupKind}, + {Kind: &machinery.HTTPRouteGroupKind}, + {Kind: &kuadrantv1beta3.RateLimitPolicyGroupKind}, + }, + } +} + +func (r *rateLimitPolicyValidator) Validate(ctx context.Context, _ []controller.ResourceEvent, topology *machinery.Topology, _ error, state *sync.Map) error { + logger := controller.LoggerFromContext(ctx).WithName("rateLimitPolicyValidator") + + policies := topology.Policies().Items(func(o machinery.Object) bool { + return o.GroupVersionKind().GroupKind() == kuadrantv1beta3.RateLimitPolicyGroupKind + }) + + logger.V(1).Info("validating rate limit policies", "policies", len(policies)) + + state.Store(StateRateLimitPolicyValid, lo.SliceToMap(policies, func(policy machinery.Policy) (string, error) { + var err error + if len(policy.GetTargetRefs()) > 0 && len(topology.Targetables().Children(policy)) == 0 { + err = ErrMissingTarget + } + return policy.GetLocator(), err + })) + + logger.V(1).Info("finished validating rate limit policies") + + return nil +} diff --git a/controllers/ratelimitpolicy_controller.go b/controllers/ratelimitpolicy_controller.go deleted file mode 100644 index 0a390eee2..000000000 --- a/controllers/ratelimitpolicy_controller.go +++ /dev/null @@ -1,253 +0,0 @@ -/* -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. -*/ - -package controllers - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/go-logr/logr" - "github.com/google/uuid" - apierrors "k8s.io/apimachinery/pkg/api/errors" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - 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" -) - -const rateLimitPolicyFinalizer = "ratelimitpolicy.kuadrant.io/finalizer" - -// RateLimitPolicyReconciler reconciles a RateLimitPolicy object -type RateLimitPolicyReconciler struct { - *reconcilers.BaseReconciler - TargetRefReconciler reconcilers.TargetRefReconciler -} - -//+kubebuilder:rbac:groups=kuadrant.io,resources=ratelimitpolicies,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=kuadrant.io,resources=ratelimitpolicies/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=kuadrant.io,resources=ratelimitpolicies/finalizers,verbs=update -//+kubebuilder:rbac:groups=limitador.kuadrant.io,resources=limitadors,verbs=get;list;watch;create;update;patch;delete - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the RateLimitPolicy object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile -func (r *RateLimitPolicyReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := r.Logger().WithValues("RateLimitPolicy", req.NamespacedName, "request id", uuid.NewString()) - logger.Info("Reconciling RateLimitPolicy") - ctx := logr.NewContext(eventCtx, logger) - - // fetch the ratelimitpolicy - rlp := &kuadrantv1beta3.RateLimitPolicy{} - if err := r.Client().Get(ctx, req.NamespacedName, rlp); err != nil { - if apierrors.IsNotFound(err) { - logger.Info("no RateLimitPolicy found") - return ctrl.Result{}, nil - } - logger.Error(err, "failed to get RateLimitPolicy") - return ctrl.Result{}, err - } - - if logger.V(1).Enabled() { - jsonData, err := json.MarshalIndent(rlp, "", " ") - if err != nil { - return ctrl.Result{}, err - } - logger.V(1).Info(string(jsonData)) - } - - markedForDeletion := rlp.GetDeletionTimestamp() != nil - - // fetch the target network object - targetNetworkObject, err := reconcilers.FetchTargetRefObject(ctx, r.Client(), rlp.GetTargetRef(), rlp.Namespace, rlp.TargetProgrammedGatewaysOnly()) - if err != nil { - if !markedForDeletion { - if apierrors.IsNotFound(err) { - logger.V(1).Info("Network object not found. Cleaning up") - delResErr := r.deleteResources(ctx, rlp, nil) - if delResErr == nil { - delResErr = err - } - return r.reconcileStatus(ctx, rlp, kuadrant.NewErrTargetNotFound(rlp.Kind(), rlp.GetTargetRef(), delResErr)) - } - return ctrl.Result{}, err - } - targetNetworkObject = nil // we need the object set to nil when there's an error, otherwise deleting the resources (when marked for deletion) will panic - } - - // handle ratelimitpolicy marked for deletion - if markedForDeletion { - if controllerutil.ContainsFinalizer(rlp, rateLimitPolicyFinalizer) { - logger.V(1).Info("Handling removal of ratelimitpolicy object") - - if err := r.deleteResources(ctx, rlp, targetNetworkObject); err != nil { - return ctrl.Result{}, err - } - - logger.Info("removing finalizer") - if err := r.RemoveFinalizer(ctx, rlp, rateLimitPolicyFinalizer); err != nil { - return ctrl.Result{}, err - } - } - - return ctrl.Result{}, nil - } - - // add finalizer to the ratelimitpolicy - if !controllerutil.ContainsFinalizer(rlp, rateLimitPolicyFinalizer) { - controllerutil.AddFinalizer(rlp, rateLimitPolicyFinalizer) - if err := r.UpdateResource(ctx, rlp); client.IgnoreNotFound(err) != nil { - return ctrl.Result{Requeue: true}, err - } - } - - // reconcile the ratelimitpolicy spec - specErr := r.reconcileResources(ctx, rlp, targetNetworkObject) - - // reconcile ratelimitpolicy status - statusResult, statusErr := r.reconcileStatus(ctx, rlp, specErr) - - if specErr != nil { - return ctrl.Result{}, specErr - } - - if statusErr != nil { - return ctrl.Result{}, statusErr - } - - if statusResult.Requeue { - logger.V(1).Info("Reconciling status not finished. Requeueing.") - return statusResult, nil - } - - logger.Info("RateLimitPolicy reconciled successfully") - return ctrl.Result{}, nil -} - -// validate performs validation before proceeding with the reconcile loop, returning a common.ErrInvalid on failing validation -func (r *RateLimitPolicyReconciler) validate(rlp *kuadrantv1beta3.RateLimitPolicy, targetNetworkObject client.Object) error { - if err := kuadrant.ValidateHierarchicalRules(rlp, targetNetworkObject); err != nil { - return kuadrant.NewErrInvalid(rlp.Kind(), err) - } - - return nil -} - -func (r *RateLimitPolicyReconciler) reconcileResources(ctx context.Context, rlp *kuadrantv1beta3.RateLimitPolicy, targetNetworkObject client.Object) error { - if err := r.validate(rlp, targetNetworkObject); err != nil { - return err - } - - // reconcile based on gateway diffs - gatewayDiffObj, err := reconcilers.ComputeGatewayDiffs(ctx, r.Client(), rlp, targetNetworkObject) - if err != nil { - return err - } - - if err := r.reconcileLimits(ctx, rlp); err != nil { - return fmt.Errorf("reconcile Limitador error %w", err) - } - - // set direct back ref - i.e. claim the target network object as taken asap - if err := r.reconcileNetworkResourceDirectBackReference(ctx, rlp, targetNetworkObject); err != nil { - return fmt.Errorf("reconcile TargetBackReference error %w", err) - } - - // set annotation of policies affecting the gateway - should be the last step, only when all the reconciliation steps succeed - if err := r.TargetRefReconciler.ReconcileGatewayPolicyReferences(ctx, rlp, gatewayDiffObj); err != nil { - return fmt.Errorf("ReconcileGatewayPolicyReferences error %w", err) - } - - return nil -} - -func (r *RateLimitPolicyReconciler) deleteResources(ctx context.Context, rlp *kuadrantv1beta3.RateLimitPolicy, targetNetworkObject client.Object) error { - // delete based on gateway diffs - gatewayDiffObj, err := reconcilers.ComputeGatewayDiffs(ctx, r.Client(), rlp, targetNetworkObject) - if err != nil { - return err - } - - if err := r.deleteLimits(ctx, rlp); err != nil && !apierrors.IsNotFound(err) { - return err - } - - // remove direct back ref - if targetNetworkObject != nil { - if err := r.deleteNetworkResourceDirectBackReference(ctx, targetNetworkObject, rlp); err != nil { - return err - } - } - - // update annotation of policies affecting the gateway - return r.TargetRefReconciler.ReconcileGatewayPolicyReferences(ctx, rlp, gatewayDiffObj) -} - -// Ensures only one RLP targets the network resource -func (r *RateLimitPolicyReconciler) reconcileNetworkResourceDirectBackReference(ctx context.Context, policy *kuadrantv1beta3.RateLimitPolicy, targetNetworkObject client.Object) error { - return r.TargetRefReconciler.ReconcileTargetBackReference(ctx, policy, targetNetworkObject, policy.DirectReferenceAnnotationName()) -} - -func (r *RateLimitPolicyReconciler) deleteNetworkResourceDirectBackReference(ctx context.Context, targetNetworkObject client.Object, policy *kuadrantv1beta3.RateLimitPolicy) error { - return r.TargetRefReconciler.DeleteTargetBackReference(ctx, targetNetworkObject, policy.DirectReferenceAnnotationName()) -} - -// SetupWithManager sets up the controller with the Manager. -func (r *RateLimitPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { - ok, err := kuadrantgatewayapi.IsGatewayAPIInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("Ratelimitpolicy controller disabled. GatewayAPI was not found") - return nil - } - - httpRouteEventMapper := mappers.NewHTTPRouteEventMapper(mappers.WithLogger(r.Logger().WithName("httproute.mapper")), mappers.WithClient(mgr.GetClient())) - gatewayEventMapper := mappers.NewGatewayEventMapper( - kuadrantv1beta3.NewRateLimitPolicyType(), - mappers.WithLogger(r.Logger().WithName("gateway.mapper")), - mappers.WithClient(mgr.GetClient()), - ) - - return ctrl.NewControllerManagedBy(mgr). - For(&kuadrantv1beta3.RateLimitPolicy{}). - Watches( - &gatewayapiv1.HTTPRoute{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, object client.Object) []reconcile.Request { - return httpRouteEventMapper.MapToPolicy(ctx, object, kuadrantv1beta3.NewRateLimitPolicyType()) - }), - ). - // Currently the purpose is to generate events when rlp references change in gateways - // so the status of the rlps targeting a route can be keep in sync - Watches(&gatewayapiv1.Gateway{}, handler.EnqueueRequestsFromMapFunc(gatewayEventMapper.Map)). - Complete(r) -} diff --git a/controllers/ratelimitpolicy_enforced_status_controller.go b/controllers/ratelimitpolicy_enforced_status_controller.go deleted file mode 100644 index d446262d0..000000000 --- a/controllers/ratelimitpolicy_enforced_status_controller.go +++ /dev/null @@ -1,311 +0,0 @@ -package controllers - -import ( - "context" - "errors" - "reflect" - "sort" - - "github.com/go-logr/logr" - "github.com/google/uuid" - "github.com/samber/lo" - 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" - - limitadorv1alpha1 "github.com/kuadrant/limitador-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" -) - -type RateLimitPolicyEnforcedStatusReconciler struct { - *reconcilers.BaseReconciler -} - -func (r *RateLimitPolicyEnforcedStatusReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := r.Logger().WithValues("gateway", req.NamespacedName, "request id", uuid.NewString()) - logger.Info("Reconciling policy 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 - } - - topology, err := r.buildTopology(ctx, gw) - if err != nil { - return ctrl.Result{}, err - } - - indexes := kuadrantgatewayapi.NewTopologyIndexes(topology) - policies := indexes.PoliciesFromGateway(gw) - numRoutes := len(topology.Routes()) - numUntargetedRoutes := len(indexes.GetUntargetedRoutes(gw)) - - sort.Sort(kuadrantgatewayapi.PolicyByTargetRefKindAndCreationTimeStamp(policies)) - - // for each policy: - // if the policy is a gateway policy: - // and no route exists (numRoutes == 0) → set the Enforced condition of the gateway policy to 'false' (unknown) - // and the gateway contains routes (numRoutes > 0) - // and the gateway policy contains overrides → set the Enforced condition of the gateway policy to 'true' - // and the gateway policy contains defaults: - // and no routes have an attached policy (numUntargetedRoutes == numRoutes) → set the Enforced condition of the gateway policy to 'true' - // and some routes have attached policy (numUntargetedRoutes < numRoutes && numUntargetedRoutes > 1) → set the Enforced condition of the gateway policy to 'true' (partially enforced) - // and all routes have attached policy (numUntargetedRoutes == 0) → set the Enforced condition of the gateway policy to 'false' (overridden) - // if the policy is a route policy: - // and the route has no gateway parent (numGatewayParents == 0) → set the Enforced condition of the route policy to 'false' (unknown) - // and the route has gateway parents (numGatewayParents > 0) - // and all gateway parents of the route have gateway policies with overrides (numGatewayParentsWithOverrides == numGatewayParents) → set the Enforced condition of the route policy to 'false' (overridden) - // and some gateway parents of the route have gateway policies with overrides (numGatewayParentsWithOverrides < numGatewayParents && numGatewayParentsWithOverrides > 1) → set the Enforced condition of the route policy to 'true' (partially enforced) - // and no gateway parent of the route has gateway policies with overrides (numGatewayParentsWithOverrides == 0) → set the Enforced condition of the route policy to 'true' - - for i := range policies { - policy := policies[i] - rlpKey := client.ObjectKeyFromObject(policy) - rlp := policy.(*kuadrantv1beta3.RateLimitPolicy) - conditions := rlp.GetStatus().GetConditions() - - // skip policy if accepted condition is false - if meta.IsStatusConditionFalse(policy.GetStatus().GetConditions(), string(gatewayapiv1alpha2.PolicyConditionAccepted)) { - continue - } - - // ensure no error on underlying subresource (i.e. Limitador) - if condition := r.hasErrCondOnSubResource(ctx, rlp); condition != nil { - if err := r.setCondition(ctx, rlp, &conditions, *condition); err != nil { - return ctrl.Result{}, err - } - continue - } - - var condition *metav1.Condition - - if kuadrantgatewayapi.IsTargetRefGateway(rlp.GetTargetRef()) { // gateway policy - if numRoutes == 0 { - condition = kuadrant.EnforcedCondition(rlp, kuadrant.NewErrUnknown(rlp.Kind(), errors.New("no free routes to enforce policy")), true) // unknown - } else { - if rlp.Spec.Overrides != nil { - condition = kuadrant.EnforcedCondition(rlp, nil, true) // fully enforced - } else { - if numUntargetedRoutes == numRoutes { - condition = kuadrant.EnforcedCondition(rlp, nil, true) // fully enforced - } else if numUntargetedRoutes > 0 { - condition = kuadrant.EnforcedCondition(rlp, nil, false) // partially enforced - } else { - otherPolicies := lo.FilterMap(policies, func(p kuadrantgatewayapi.Policy, _ int) (client.ObjectKey, bool) { - key := client.ObjectKeyFromObject(p) - return key, key != rlpKey - }) - condition = kuadrant.EnforcedCondition(rlp, kuadrant.NewErrOverridden(rlp.Kind(), otherPolicies), true) // overridden - } - } - } - } else { // route policy - route := indexes.GetPolicyHTTPRoute(rlp) - gatewayParents := lo.FilterMap(kuadrantgatewayapi.GetRouteAcceptedGatewayParentKeys(route), func(parentKey client.ObjectKey, _ int) (*gatewayapiv1.Gateway, bool) { - g, found := utils.Find(topology.Gateways(), func(g kuadrantgatewayapi.GatewayNode) bool { return client.ObjectKeyFromObject(g.Gateway) == parentKey }) - if !found { - return nil, false - } - return g.Gateway, true - }) - numGatewayParents := len(gatewayParents) - if numGatewayParents == 0 { - condition = kuadrant.EnforcedCondition(rlp, kuadrant.NewErrUnknown(rlp.Kind(), errors.New("the targeted route has not been accepted by any gateway parent")), true) // unknown - } else { - var gatewayParentOverridePolicies []kuadrantgatewayapi.Policy - gatewayParentsWithOverrides := utils.Filter(gatewayParents, func(gatewayParent *gatewayapiv1.Gateway) bool { - _, found := utils.Find(indexes.PoliciesFromGateway(gatewayParent), func(p kuadrantgatewayapi.Policy) bool { - rlp := p.(*kuadrantv1beta3.RateLimitPolicy) - if kuadrantgatewayapi.IsTargetRefGateway(p.GetTargetRef()) && rlp != nil && rlp.Spec.Overrides != nil { - gatewayParentOverridePolicies = append(gatewayParentOverridePolicies, p) - return true - } - return false - }) - return found - }) - numGatewayParentsWithOverrides := len(gatewayParentsWithOverrides) - if numGatewayParentsWithOverrides == numGatewayParents { - sort.Sort(kuadrantgatewayapi.PolicyByTargetRefKindAndCreationTimeStamp(gatewayParentOverridePolicies)) - condition = kuadrant.EnforcedCondition(rlp, kuadrant.NewErrOverridden(rlp.Kind(), utils.Map(gatewayParentOverridePolicies, func(p kuadrantgatewayapi.Policy) client.ObjectKey { return client.ObjectKeyFromObject(p) })), true) // overridden - } else if numGatewayParentsWithOverrides > 0 { - condition = kuadrant.EnforcedCondition(rlp, nil, false) // partially enforced - } else { - condition = kuadrant.EnforcedCondition(rlp, nil, true) // fully enforced - } - } - } - - if err := r.setCondition(ctx, rlp, &conditions, *condition); err != nil { - return ctrl.Result{}, err - } - } - - logger.Info("Policy status reconciled successfully") - return ctrl.Result{}, nil -} - -func (r *RateLimitPolicyEnforcedStatusReconciler) buildTopology(ctx context.Context, gw *gatewayapiv1.Gateway) (*kuadrantgatewayapi.Topology, error) { - logger, err := logr.FromContext(ctx) - if err != nil { - return nil, err - } - - gatewayList := &gatewayapiv1.GatewayList{} - err = r.Client().List(ctx, gatewayList) - logger.V(1).Info("list gateways", "#gateways", len(gatewayList.Items), "err", err) - 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 - } - - policyList := &kuadrantv1beta3.RateLimitPolicyList{} - err = r.Client().List(ctx, policyList) - logger.V(1).Info("list rate limit policies", "#policies", len(policyList.Items), "err", err) - if err != nil { - return nil, err - } - - return kuadrantgatewayapi.NewTopology( - kuadrantgatewayapi.WithAcceptedRoutesLinkedOnly(), - kuadrantgatewayapi.WithGateways(utils.Map(gatewayList.Items, ptr.To[gatewayapiv1.Gateway])), - kuadrantgatewayapi.WithRoutes(utils.Map(routeList.Items, ptr.To[gatewayapiv1.HTTPRoute])), - kuadrantgatewayapi.WithPolicies(utils.Map(policyList.Items, func(p kuadrantv1beta3.RateLimitPolicy) kuadrantgatewayapi.Policy { return &p })), - kuadrantgatewayapi.WithLogger(logger), - ) -} - -func (r *RateLimitPolicyEnforcedStatusReconciler) hasErrCondOnSubResource(ctx context.Context, p *kuadrantv1beta3.RateLimitPolicy) *metav1.Condition { - logger, err := logr.FromContext(ctx) - logger.WithName("hasErrCondOnSubResource") - if err != nil { - logger = r.Logger() - } - - limitador, err := GetLimitador(ctx, r.Client(), p) - if err != nil { - logger.V(1).Error(err, "failed to get limitador") - return kuadrant.EnforcedCondition(p, kuadrant.NewErrUnknown(p.Kind(), err), false) - } - if meta.IsStatusConditionFalse(limitador.Status.Conditions, "Ready") { - logger.V(1).Info("Limitador is not ready") - return kuadrant.EnforcedCondition(p, kuadrant.NewErrUnknown(p.Kind(), errors.New("limitador is not ready")), false) - } - - logger.V(1).Info("limitador is ready and enforcing limits") - return nil -} - -func (r *RateLimitPolicyEnforcedStatusReconciler) setCondition(ctx context.Context, p *kuadrantv1beta3.RateLimitPolicy, conditions *[]metav1.Condition, cond metav1.Condition) error { - logger, err := logr.FromContext(ctx) - logger.WithName("setCondition") - if err != nil { - logger = r.Logger() - } - - idx := utils.Index(*conditions, func(c metav1.Condition) bool { - return c.Type == cond.Type && c.Status == cond.Status && c.Reason == cond.Reason && c.Message == cond.Message - }) - if idx == -1 { - meta.SetStatusCondition(conditions, cond) - p.Status.Conditions = *conditions - if err := r.Client().Status().Update(ctx, p); err != nil { - logger.Error(err, "failed to update policy status") - return err - } - return nil - } - - logger.V(1).Info("skipping policy enforced condition status update - already up to date") - return nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *RateLimitPolicyEnforcedStatusReconciler) SetupWithManager(mgr ctrl.Manager) error { - ok, err := kuadrantgatewayapi.IsGatewayAPIInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("RateLimitPolicyEnforcedStatus 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()), - ) - - limitadorStatusToParentGatewayEventMapper := limitadorStatusRLPGatewayEventHandler{ - Client: r.Client(), - Logger: r.Logger().WithName("limitadorStatusToRLPsEventHandler"), - } - - 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.RateLimitPolicy{}, - handler.EnqueueRequestsFromMapFunc(policyToParentGatewaysEventMapper.Map), - builder.WithPredicates(policyStatusChangedPredicate), - ). - Watches(&limitadorv1alpha1.Limitador{}, limitadorStatusToParentGatewayEventMapper). - Complete(r) -} diff --git a/controllers/ratelimitpolicy_limits.go b/controllers/ratelimitpolicy_limits.go deleted file mode 100644 index 442df9b87..000000000 --- a/controllers/ratelimitpolicy_limits.go +++ /dev/null @@ -1,198 +0,0 @@ -package controllers - -import ( - "context" - "fmt" - "slices" - "sort" - - "github.com/go-logr/logr" - limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" - "github.com/samber/lo" - "k8s.io/utils/ptr" - "sigs.k8s.io/controller-runtime/pkg/client" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - "github.com/kuadrant/kuadrant-operator/pkg/common" - kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" - "github.com/kuadrant/kuadrant-operator/pkg/library/utils" - "github.com/kuadrant/kuadrant-operator/pkg/rlptools" -) - -func (r *RateLimitPolicyReconciler) reconcileLimits(ctx context.Context, rlp *kuadrantv1beta3.RateLimitPolicy) error { - policies, err := r.getPolicies(ctx) - if err != nil { - return err - } - topology, err := r.buildTopology(ctx, policies) - if err != nil { - return err - } - return r.reconcileLimitador(ctx, rlp, topology) -} - -func (r *RateLimitPolicyReconciler) deleteLimits(ctx context.Context, rlp *kuadrantv1beta3.RateLimitPolicy) error { - policies, err := r.getPolicies(ctx) - if err != nil { - return err - } - policiesWithoutRLP := utils.Filter(policies, func(policy kuadrantgatewayapi.Policy) bool { - return client.ObjectKeyFromObject(policy) != client.ObjectKeyFromObject(rlp) - }) - topology, err := r.buildTopology(ctx, policiesWithoutRLP) - if err != nil { - return err - } - return r.reconcileLimitador(ctx, rlp, topology) -} - -func (r *RateLimitPolicyReconciler) reconcileLimitador(ctx context.Context, rlp *kuadrantv1beta3.RateLimitPolicy, topology *kuadrantgatewayapi.Topology) error { - logger, _ := logr.FromContext(ctx) - logger = logger.WithName("reconcileLimitador") - - rateLimitIndex := r.buildRateLimitIndex(ctx, topology) - - // get the current limitador cr for the kuadrant instance so we can compare if it needs to be updated - limitador, err := GetLimitador(ctx, r.Client(), rlp) - if err != nil { - return err - } - // return if limitador is up-to-date - if rlptools.Equal(rateLimitIndex.ToRateLimits(), limitador.Spec.Limits) { - logger.V(1).Info("limitador is up to date, skipping update") - return nil - } - - // update limitador - limitador.Spec.Limits = rateLimitIndex.ToRateLimits() - err = r.UpdateResource(ctx, limitador) - logger.V(1).Info("update limitador", "limitador", client.ObjectKeyFromObject(limitador), "err", err) - if err != nil { - return err - } - - return nil -} - -func GetLimitador(ctx context.Context, k8sclient client.Client, rlp *kuadrantv1beta3.RateLimitPolicy) (*limitadorv1alpha1.Limitador, error) { - logger, _ := logr.FromContext(ctx) - - logger.V(1).Info("get kuadrant namespace") - var kuadrantNamespace string - kuadrantNamespace, isSet := kuadrant.GetKuadrantNamespaceFromPolicy(rlp) - if !isSet { - var err error - kuadrantNamespace, err = kuadrant.GetKuadrantNamespaceFromPolicyTargetRef(ctx, k8sclient, rlp) - if err != nil { - logger.Error(err, "failed to get kuadrant namespace") - return nil, err - } - kuadrant.AnnotateObject(rlp, kuadrantNamespace) - err = k8sclient.Update(ctx, rlp) // @guicassolato: not sure if this belongs to here - if err != nil { - logger.Error(err, "failed to update policy, re-queuing") - return nil, err - } - } - limitadorKey := client.ObjectKey{Name: common.LimitadorName, Namespace: kuadrantNamespace} - limitador := &limitadorv1alpha1.Limitador{} - err := k8sclient.Get(ctx, limitadorKey, limitador) - logger.V(1).Info("get limitador", "limitador", limitadorKey, "err", err) - if err != nil { - return nil, err - } - - return limitador, nil -} - -func (r *RateLimitPolicyReconciler) getPolicies(ctx context.Context) ([]kuadrantgatewayapi.Policy, error) { - logger, _ := logr.FromContext(ctx) - - rlpList := &kuadrantv1beta3.RateLimitPolicyList{} - err := r.Client().List(ctx, rlpList) - logger.V(1).Info("topology: list rate limit policies", "#RLPS", len(rlpList.Items), "err", err) - if err != nil { - return nil, err - } - - policies := utils.Map(rlpList.Items, func(p kuadrantv1beta3.RateLimitPolicy) kuadrantgatewayapi.Policy { return &p }) - - return policies, nil -} - -func (r *RateLimitPolicyReconciler) buildTopology(ctx context.Context, policies []kuadrantgatewayapi.Policy) (*kuadrantgatewayapi.Topology, error) { - logger, _ := logr.FromContext(ctx) - - gwList := &gatewayapiv1.GatewayList{} - err := r.Client().List(ctx, gwList) - logger.V(1).Info("topology: list gateways", "#Gateways", len(gwList.Items), "err", err) - if err != nil { - return nil, err - } - - routeList := &gatewayapiv1.HTTPRouteList{} - err = r.Client().List(ctx, routeList) - logger.V(1).Info("topology: list httproutes", "#HTTPRoutes", len(routeList.Items), "err", err) - if err != nil { - return nil, err - } - - return kuadrantgatewayapi.NewTopology( - kuadrantgatewayapi.WithAcceptedRoutesLinkedOnly(), - kuadrantgatewayapi.WithProgrammedGatewaysOnly(), - kuadrantgatewayapi.WithGateways(utils.Map(gwList.Items, ptr.To[gatewayapiv1.Gateway])), - kuadrantgatewayapi.WithRoutes(utils.Map(routeList.Items, ptr.To[gatewayapiv1.HTTPRoute])), - kuadrantgatewayapi.WithPolicies(policies), - kuadrantgatewayapi.WithLogger(logger), - ) -} - -func (r *RateLimitPolicyReconciler) buildRateLimitIndex(ctx context.Context, topology *kuadrantgatewayapi.Topology) *rlptools.RateLimitIndex { - logger, _ := logr.FromContext(ctx) - logger = logger.WithName("buildRateLimitIndex") - - gateways := lo.KeyBy(topology.Gateways(), func(gateway kuadrantgatewayapi.GatewayNode) string { - return client.ObjectKeyFromObject(gateway.Gateway).String() - }) - - // sort the gateways for deterministic output and consistent comparison against existing objects - gatewayNames := lo.Keys(gateways) - slices.Sort(gatewayNames) - - rateLimitIndex := rlptools.NewRateLimitIndex() - - for _, gatewayName := range gatewayNames { - gateway := gateways[gatewayName].Gateway - topologyWithOverrides, err := rlptools.ApplyOverrides(topology, gateway) - if err != nil { - logger.Error(err, "failed to apply overrides") - return nil - } - - // sort the policies for deterministic output and consistent comparison against existing objects - indexes := kuadrantgatewayapi.NewTopologyIndexes(topologyWithOverrides) - policies := indexes.PoliciesFromGateway(gateway) - sort.Sort(kuadrantgatewayapi.PolicyByTargetRefKindAndCreationTimeStamp(policies)) - - logger.V(1).Info("new rate limit index", "gateway", client.ObjectKeyFromObject(gateway), "policies", lo.Map(policies, func(p kuadrantgatewayapi.Policy, _ int) string { return client.ObjectKeyFromObject(p).String() })) - - for _, policy := range policies { - rlpKey := client.ObjectKeyFromObject(policy) - gatewayKey := client.ObjectKeyFromObject(gateway) - key := rlptools.RateLimitIndexKey{ - RateLimitPolicyKey: rlpKey, - GatewayKey: gatewayKey, - } - if _, ok := rateLimitIndex.Get(key); ok { // should never happen - logger.Error(fmt.Errorf("unexpected duplicate rate limit policy key found"), "failed do add rate limit policy to index", "RateLimitPolicy", rlpKey.String(), "Gateway", gatewayKey) - continue - } - rlp := policy.(*kuadrantv1beta3.RateLimitPolicy) - rateLimitIndex.Set(key, rlptools.LimitadorRateLimitsFromRLP(rlp)) - } - } - - return rateLimitIndex -} diff --git a/controllers/ratelimitpolicy_status.go b/controllers/ratelimitpolicy_status.go deleted file mode 100644 index 6ece9605b..000000000 --- a/controllers/ratelimitpolicy_status.go +++ /dev/null @@ -1,59 +0,0 @@ -package controllers - -import ( - "context" - "slices" - - "github.com/go-logr/logr" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" -) - -func (r *RateLimitPolicyReconciler) reconcileStatus(ctx context.Context, rlp *kuadrantv1beta3.RateLimitPolicy, specErr error) (ctrl.Result, error) { - logger, _ := logr.FromContext(ctx) - newStatus := r.calculateStatus(rlp, specErr) - if err := r.ReconcileResourceStatus( - ctx, - client.ObjectKeyFromObject(rlp), - &kuadrantv1beta3.RateLimitPolicy{}, - kuadrantv1beta3.RateLimitPolicyStatusMutator(newStatus, logger), - ); err != nil { - // Ignore conflicts, resource might just be outdated. - if apierrors.IsConflict(err) { - logger.V(1).Info("Failed to update status: resource might just be outdated") - return reconcile.Result{Requeue: true}, nil - } - - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil -} - -func (r *RateLimitPolicyReconciler) calculateStatus(rlp *kuadrantv1beta3.RateLimitPolicy, specErr error) *kuadrantv1beta3.RateLimitPolicyStatus { - newStatus := &kuadrantv1beta3.RateLimitPolicyStatus{ - // Copy initial conditions. Otherwise, status will always be updated - Conditions: slices.Clone(rlp.Status.Conditions), - } - - newStatus.SetObservedGeneration(rlp.GetGeneration()) - - acceptedCond := kuadrant.AcceptedCondition(rlp, specErr) - - meta.SetStatusCondition(&newStatus.Conditions, *acceptedCond) - - // Do not set enforced condition if Accepted condition is false - if meta.IsStatusConditionFalse(newStatus.Conditions, string(gatewayapiv1alpha2.PolicyReasonAccepted)) { - meta.RemoveStatusCondition(&newStatus.Conditions, string(kuadrant.PolicyConditionEnforced)) - return newStatus - } - - return newStatus -} diff --git a/controllers/ratelimitpolicy_status_test.go b/controllers/ratelimitpolicy_status_test.go deleted file mode 100644 index d1ebe7ea9..000000000 --- a/controllers/ratelimitpolicy_status_test.go +++ /dev/null @@ -1,70 +0,0 @@ -//go:build unit - -package controllers - -import ( - "errors" - "reflect" - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" -) - -func TestRateLimitPolicyReconciler_calculateStatus(t *testing.T) { - type args struct { - rlp *kuadrantv1beta3.RateLimitPolicy - specErr error - } - tests := []struct { - name string - args args - want *kuadrantv1beta3.RateLimitPolicyStatus - }{ - { - name: "Enforced status block removed if policy not Accepted. (Regression test)", // https://github.com/Kuadrant/kuadrant-operator/issues/588 - args: args{ - rlp: &kuadrantv1beta3.RateLimitPolicy{ - Status: kuadrantv1beta3.RateLimitPolicyStatus{ - Conditions: []metav1.Condition{ - { - Message: "not accepted", - Type: string(gatewayapiv1alpha2.PolicyConditionAccepted), - Status: metav1.ConditionFalse, - Reason: string(gatewayapiv1alpha2.PolicyReasonTargetNotFound), - }, - { - Message: "RateLimitPolicy has been successfully enforced", - Type: string(kuadrant.PolicyConditionEnforced), - Status: metav1.ConditionTrue, - Reason: string(kuadrant.PolicyConditionEnforced), - }, - }, - }, - }, - specErr: kuadrant.NewErrInvalid("RateLimitPolicy", errors.New("policy Error")), - }, - want: &kuadrantv1beta3.RateLimitPolicyStatus{ - Conditions: []metav1.Condition{ - { - Message: "RateLimitPolicy target is invalid: policy Error", - Type: string(gatewayapiv1alpha2.PolicyConditionAccepted), - Status: metav1.ConditionFalse, - Reason: string(gatewayapiv1alpha2.PolicyReasonInvalid), - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := &RateLimitPolicyReconciler{} - if got := r.calculateStatus(tt.args.rlp, tt.args.specErr); !reflect.DeepEqual(got, tt.want) { - t.Errorf("calculateStatus() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/controllers/ratelimitpolicy_status_updater.go b/controllers/ratelimitpolicy_status_updater.go new file mode 100644 index 000000000..21a2c3476 --- /dev/null +++ b/controllers/ratelimitpolicy_status_updater.go @@ -0,0 +1,117 @@ +package controllers + +import ( + "context" + "slices" + "sync" + + "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/client-go/dynamic" + "k8s.io/utils/ptr" + gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + "github.com/kuadrant/policy-machinery/controller" + "github.com/kuadrant/policy-machinery/machinery" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" + kuadrantenvoygateway "github.com/kuadrant/kuadrant-operator/pkg/envoygateway" + kuadrantistio "github.com/kuadrant/kuadrant-operator/pkg/istio" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" +) + +type rateLimitPolicyStatusUpdater struct { + client *dynamic.DynamicClient +} + +func (r *rateLimitPolicyStatusUpdater) Subscription() controller.Subscription { + return controller.Subscription{ + ReconcileFunc: r.UpdateStatus, + Events: []controller.ResourceEventMatcher{ + {Kind: &kuadrantv1beta1.KuadrantGroupKind}, + {Kind: &machinery.GatewayClassGroupKind}, + {Kind: &machinery.GatewayGroupKind}, + {Kind: &machinery.HTTPRouteGroupKind}, + {Kind: &kuadrantv1beta3.RateLimitPolicyGroupKind, EventType: ptr.To(controller.CreateEvent)}, + {Kind: &kuadrantv1beta3.RateLimitPolicyGroupKind, EventType: ptr.To(controller.UpdateEvent)}, + {Kind: &kuadrantv1beta1.LimitadorGroupKind}, + {Kind: &kuadrantistio.EnvoyFilterGroupKind}, + {Kind: &kuadrantistio.WasmPluginGroupKind}, + {Kind: &kuadrantenvoygateway.EnvoyPatchPolicyGroupKind}, + {Kind: &kuadrantenvoygateway.EnvoyExtensionPolicyGroupKind}, + }, + } +} + +func (r *rateLimitPolicyStatusUpdater) UpdateStatus(ctx context.Context, _ []controller.ResourceEvent, topology *machinery.Topology, _ error, state *sync.Map) error { + logger := controller.LoggerFromContext(ctx).WithName("rateLimitPolicyStatusUpdater") + + policies := lo.FilterMap(topology.Policies().Items(), func(item machinery.Policy, index int) (*kuadrantv1beta3.RateLimitPolicy, bool) { + p, ok := item.(*kuadrantv1beta3.RateLimitPolicy) + return p, ok + }) + + logger.V(1).Info("updating rate limit policy statuses", "policies", len(policies)) + + for _, policy := range policies { + if policy.GetDeletionTimestamp() != nil { + logger.V(1).Info("ratelimitpolicy is marked for deletion, skipping", "name", policy.Name, "namespace", policy.Namespace) + continue + } + + // copy initial conditions, otherwise status will always be updated + newStatus := &kuadrantv1beta3.RateLimitPolicyStatus{ + Conditions: slices.Clone(policy.Status.Conditions), + ObservedGeneration: policy.Status.ObservedGeneration, + } + + var err error + if validatedPolicies, validated := state.Load(StateRateLimitPolicyValid); validated { + err = validatedPolicies.(map[string]error)[policy.GetLocator()] + } + meta.SetStatusCondition(&newStatus.Conditions, *kuadrant.AcceptedCondition(policy, err)) + + // do not set enforced condition if Accepted condition is false + if meta.IsStatusConditionFalse(newStatus.Conditions, string(gatewayapiv1alpha2.PolicyReasonAccepted)) { + meta.RemoveStatusCondition(&newStatus.Conditions, string(kuadrant.PolicyConditionEnforced)) + } else { + enforcedCond := r.enforcedCondition(ctx, policy, topology) + meta.SetStatusCondition(&newStatus.Conditions, *enforcedCond) + } + + equalStatus := equality.Semantic.DeepEqual(newStatus, policy.Status) + if equalStatus && policy.Generation == policy.Status.ObservedGeneration { + logger.V(1).Info("policy status unchanged, skipping update") + continue + } + newStatus.ObservedGeneration = policy.Generation + policy.Status = *newStatus + + obj, err := controller.Destruct(policy) + if err != nil { + logger.Error(err, "unable to destruct policy") // should never happen + continue + } + + _, err = r.client.Resource(kuadrantv1beta3.RateLimitPoliciesResource).Namespace(policy.GetNamespace()).UpdateStatus(ctx, obj, metav1.UpdateOptions{}) + if err != nil { + logger.Error(err, "unable to update status for ratelimitpolicy", "name", policy.GetName(), "namespace", policy.GetNamespace()) + } + } + + logger.V(1).Info("finished updating rate limit policy statuses") + + return nil +} + +func (r *rateLimitPolicyStatusUpdater) enforcedCondition(ctx context.Context, policy *kuadrantv1beta3.RateLimitPolicy, topology *machinery.Topology) *metav1.Condition { + var err error + if err != nil { // TODO: check if any condition for the policy to be enforced fails + return kuadrant.EnforcedCondition(policy, kuadrant.NewErrUnknown(kuadrantv1beta3.RateLimitPolicyGroupKind.Kind, err), false) + } + + return kuadrant.EnforcedCondition(policy, nil, true) +} diff --git a/controllers/state_of_the_world.go b/controllers/state_of_the_world.go index ad38d7d98..24a2e517b 100644 --- a/controllers/state_of_the_world.go +++ b/controllers/state_of_the_world.go @@ -2,6 +2,7 @@ package controllers import ( "fmt" + "sort" certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" @@ -9,6 +10,8 @@ import ( authorinov1beta1 "github.com/kuadrant/authorino-operator/api/v1beta1" limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" "github.com/kuadrant/policy-machinery/controller" + "github.com/kuadrant/policy-machinery/machinery" + "github.com/samber/lo" istioclientgoextensionv1alpha1 "istio.io/client-go/pkg/apis/extensions/v1alpha1" istioclientnetworkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" istioclientgosecurityv1beta1 "istio.io/client-go/pkg/apis/security/v1beta1" @@ -35,8 +38,17 @@ var ( operatorNamespace = env.GetString("OPERATOR_NAMESPACE", "kuadrant-system") ) +// gateway-api permissions //+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gatewayclasses,verbs=list;watch +// istio permissions +//+kubebuilder:rbac:groups=networking.istio.io,resources=envoyfilters,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=extensions.istio.io,resources=wasmplugins,verbs=get;list;watch;create;update;patch;delete + +// envoy gateway permissions +//+kubebuilder:rbac:groups=gateway.envoyproxy.io,resources=envoypatchpolicies,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=gateway.envoyproxy.io,resources=envoyextensionpolicies,verbs=get;list;watch;create;update;patch;delete + func NewPolicyMachineryController(manager ctrlruntime.Manager, client *dynamic.DynamicClient, logger logr.Logger) *controller.Controller { controllerOpts := []controller.ControllerOption{ controller.ManagedBy(manager), @@ -148,7 +160,7 @@ func buildReconciler(client *dynamic.DynamicClient, isIstioInstalled, isEnvoyGat NewDNSWorkflow().Run, NewTLSWorkflow().Run, NewAuthWorkflow().Run, - NewRateLimitWorkflow().Run, + NewRateLimitWorkflow(client).Run, }, Postcondition: finalStepsWorkflow(client, isIstioInstalled, isEnvoyGatewayInstalled).Run, } @@ -210,3 +222,18 @@ func GetOldestKuadrant(kuadrants []*kuadrantv1beta1.Kuadrant) (*kuadrantv1beta1. } return oldest, nil } + +var ErrMissingKuadrant = fmt.Errorf("missing kuadrant object in topology") + +func GetKuadrantFromTopology(topology *machinery.Topology) (*kuadrantv1beta1.Kuadrant, error) { + kuadrants := lo.FilterMap(topology.Objects().Roots(), func(root machinery.Object, _ int) (controller.Object, bool) { + o, isSortable := root.(controller.Object) + return o, isSortable && root.GroupVersionKind().GroupKind() == kuadrantv1beta1.KuadrantGroupKind && o.GetDeletionTimestamp() == nil + }) + if len(kuadrants) == 0 { + return nil, ErrMissingKuadrant + } + sort.Sort(controller.ObjectsByCreationTimestamp(kuadrants)) + kuadrant, _ := kuadrants[0].(*kuadrantv1beta1.Kuadrant) + return kuadrant, nil +} diff --git a/controllers/target_status_controller.go b/controllers/target_status_controller.go index 799a73131..5948b8b30 100644 --- a/controllers/target_status_controller.go +++ b/controllers/target_status_controller.go @@ -81,10 +81,10 @@ func (r *TargetStatusReconciler) Reconcile(eventCtx context.Context, req ctrl.Re func (r *TargetStatusReconciler) reconcileResources(ctx context.Context, gw *gatewayapiv1.Gateway) error { policyKinds := map[kuadrantgatewayapi.Policy]client.ObjectList{ - &kuadrantv1beta2.AuthPolicy{TypeMeta: ctrl.TypeMeta{Kind: "AuthPolicy"}}: &kuadrantv1beta2.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{}, + &kuadrantv1beta2.AuthPolicy{TypeMeta: ctrl.TypeMeta{Kind: "AuthPolicy"}}: &kuadrantv1beta2.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 diff --git a/controllers/test_common.go b/controllers/test_common.go index 494d5e344..c43672047 100644 --- a/controllers/test_common.go +++ b/controllers/test_common.go @@ -86,19 +86,6 @@ func SetupKuadrantOperatorForTest(s *runtime.Scheme, cfg *rest.Config) { }).SetupWithManager(mgr) Expect(err).NotTo(HaveOccurred()) - rateLimitPolicyBaseReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("ratelimitpolicy"), - mgr.GetEventRecorderFor("RateLimitPolicy"), - ) - - err = (&RateLimitPolicyReconciler{ - BaseReconciler: rateLimitPolicyBaseReconciler, - TargetRefReconciler: reconcilers.TargetRefReconciler{Client: mgr.GetClient()}, - }).SetupWithManager(mgr) - - Expect(err).NotTo(HaveOccurred()) - tlsPolicyBaseReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("tlspolicy"), @@ -139,18 +126,6 @@ func SetupKuadrantOperatorForTest(s *runtime.Scheme, cfg *rest.Config) { Expect(err).NotTo(HaveOccurred()) - limitadorClusterEnvoyFilterBaseReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("ratelimitpolicy").WithName("envoyfilter"), - mgr.GetEventRecorderFor("LimitadorClusterEnvoyFilter"), - ) - - err = (&LimitadorClusterEnvoyFilterReconciler{ - BaseReconciler: limitadorClusterEnvoyFilterBaseReconciler, - }).SetupWithManager(mgr) - - Expect(err).NotTo(HaveOccurred()) - gatewayKuadrantBaseReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("kuadrant").WithName("gateway"), @@ -163,18 +138,6 @@ func SetupKuadrantOperatorForTest(s *runtime.Scheme, cfg *rest.Config) { Expect(err).NotTo(HaveOccurred()) - rateLimitingIstioWASMPluginBaseReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("ratelimitpolicy").WithName("wasmplugin"), - mgr.GetEventRecorderFor("RateLimitingIstioWASMPlugin"), - ) - - err = (&RateLimitingIstioWASMPluginReconciler{ - BaseReconciler: rateLimitingIstioWASMPluginBaseReconciler, - }).SetupWithManager(mgr) - - Expect(err).NotTo(HaveOccurred()) - authPolicyIstioAuthorizationPolicyReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("authpolicy").WithName("istioauthorizationpolicy"), @@ -199,17 +162,6 @@ func SetupKuadrantOperatorForTest(s *runtime.Scheme, cfg *rest.Config) { Expect(err).NotTo(HaveOccurred()) - policyStatusBaseReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("ratelimitpolicy").WithName("status"), - mgr.GetEventRecorderFor("RateLimitPolicyStatus"), - ) - err = (&RateLimitPolicyEnforcedStatusReconciler{ - BaseReconciler: policyStatusBaseReconciler, - }).SetupWithManager(mgr) - - Expect(err).NotTo(HaveOccurred()) - authPolicyEnvoySecurityPolicyReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("authpolicy").WithName("securitypolicy"), @@ -234,18 +186,6 @@ func SetupKuadrantOperatorForTest(s *runtime.Scheme, cfg *rest.Config) { Expect(err).NotTo(HaveOccurred()) - envoyGatewayWasmReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("envoyGatewayWasmReconciler"), - mgr.GetEventRecorderFor("EnvoyGatewayWasmReconciler"), - ) - - err = (&EnvoyGatewayWasmReconciler{ - BaseReconciler: envoyGatewayWasmReconciler, - }).SetupWithManager(mgr) - - Expect(err).NotTo(HaveOccurred()) - envoyGatewayLimitadorClusterReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("envoyGatewayLimitadorClusterReconciler"), diff --git a/go.mod b/go.mod index 6609380b1..b114b64fe 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/kuadrant/authorino-operator v0.11.1 github.com/kuadrant/dns-operator v0.0.0-20241002074817-d0cab9eecbdb github.com/kuadrant/limitador-operator v0.9.0 - github.com/kuadrant/policy-machinery v0.2.0 + github.com/kuadrant/policy-machinery v0.4.0 github.com/martinlindhe/base36 v1.1.1 github.com/onsi/ginkgo/v2 v2.20.2 github.com/onsi/gomega v1.34.1 diff --git a/go.sum b/go.sum index 75bc3ff74..986e63b15 100644 --- a/go.sum +++ b/go.sum @@ -268,6 +268,8 @@ github.com/kuadrant/limitador-operator v0.9.0 h1:hTQ6CFPayf/sL7cIzwWjCoU8uTn6fzW github.com/kuadrant/limitador-operator v0.9.0/go.mod h1:DQOlg9qFOcnWPrwO529JRCMLLOEXJQxkmOes952S/Hw= github.com/kuadrant/policy-machinery v0.2.0 h1:6kACb+bdEwHXz2tvTs6dlLgvxFgFrowvGTZKMI9p0Qo= github.com/kuadrant/policy-machinery v0.2.0/go.mod h1:ZV4xS0CCxPgu/Xg6gz+YUaS9zqEXKOiAj33bZ67B6Lo= +github.com/kuadrant/policy-machinery v0.4.0 h1:YgmlpG3U4YOrhP5oje+nMJ4GYD+UYYfoDevJ2Tjl6DA= +github.com/kuadrant/policy-machinery v0.4.0/go.mod h1:ZV4xS0CCxPgu/Xg6gz+YUaS9zqEXKOiAj33bZ67B6Lo= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= diff --git a/main.go b/main.go index 05f5ba7cc..1e9fde350 100644 --- a/main.go +++ b/main.go @@ -171,20 +171,6 @@ func main() { os.Exit(1) } - rateLimitPolicyBaseReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("ratelimitpolicy"), - mgr.GetEventRecorderFor("RateLimitPolicy"), - ) - - if err = (&controllers.RateLimitPolicyReconciler{ - TargetRefReconciler: reconcilers.TargetRefReconciler{Client: mgr.GetClient()}, - BaseReconciler: rateLimitPolicyBaseReconciler, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "RateLimitPolicy") - os.Exit(1) - } - authPolicyBaseReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("authpolicy"), @@ -229,19 +215,6 @@ func main() { os.Exit(1) } - limitadorClusterEnvoyFilterBaseReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("ratelimitpolicy").WithName("envoyfilter"), - mgr.GetEventRecorderFor("LimitadorClusterEnvoyFilter"), - ) - - if err = (&controllers.LimitadorClusterEnvoyFilterReconciler{ - BaseReconciler: limitadorClusterEnvoyFilterBaseReconciler, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "EnvoyFilter") - os.Exit(1) - } - gatewayKuadrantBaseReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("kuadrant").WithName("gateway"), @@ -255,19 +228,6 @@ func main() { os.Exit(1) } - rateLimitingIstioWASMPluginBaseReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("ratelimitpolicy").WithName("wasmplugin"), - mgr.GetEventRecorderFor("RateLimitingIstioWASMPlugin"), - ) - - if err = (&controllers.RateLimitingIstioWASMPluginReconciler{ - BaseReconciler: rateLimitingIstioWASMPluginBaseReconciler, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "RateLimitingIstioWASMPlugin") - os.Exit(1) - } - authPolicyIstioAuthorizationPolicyReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("authpolicy").WithName("istioauthorizationpolicy"), @@ -292,18 +252,6 @@ func main() { os.Exit(1) } - policyStatusBaseReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("ratelimitpolicy").WithName("status"), - mgr.GetEventRecorderFor("RateLimitPolicyStatus"), - ) - if err = (&controllers.RateLimitPolicyEnforcedStatusReconciler{ - BaseReconciler: policyStatusBaseReconciler, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "RateLimitPolicyEnforcedStatusReconciler") - os.Exit(1) - } - authPolicyEnvoySecurityPolicyReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("authpolicy").WithName("securitypolicy"), @@ -328,18 +276,6 @@ func main() { os.Exit(1) } - envoyGatewayWasmReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("envoyGatewayWasmReconciler"), - mgr.GetEventRecorderFor("EnvoyGatewayWasmReconciler"), - ) - if err = (&controllers.EnvoyGatewayWasmReconciler{ - BaseReconciler: envoyGatewayWasmReconciler, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "EnvoyGatewayWasmReconciler") - os.Exit(1) - } - envoyGatewayLimitadorClusterReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("envoyGatewayLimitadorClusterReconciler"), diff --git a/pkg/library/mappers/gateway_test.go b/pkg/library/mappers/gateway_test.go index 6de310348..91b60c6c6 100644 --- a/pkg/library/mappers/gateway_test.go +++ b/pkg/library/mappers/gateway_test.go @@ -8,7 +8,9 @@ import ( "gotest.tools/assert" appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -31,7 +33,7 @@ func TestNewGatewayEventMapper(t *testing.T) { t.Fatal(err) } - err = gatewayapiv1.AddToScheme(testScheme) + err = gatewayapiv1.Install(testScheme) if err != nil { t.Fatal(err) } @@ -63,7 +65,7 @@ func TestNewGatewayEventMapper(t *testing.T) { t.Run("not gateway related event", func(subT *testing.T) { objs := []runtime.Object{} cl := clientBuilder(objs) - em := NewGatewayEventMapper(kuadrantv1beta3.NewRateLimitPolicyType(), WithClient(cl), WithLogger(log.NewLogger())) + em := NewGatewayEventMapper(&rateLimitPolicyType{}, WithClient(cl), WithLogger(log.NewLogger())) requests := em.Map(ctx, &gatewayapiv1.HTTPRoute{}) assert.DeepEqual(subT, []reconcile.Request{}, requests) }) @@ -71,7 +73,7 @@ func TestNewGatewayEventMapper(t *testing.T) { t.Run("gateway related event - no policies - no requests", func(subT *testing.T) { objs := []runtime.Object{} cl := clientBuilder(objs) - em := NewGatewayEventMapper(kuadrantv1beta3.NewRateLimitPolicyType(), WithClient(cl), WithLogger(log.NewLogger())) + em := NewGatewayEventMapper(&rateLimitPolicyType{}, WithClient(cl), WithLogger(log.NewLogger())) requests := em.Map(ctx, &gatewayapiv1.Gateway{}) assert.DeepEqual(subT, []reconcile.Request{}, requests) }) @@ -91,7 +93,7 @@ func TestNewGatewayEventMapper(t *testing.T) { }) objs := []runtime.Object{gw, route, pGw, pRoute} cl := clientBuilder(objs) - em := NewGatewayEventMapper(kuadrantv1beta3.NewRateLimitPolicyType(), WithClient(cl), WithLogger(log.NewLogger())) + em := NewGatewayEventMapper(&rateLimitPolicyType{}, WithClient(cl), WithLogger(log.NewLogger())) requests := em.Map(ctx, gw) assert.Equal(subT, len(requests), 2) assert.Assert(subT, utils.Index(requests, func(r reconcile.Request) bool { @@ -102,3 +104,43 @@ func TestNewGatewayEventMapper(t *testing.T) { }) >= 0) }) } + +const ( + RateLimitPolicyBackReferenceAnnotationName = "kuadrant.io/ratelimitpolicies" + RateLimitPolicyDirectReferenceAnnotationName = "kuadrant.io/ratelimitpolicy" +) + +type rateLimitPolicyType struct{} + +func (r rateLimitPolicyType) GetGVK() schema.GroupVersionKind { + return schema.GroupVersionKind{ + Group: kuadrantv1beta3.GroupVersion.Group, + Version: kuadrantv1beta3.GroupVersion.Version, + Kind: "RateLimitPolicy", + } +} +func (r rateLimitPolicyType) GetInstance() client.Object { + return &kuadrantv1beta3.RateLimitPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: kuadrantv1beta3.RateLimitPolicyGroupKind.Kind, + APIVersion: kuadrantv1beta3.GroupVersion.String(), + }, + } +} + +func (r rateLimitPolicyType) BackReferenceAnnotationName() string { + return RateLimitPolicyBackReferenceAnnotationName +} + +func (r rateLimitPolicyType) DirectReferenceAnnotationName() string { + return RateLimitPolicyDirectReferenceAnnotationName +} + +func (r rateLimitPolicyType) GetList(ctx context.Context, cl client.Client, listOpts ...client.ListOption) ([]kuadrantgatewayapi.Policy, error) { + rlpList := &kuadrantv1beta3.RateLimitPolicyList{} + err := cl.List(ctx, rlpList, listOpts...) + if err != nil { + return nil, err + } + return utils.Map(rlpList.Items, func(p kuadrantv1beta3.RateLimitPolicy) kuadrantgatewayapi.Policy { return &p }), nil +} diff --git a/pkg/library/mappers/utils_test.go b/pkg/library/mappers/utils_test.go index a7fbb0b11..50ed4b541 100644 --- a/pkg/library/mappers/utils_test.go +++ b/pkg/library/mappers/utils_test.go @@ -34,6 +34,6 @@ func policyFactory(ns, name string, targetRef gatewayapiv1alpha2.LocalPolicyTarg return &kuadrantv1beta3.RateLimitPolicy{ TypeMeta: metav1.TypeMeta{Kind: "RateLimitPolicy", APIVersion: kuadrantv1beta3.GroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns}, - Spec: kuadrantv1beta3.RateLimitPolicySpec{TargetRef: targetRef}, + Spec: kuadrantv1beta3.RateLimitPolicySpec{TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{LocalPolicyTargetReference: targetRef}}, } } diff --git a/pkg/rlptools/overrides.go b/pkg/rlptools/overrides.go index f361ef16f..9a12f1ce2 100644 --- a/pkg/rlptools/overrides.go +++ b/pkg/rlptools/overrides.go @@ -46,7 +46,7 @@ func ApplyOverrides(topology *kuadrantgatewayapi.Topology, gateway *gatewayapiv1 for _, policy := range route.AttachedPolicies() { overriddenPolicy := policy.DeepCopyObject().(*kuadrantv1beta3.RateLimitPolicy) - overriddenPolicy.Spec.CommonSpec().Limits = overridePolicies[0].Spec.Overrides.Limits + overriddenPolicy.Spec.Proper().Limits = overridePolicies[0].Spec.Overrides.Limits overriddenPolicies = append(overriddenPolicies, overriddenPolicy) } } diff --git a/pkg/rlptools/utils.go b/pkg/rlptools/utils.go index f5472e695..756f51330 100644 --- a/pkg/rlptools/utils.go +++ b/pkg/rlptools/utils.go @@ -15,13 +15,6 @@ func LimitsNameFromRLP(rlp *kuadrantv1beta3.RateLimitPolicy) string { return wasm.LimitsNamespaceFromRLP(rlp) } -var timeUnitMap = map[kuadrantv1beta3.TimeUnit]int{ - kuadrantv1beta3.TimeUnit("second"): 1, - kuadrantv1beta3.TimeUnit("minute"): 60, - kuadrantv1beta3.TimeUnit("hour"): 60 * 60, - kuadrantv1beta3.TimeUnit("day"): 60 * 60 * 24, -} - // LimitadorRateLimitsFromRLP converts rate limits from a Kuadrant RateLimitPolicy into a list of Limitador rate limit // objects func LimitadorRateLimitsFromRLP(rlp *kuadrantv1beta3.RateLimitPolicy) []limitadorv1alpha1.RateLimit { @@ -29,10 +22,10 @@ func LimitadorRateLimitsFromRLP(rlp *kuadrantv1beta3.RateLimitPolicy) []limitado rlpKey := client.ObjectKeyFromObject(rlp) rateLimits := make([]limitadorv1alpha1.RateLimit, 0) - for limitKey, limit := range rlp.Spec.CommonSpec().Limits { + for limitKey, limit := range rlp.Spec.Proper().Limits { limitIdentifier := wasm.LimitNameToLimitadorIdentifier(rlpKey, limitKey) for _, rate := range limit.Rates { - maxValue, seconds := rateToSeconds(rate) + maxValue, seconds := rate.ToSeconds() rateLimits = append(rateLimits, limitadorv1alpha1.RateLimit{ Namespace: limitsNamespace, MaxValue: maxValue, @@ -45,24 +38,3 @@ func LimitadorRateLimitsFromRLP(rlp *kuadrantv1beta3.RateLimitPolicy) []limitado } return rateLimits } - -// rateToSeconds converts from RLP Rate API (limit, duration and unit) -// to Limitador's Limit format (maxValue, Seconds) -func rateToSeconds(rate kuadrantv1beta3.Rate) (maxValue int, seconds int) { - maxValue = rate.Limit - seconds = 0 - - if tmpSecs, ok := timeUnitMap[rate.Unit]; ok && rate.Duration > 0 { - seconds = tmpSecs * rate.Duration - } - - if rate.Duration < 0 { - seconds = 0 - } - - if rate.Limit < 0 { - maxValue = 0 - } - - return -} diff --git a/pkg/rlptools/utils_test.go b/pkg/rlptools/utils_test.go index 8d414847a..16f200112 100644 --- a/pkg/rlptools/utils_test.go +++ b/pkg/rlptools/utils_test.go @@ -25,7 +25,7 @@ func testRLP_1Limit_1Rate(ns, name string) *kuadrantv1beta3.RateLimitPolicy { Namespace: ns, }, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "l1": { Rates: []kuadrantv1beta3.Rate{ @@ -53,7 +53,7 @@ func testRLP_2Limits_1Rate(ns, name string) *kuadrantv1beta3.RateLimitPolicy { Namespace: ns, }, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "l1": { Rates: []kuadrantv1beta3.Rate{ @@ -90,7 +90,7 @@ func testRLP_1Limit_2Rates(ns, name string) *kuadrantv1beta3.RateLimitPolicy { Namespace: ns, }, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "l1": { Rates: []kuadrantv1beta3.Rate{ @@ -123,7 +123,7 @@ func testRLP_1Limit_1Rate_1Counter(ns, name string) *kuadrantv1beta3.RateLimitPo Namespace: ns, }, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "l1": { Counters: []kuadrantv1beta3.ContextSelector{ @@ -325,7 +325,7 @@ func TestConvertRateIntoSeconds(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(subT *testing.T) { - maxValue, seconds := rateToSeconds(tc.rate) + maxValue, seconds := tc.rate.ToSeconds() if maxValue != tc.expectedMaxValue { subT.Errorf("maxValue does not match, expected(%d), got (%d)", tc.expectedMaxValue, maxValue) } diff --git a/pkg/rlptools/wasm/utils.go b/pkg/rlptools/wasm/utils.go index dc5ce74b3..962b669b3 100644 --- a/pkg/rlptools/wasm/utils.go +++ b/pkg/rlptools/wasm/utils.go @@ -46,7 +46,7 @@ func Rules(rlp *kuadrantv1beta3.RateLimitPolicy, route *gatewayapiv1.HTTPRoute) } rlpKey := client.ObjectKeyFromObject(rlp) - limits := rlp.Spec.CommonSpec().Limits + limits := rlp.Spec.Proper().Limits // Sort RLP limits for consistent comparison with existing wasmplugin objects limitNames := lo.Keys(limits) @@ -107,6 +107,7 @@ func ruleFromLimit(rlp *kuadrantv1beta3.RateLimitPolicy, limitIdentifier string, return rule, nil } +// TODO: build conditions for a specific HTTPRouteRule, not for the entire HTTPRoute func conditionsFromLimit(limit *kuadrantv1beta3.Limit, route *gatewayapiv1.HTTPRoute) ([]Condition, error) { if limit == nil { return nil, errors.New("limit should not be nil") diff --git a/pkg/rlptools/wasm/utils_test.go b/pkg/rlptools/wasm/utils_test.go index 3ed026a9f..4da413241 100644 --- a/pkg/rlptools/wasm/utils_test.go +++ b/pkg/rlptools/wasm/utils_test.go @@ -53,7 +53,7 @@ func TestRules(t *testing.T) { Namespace: "my-app", }, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: limits, }, }, diff --git a/tests/common/ratelimitpolicy/ratelimitpolicy_controller_test.go b/tests/common/ratelimitpolicy/ratelimitpolicy_controller_test.go index 4ffb86ce9..b1c39fc35 100644 --- a/tests/common/ratelimitpolicy/ratelimitpolicy_controller_test.go +++ b/tests/common/ratelimitpolicy/ratelimitpolicy_controller_test.go @@ -51,17 +51,21 @@ var _ = Describe("RateLimitPolicy controller (Serial)", Serial, func() { Namespace: testNamespace, }, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: gatewayapiv1.ObjectName(routeName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: gatewayapiv1.ObjectName(routeName), + }, }, - Defaults: &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "l1": { - Rates: []kuadrantv1beta3.Rate{ - { - Limit: 1, Duration: 3, Unit: "minute", + Defaults: &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "l1": { + Rates: []kuadrantv1beta3.Rate{ + { + Limit: 1, Duration: 3, Unit: "minute", + }, }, }, }, @@ -170,17 +174,21 @@ var _ = Describe("RateLimitPolicy controller", func() { Namespace: testNamespace, }, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: gatewayapiv1.ObjectName(routeName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: gatewayapiv1.ObjectName(routeName), + }, }, - Defaults: &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "l1": { - Rates: []kuadrantv1beta3.Rate{ - { - Limit: 1, Duration: 3, Unit: "minute", + Defaults: &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "l1": { + Rates: []kuadrantv1beta3.Rate{ + { + Limit: 1, Duration: 3, Unit: "minute", + }, }, }, }, @@ -312,17 +320,21 @@ var _ = Describe("RateLimitPolicy controller", func() { Namespace: testNamespace, }, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.Group("gateway.networking.k8s.io"), - Kind: "Gateway", - Name: gatewayapiv1.ObjectName(TestGatewayName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.Group("gateway.networking.k8s.io"), + Kind: "Gateway", + Name: gatewayapiv1.ObjectName(TestGatewayName), + }, }, - Defaults: &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "l1": { - Rates: []kuadrantv1beta3.Rate{ - { - Limit: 1, Duration: 3, Unit: "minute", + Defaults: &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "l1": { + Rates: []kuadrantv1beta3.Rate{ + { + Limit: 1, Duration: 3, Unit: "minute", + }, }, }, }, @@ -458,7 +470,7 @@ var _ = Describe("RateLimitPolicy controller", func() { // Create HTTPRoute RLP with new default limits routeRLP = policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { policy.Name = "httproute-rlp" - policy.Spec.CommonSpec().Limits = map[string]kuadrantv1beta3.Limit{ + policy.Spec.Proper().Limits = map[string]kuadrantv1beta3.Limit{ "l1": { Rates: []kuadrantv1beta3.Rate{ { @@ -534,7 +546,7 @@ var _ = Describe("RateLimitPolicy controller", func() { gwRLP := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = gatewayapiv1.ObjectName(TestGatewayName) - policy.Spec.RateLimitPolicyCommonSpec = *policy.Spec.Defaults.DeepCopy() + policy.Spec.RateLimitPolicySpecProper = *policy.Spec.Defaults.RateLimitPolicySpecProper.DeepCopy() policy.Spec.Defaults = nil }) @@ -564,7 +576,7 @@ var _ = Describe("RateLimitPolicy controller", func() { routeRLP = policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { policy.Name = "httproute-rlp" - policy.Spec.CommonSpec().Limits = map[string]kuadrantv1beta3.Limit{ + policy.Spec.Proper().Limits = map[string]kuadrantv1beta3.Limit{ "route": { Rates: []kuadrantv1beta3.Rate{ { @@ -1316,12 +1328,14 @@ var _ = Describe("RateLimitPolicy controller", func() { policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = gatewayapiv1.ObjectName(gatewayAName) policy.Spec.Defaults = nil - policy.Spec.Overrides = &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "gw-a-1000rps": { - Rates: []kuadrantv1beta3.Rate{ - { - Limit: 1000, Duration: 1, Unit: "second", + policy.Spec.Overrides = &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "gw-a-1000rps": { + Rates: []kuadrantv1beta3.Rate{ + { + Limit: 1000, Duration: 1, Unit: "second", + }, }, }, }, @@ -1336,12 +1350,14 @@ var _ = Describe("RateLimitPolicy controller", func() { policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = gatewayapiv1.ObjectName(gatewayBName) policy.Spec.Defaults = nil - policy.Spec.Overrides = &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "gw-b-100rps": { - Rates: []kuadrantv1beta3.Rate{ - { - Limit: 100, Duration: 1, Unit: "second", + policy.Spec.Overrides = &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "gw-b-100rps": { + Rates: []kuadrantv1beta3.Rate{ + { + Limit: 100, Duration: 1, Unit: "second", + }, }, }, }, @@ -1355,7 +1371,7 @@ var _ = Describe("RateLimitPolicy controller", func() { policy.ObjectMeta.Name = targetedRouteName policy.Spec.TargetRef.Kind = "HTTPRoute" policy.Spec.TargetRef.Name = gatewayapiv1.ObjectName(targetedRouteName) - policy.Spec.CommonSpec().Limits = map[string]kuadrantv1beta3.Limit{ + policy.Spec.Proper().Limits = map[string]kuadrantv1beta3.Limit{ "route-10rps": { Rates: []kuadrantv1beta3.Rate{ { @@ -1430,10 +1446,12 @@ var _ = Describe("RateLimitPolicy CEL Validations", func() { Namespace: testNamespace, }, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: "my-target", + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: "my-target", + }, }, }, } @@ -1492,10 +1510,12 @@ var _ = Describe("RateLimitPolicy CEL Validations", func() { It("Valid - only explicit defaults defined", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { - policy.Spec.Defaults = &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "explicit": { - Rates: []kuadrantv1beta3.Rate{{Limit: 1, Duration: 10, Unit: "second"}}, + policy.Spec.Defaults = &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "explicit": { + Rates: []kuadrantv1beta3.Rate{{Limit: 1, Duration: 10, Unit: "second"}}, + }, }, }, } @@ -1505,10 +1525,12 @@ var _ = Describe("RateLimitPolicy CEL Validations", func() { It("Invalid - implicit and explicit defaults are mutually exclusive", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { - policy.Spec.Defaults = &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "explicit": { - Rates: []kuadrantv1beta3.Rate{{Limit: 1, Duration: 10, Unit: "second"}}, + policy.Spec.Defaults = &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "explicit": { + Rates: []kuadrantv1beta3.Rate{{Limit: 1, Duration: 10, Unit: "second"}}, + }, }, }, } @@ -1525,17 +1547,21 @@ var _ = Describe("RateLimitPolicy CEL Validations", func() { It("Invalid - explicit default and override defined", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { - policy.Spec.Defaults = &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "implicit": { - Rates: []kuadrantv1beta3.Rate{{Limit: 2, Duration: 20, Unit: "second"}}, + policy.Spec.Defaults = &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "implicit": { + Rates: []kuadrantv1beta3.Rate{{Limit: 2, Duration: 20, Unit: "second"}}, + }, }, }, } - policy.Spec.Overrides = &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "explicit": { - Rates: []kuadrantv1beta3.Rate{{Limit: 1, Duration: 10, Unit: "second"}}, + policy.Spec.Overrides = &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "explicit": { + Rates: []kuadrantv1beta3.Rate{{Limit: 1, Duration: 10, Unit: "second"}}, + }, }, }, } @@ -1552,10 +1578,12 @@ var _ = Describe("RateLimitPolicy CEL Validations", func() { Rates: []kuadrantv1beta3.Rate{{Limit: 2, Duration: 20, Unit: "second"}}, }, } - policy.Spec.Overrides = &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "overrides": { - Rates: []kuadrantv1beta3.Rate{{Limit: 1, Duration: 10, Unit: "second"}}, + policy.Spec.Overrides = &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "overrides": { + Rates: []kuadrantv1beta3.Rate{{Limit: 1, Duration: 10, Unit: "second"}}, + }, }, }, } @@ -1567,10 +1595,12 @@ var _ = Describe("RateLimitPolicy CEL Validations", func() { It("Invalid - policy override targeting resource other than Gateway", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { - policy.Spec.Overrides = &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "implicit": { - Rates: []kuadrantv1beta3.Rate{{Limit: 1, Duration: 10, Unit: "second"}}, + policy.Spec.Overrides = &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "implicit": { + Rates: []kuadrantv1beta3.Rate{{Limit: 1, Duration: 10, Unit: "second"}}, + }, }, }, } @@ -1583,10 +1613,12 @@ var _ = Describe("RateLimitPolicy CEL Validations", func() { It("Valid - policy override targeting Gateway", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { policy.Spec.TargetRef.Kind = "Gateway" - policy.Spec.Overrides = &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "override": { - Rates: []kuadrantv1beta3.Rate{{Limit: 1, Duration: 10, Unit: "second"}}, + policy.Spec.Overrides = &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "override": { + Rates: []kuadrantv1beta3.Rate{{Limit: 1, Duration: 10, Unit: "second"}}, + }, }, }, } diff --git a/tests/common/targetstatus/target_status_controller_test.go b/tests/common/targetstatus/target_status_controller_test.go index 7ce33de85..fd44390c9 100644 --- a/tests/common/targetstatus/target_status_controller_test.go +++ b/tests/common/targetstatus/target_status_controller_test.go @@ -340,17 +340,21 @@ var _ = Describe("Target status reconciler", func() { Namespace: testNamespace, }, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: gatewayapiv1.ObjectName(TestHTTPRouteName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: gatewayapiv1.ObjectName(TestHTTPRouteName), + }, }, - Defaults: &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "l1": { - Rates: []kuadrantv1beta3.Rate{ - { - Limit: 1, Duration: 3, Unit: kuadrantv1beta3.TimeUnit("minute"), + Defaults: &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "l1": { + Rates: []kuadrantv1beta3.Rate{ + { + Limit: 1, Duration: 3, Unit: kuadrantv1beta3.TimeUnit("minute"), + }, }, }, }, @@ -372,7 +376,7 @@ var _ = Describe("Target status reconciler", func() { if !tests.RLPIsAccepted(ctx, testClient(), policyKey)() { return false } - return targetsAffected(ctx, policyKey, policyAffectedCondition, policy.Spec.TargetRef, routeNames...) + return targetsAffected(ctx, policyKey, policyAffectedCondition, policy.Spec.TargetRef.LocalPolicyTargetReference, routeNames...) } } @@ -403,10 +407,12 @@ var _ = Describe("Target status reconciler", func() { It("adds PolicyAffected status condition to the targeted gateway and routes", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { policy.Name = "gateway-rlp" - policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: TestGatewayName, + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, } }) Expect(k8sClient.Create(ctx, policy)).To(Succeed()) @@ -416,10 +422,12 @@ var _ = Describe("Target status reconciler", func() { It("removes PolicyAffected status condition from the targeted gateway and routes when the policy is deleted", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { policy.Name = "gateway-rlp" - policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: TestGatewayName, + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, } }) Expect(k8sClient.Create(ctx, policy)).To(Succeed()) @@ -455,10 +463,12 @@ var _ = Describe("Target status reconciler", func() { gatewayPolicy := policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { policy.Name = "gateway-rlp" - policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: TestGatewayName, + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, } }) Expect(k8sClient.Create(ctx, gatewayPolicy)).To(Succeed()) diff --git a/tests/envoygateway/envoygateway_limitador_cluster_controller_test.go b/tests/envoygateway/envoygateway_limitador_cluster_controller_test.go index aad9dab0a..cb382b1ac 100644 --- a/tests/envoygateway/envoygateway_limitador_cluster_controller_test.go +++ b/tests/envoygateway/envoygateway_limitador_cluster_controller_test.go @@ -103,12 +103,14 @@ var _ = Describe("limitador cluster controller", func() { policy.Spec.TargetRef.Group = gatewayapiv1.GroupName policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = TestGatewayName - policy.Spec.Defaults = &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "l1": { - Rates: []kuadrantv1beta3.Rate{ - { - Limit: 1, Duration: 3, Unit: "minute", + policy.Spec.Defaults = &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "l1": { + Rates: []kuadrantv1beta3.Rate{ + { + Limit: 1, Duration: 3, Unit: "minute", + }, }, }, }, diff --git a/tests/envoygateway/wasm_controller_test.go b/tests/envoygateway/wasm_controller_test.go index c786d71a3..d4275d7ef 100644 --- a/tests/envoygateway/wasm_controller_test.go +++ b/tests/envoygateway/wasm_controller_test.go @@ -92,12 +92,14 @@ var _ = Describe("wasm controller", func() { policy.Spec.TargetRef.Group = gatewayapiv1.GroupName policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = TestGatewayName - policy.Spec.Defaults = &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "l1": { - Rates: []kuadrantv1beta3.Rate{ - { - Limit: 1, Duration: 3, Unit: "minute", + policy.Spec.Defaults = &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "l1": { + Rates: []kuadrantv1beta3.Rate{ + { + Limit: 1, Duration: 3, Unit: "minute", + }, }, }, }, @@ -252,12 +254,14 @@ var _ = Describe("wasm controller", func() { policy.Spec.TargetRef.Group = gatewayapiv1.GroupName policy.Spec.TargetRef.Kind = "HTTPRoute" policy.Spec.TargetRef.Name = TestHTTPRouteName - policy.Spec.Defaults = &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "l1": { - Rates: []kuadrantv1beta3.Rate{ - { - Limit: 1, Duration: 3, Unit: "minute", + policy.Spec.Defaults = &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "l1": { + Rates: []kuadrantv1beta3.Rate{ + { + Limit: 1, Duration: 3, Unit: "minute", + }, }, }, }, diff --git a/tests/istio/limitador_cluster_envoyfilter_controller_test.go b/tests/istio/limitador_cluster_envoyfilter_controller_test.go index 6effe5fec..7527e2d72 100644 --- a/tests/istio/limitador_cluster_envoyfilter_controller_test.go +++ b/tests/istio/limitador_cluster_envoyfilter_controller_test.go @@ -89,12 +89,14 @@ var _ = Describe("Limitador Cluster EnvoyFilter controller", func() { Namespace: testNamespace, }, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: gatewayapiv1.ObjectName(TestGatewayName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: gatewayapiv1.ObjectName(TestGatewayName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "l1": { Rates: []kuadrantv1beta3.Rate{ diff --git a/tests/istio/rate_limiting_istio_wasmplugin_controller_test.go b/tests/istio/rate_limiting_istio_wasmplugin_controller_test.go index 4fbd6c8dd..4a008e088 100644 --- a/tests/istio/rate_limiting_istio_wasmplugin_controller_test.go +++ b/tests/istio/rate_limiting_istio_wasmplugin_controller_test.go @@ -87,12 +87,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: rlpName, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: gatewayapiv1.ObjectName(routeName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: gatewayapiv1.ObjectName(routeName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "l1": { Rates: []kuadrantv1beta3.Rate{ @@ -227,12 +229,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { Namespace: testNamespace, }, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: gatewayapiv1.ObjectName(routeName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: gatewayapiv1.ObjectName(routeName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "users": { Rates: []kuadrantv1beta3.Rate{ @@ -434,12 +438,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: rlpName, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: gatewayapiv1.ObjectName(TestGatewayName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: gatewayapiv1.ObjectName(TestGatewayName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "l1": { Rates: []kuadrantv1beta3.Rate{ @@ -546,12 +552,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: rlpName, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: gatewayapiv1.ObjectName(TestGatewayName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: gatewayapiv1.ObjectName(TestGatewayName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "l1": { Rates: []kuadrantv1beta3.Rate{ @@ -623,12 +631,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: rlpName, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: gatewayapiv1.ObjectName(TestGatewayName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: gatewayapiv1.ObjectName(TestGatewayName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "l1": { Rates: []kuadrantv1beta3.Rate{ @@ -844,12 +854,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: rlpName, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: gatewayapiv1.ObjectName(routeName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: gatewayapiv1.ObjectName(routeName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "l1": { Rates: []kuadrantv1beta3.Rate{ @@ -1165,12 +1177,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: rlpName, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: gatewayapiv1.ObjectName(routeAName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: gatewayapiv1.ObjectName(routeAName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "l1": { Rates: []kuadrantv1beta3.Rate{ @@ -1424,12 +1438,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: rlp1Name, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: gatewayapiv1.ObjectName(TestGatewayName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: gatewayapiv1.ObjectName(TestGatewayName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "gatewaylimit": { Rates: []kuadrantv1beta3.Rate{ @@ -1540,12 +1556,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: rlp2Name, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: gatewayapiv1.ObjectName(routeAName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: gatewayapiv1.ObjectName(routeAName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "routelimit": { Rates: []kuadrantv1beta3.Rate{ @@ -1711,12 +1729,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: rlp1Name, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: gatewayapiv1.ObjectName(TestGatewayName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: gatewayapiv1.ObjectName(TestGatewayName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "gatewaylimit": { Rates: []kuadrantv1beta3.Rate{ @@ -1742,12 +1762,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: rlp2Name, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: gatewayapiv1.ObjectName(routeAName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: gatewayapiv1.ObjectName(routeAName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "routelimit": { Rates: []kuadrantv1beta3.Rate{ @@ -2029,12 +2051,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: rlpName, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: gatewayapiv1.ObjectName(routeName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: gatewayapiv1.ObjectName(routeName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "l1": { Rates: []kuadrantv1beta3.Rate{ @@ -2202,17 +2226,21 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: gwRLPName, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: gatewayapiv1.ObjectName(TestGatewayName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: gatewayapiv1.ObjectName(TestGatewayName), + }, }, - Defaults: &kuadrantv1beta3.RateLimitPolicyCommonSpec{ - Limits: map[string]kuadrantv1beta3.Limit{ - "gateway": { - Rates: []kuadrantv1beta3.Rate{ - { - Limit: 1, Duration: 3, Unit: kuadrantv1beta3.TimeUnit("minute"), + Defaults: &kuadrantv1beta3.MergeableRateLimitPolicySpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ + Limits: map[string]kuadrantv1beta3.Limit{ + "gateway": { + Rates: []kuadrantv1beta3.Rate{ + { + Limit: 1, Duration: 3, Unit: kuadrantv1beta3.TimeUnit("minute"), + }, }, }, }, @@ -2243,12 +2271,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, ObjectMeta: metav1.ObjectMeta{Name: routeRLPName, Namespace: testNamespace}, Spec: kuadrantv1beta3.RateLimitPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: gatewayapiv1.ObjectName(routeName), + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: gatewayapiv1.ObjectName(routeName), + }, }, - RateLimitPolicyCommonSpec: kuadrantv1beta3.RateLimitPolicyCommonSpec{ + RateLimitPolicySpecProper: kuadrantv1beta3.RateLimitPolicySpecProper{ Limits: map[string]kuadrantv1beta3.Limit{ "route": { Rates: []kuadrantv1beta3.Rate{