Skip to content

Commit

Permalink
sotw: rlp workflow (#893)
Browse files Browse the repository at this point in the history
* sotw: effective ratelimitpolicy

- ratelimit workflow
- ratelimitopolicies validator
- effective ratelimitpolicies reconciler
- limitador limits reconciler
- ratelimitpolicy status updater (accepted condition)

Signed-off-by: Guilherme Cassolato <[email protected]>

* removed unused function to apply rlp overrides

Signed-off-by: Guilherme Cassolato <[email protected]>

* istio extension (WasmPlugin) reconciler

Signed-off-by: Guilherme Cassolato <[email protected]>

* fixup: unique limit definitions per scope

Signed-off-by: Guilherme Cassolato <[email protected]>

* fixup: log error only when indeed there's an error to be logged

Signed-off-by: Guilherme Cassolato <[email protected]>

* do not fail when missing kuadrant object

Signed-off-by: Guilherme Cassolato <[email protected]>

* fixup: equality between wasmplugins and avoid rebuilding wasm config from struct when nil

Signed-off-by: Guilherme Cassolato <[email protected]>

* fixup: error comparison

Signed-off-by: Guilherme Cassolato <[email protected]>

* cleanup istio extension objects when it cannot calculate effective policies

Signed-off-by: Guilherme Cassolato <[email protected]>

* refactor: removal of SortablePolicy for sorting policies objects by creation timestamp

Signed-off-by: Guilherme Cassolato <[email protected]>

* remove no longer relevant integration test case

Signed-off-by: Guilherme Cassolato <[email protected]>

* fixup: avoid updating invalid rate limit policies to 'accepted' on events that do not (re)validate policies

Signed-off-by: Guilherme Cassolato <[email protected]>

* fixup: continue istio extension workflow when it fails for a given gateway

Signed-off-by: Guilherme Cassolato <[email protected]>

* Remove unnused event recorder from base reconciler

Signed-off-by: Guilherme Cassolato <[email protected]>

* Istio rate limit cluster reconciler

Signed-off-by: Guilherme Cassolato <[email protected]>

* enable Istio-related rate limit tasks only when Istio is installed

Signed-off-by: Guilherme Cassolato <[email protected]>

* ensure at least one hostname per wasm config policy

Signed-off-by: Guilherme Cassolato <[email protected]>

* bump istio to 1.22

Signed-off-by: Guilherme Cassolato <[email protected]>

* Use targetRefs to attach to gateways in the Istio EnvoyFilter and WasmPlugin resources

Signed-off-by: Guilherme Cassolato <[email protected]>

* refactor: debug log messages for when Limitador, EnvoyFilter and WasmPlugins are up to date already and therefore nothing to be done

Signed-off-by: Guilherme Cassolato <[email protected]>

* sort wasm 'policies' within the wasm plugin config by hostname from most specific to least specific

Signed-off-by: Guilherme Cassolato <[email protected]>

* go fmt: refactor: debug log messages for when Limitador, EnvoyFilter and WasmPlugin

Signed-off-by: Guilherme Cassolato <[email protected]>

* sort wasm 'policies' within the wasm plugin config by hostname and http route match from most specific to least specific

Signed-off-by: Guilherme Cassolato <[email protected]>

* code style: remove unused parameter ctx

Signed-off-by: Guilherme Cassolato <[email protected]>

* fix: unit test: sort wasm 'policies' within the wasm plugin config by hostname and http route match

Signed-off-by: Guilherme Cassolato <[email protected]>

* Refactor WasmPlugin reconciliation to reduce repetition with EnvoyExtensionPolicy and ease the merge of auth

* Separate the code for building Wasm Configs from any logic specific to the Istio WasmPlugin resource
* Move all generic Wasm-related code either upwards to a common file of the workflow tasks (in the `controllers` package) into new package `pkg/wasm` (replacing `pkg/rlptools/wasm`)
  * Logic related to RL reconciliation →  controllers/ratelimit_workflow.go
  * Logic related to Wasm Config types →  pkg/wasm
* Rename `rlptools` package as `ratelimit` – only Limitador RateLimit index types remaining there

Signed-off-by: Guilherme Cassolato <[email protected]>

* envoy gateway rate limit cluster reconciler

Signed-off-by: Guilherme Cassolato <[email protected]>

* fix: wrong default GroupKind assumed on Istio policies TargetRefs

Signed-off-by: Guilherme Cassolato <[email protected]>

* minor fixes to code comments and log messages

Signed-off-by: Guilherme Cassolato <[email protected]>

* envoy gateway extension reconciler

Signed-off-by: Guilherme Cassolato <[email protected]>

* code style: ratelimit.RateLimitIndex -> Index

Signed-off-by: Guilherme Cassolato <[email protected]>

* code style: wasm.WasmExtensionName -> ExtensionName

Signed-off-by: Guilherme Cassolato <[email protected]>

* code style: wasm.WasmRuleBuilderFunc -> RuleBuilderFunc

Signed-off-by: Guilherme Cassolato <[email protected]>

* code style: fix grouping of go imports

Signed-off-by: Guilherme Cassolato <[email protected]>

* new structure of the wasm config based on Kuadrant/wasm-shim#110

Signed-off-by: Guilherme Cassolato <[email protected]>

* fix: envoy_gateway_extension_reconciler.go file name

Signed-off-by: Guilherme Cassolato <[email protected]>

* fix: log messages of envoy patch policy reconciliation

Signed-off-by: Guilherme Cassolato <[email protected]>

* rlp enforced condition and consistency in the message when target not found

Signed-off-by: Guilherme Cassolato <[email protected]>

* tests: gateway extension reconciler tests resource comparison

Signed-off-by: Guilherme Cassolato <[email protected]>

* more descriptive wasm actionset names

Signed-off-by: Guilherme Cassolato <[email protected]>

* tests: fix integration tests - order of wasm action sets

Signed-off-by: Guilherme Cassolato <[email protected]>

* fixup: should not increment twice on the index of http route match when building the wasm action set name

Signed-off-by: Guilherme Cassolato <[email protected]>

* tests: fix integration tests - order of wasm action sets (again!)

Signed-off-by: Guilherme Cassolato <[email protected]>

* back to opaque wasm action set names

Signed-off-by: Guilherme Cassolato <[email protected]>

* remove duplicate GetKuadrantFromTopology func

Signed-off-by: Guilherme Cassolato <[email protected]>

* do not isolate policy-machinery imports in a separate group

Signed-off-by: Guilherme Cassolato <[email protected]>

* helper function to extract network objects in a request path

Signed-off-by: Guilherme Cassolato <[email protected]>

* set owner reference directly when building the desired objects

Signed-off-by: Guilherme Cassolato <[email protected]>

* only validate policies on create and update events

Signed-off-by: Guilherme Cassolato <[email protected]>

* update policy status on all kinds of policy-releated event (deleting a policy may also affect the state of the resources status depends on)

Signed-off-by: Guilherme Cassolato <[email protected]>

* use labels on the internal resources created to watch and select them from topology

Signed-off-by: Guilherme Cassolato <[email protected]>

* policy not enforced due to 'not in the path to any existing routes' (formerly reported as 'no free routes to enforce policy')

Signed-off-by: Guilherme Cassolato <[email protected]>

* tests: fix integration tests - Istio EnvoyFilter only created if RLP is in the path to a route

Signed-off-by: Guilherme Cassolato <[email protected]>

* lint: removed unnused func arg in the tests

Signed-off-by: Guilherme Cassolato <[email protected]>

* tests: fix integration tests - Istio WasmPlugin config with correct limit IDs and scopes

Signed-off-by: Guilherme Cassolato <[email protected]>

* check state of the targeted resources to define the policy's enforced status condition

Signed-off-by: Guilherme Cassolato <[email protected]>

* re-enable RateLimitPolicy discoverability (PolicyAffected status condition)

Signed-off-by: Guilherme Cassolato <[email protected]>

* tests: fix integration tests - common ratelimit workflow tests

Signed-off-by: Guilherme Cassolato <[email protected]>

* refactor: common.NamespacedNameFromLocator func

Signed-off-by: Guilherme Cassolato <[email protected]>

* hack to isolate test namespaces sharing the same limitador cr

Signed-off-by: Guilherme Cassolato <[email protected]>

---------

Signed-off-by: Guilherme Cassolato <[email protected]>
  • Loading branch information
guicassolato authored Oct 22, 2024
1 parent d981b16 commit 9d0598f
Show file tree
Hide file tree
Showing 71 changed files with 5,770 additions and 5,825 deletions.
200 changes: 200 additions & 0 deletions api/v1/merge_strategies.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
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 (
"sort"
"strings"

"github.com/kuadrant/policy-machinery/controller"
"github.com/kuadrant/policy-machinery/machinery"
"github.com/samber/lo"
"k8s.io/apimachinery/pkg/runtime"
k8stypes "k8s.io/apimachinery/pkg/types"
)

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
}

// 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) (controller.Object, bool) {
o, object := policy.(controller.Object)
return o, object && predicate(policy)
})
sort.Sort(controller.ObjectsByCreationTimestamp(policies))
return lo.Map(policies, func(policy controller.Object, _ int) machinery.Policy {
p, _ := policy.(machinery.Policy)
return p
})
})
}

func PathID(path []machinery.Targetable) string {
return strings.Join(lo.Map(path, func(t machinery.Targetable, _ int) string {
return strings.TrimPrefix(k8stypes.NamespacedName{Namespace: t.GetNamespace(), Name: t.GetName()}.String(), string(k8stypes.Separator))
}), "|")
}
32 changes: 21 additions & 11 deletions api/v1beta3/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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()
}
Loading

0 comments on commit 9d0598f

Please sign in to comment.