Skip to content

Commit

Permalink
envoy gateway rate limit cluster reconciler
Browse files Browse the repository at this point in the history
Signed-off-by: Guilherme Cassolato <[email protected]>
  • Loading branch information
guicassolato committed Oct 14, 2024
1 parent 71a3b19 commit 83d1fd5
Show file tree
Hide file tree
Showing 11 changed files with 368 additions and 351 deletions.
234 changes: 234 additions & 0 deletions controllers/envoy_gateway_rate_limit_cluster_reconciler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package controllers

import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"

envoygatewayv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1"
limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1"
"github.com/samber/lo"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8stypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
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"
"github.com/kuadrant/kuadrant-operator/pkg/common"
kuadrantenvoygateway "github.com/kuadrant/kuadrant-operator/pkg/envoygateway"
"github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers"
)

// envoyGatewayRateLimitClusterReconciler reconciles EnvoyGateway EnvoyPatchPolicy custom resources
type envoyGatewayRateLimitClusterReconciler struct {
*reconcilers.BaseReconciler
client *dynamic.DynamicClient
}

func (r *envoyGatewayRateLimitClusterReconciler) Subscription() controller.Subscription {
return controller.Subscription{
ReconcileFunc: r.Reconcile,
Events: []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: &kuadrantenvoygateway.EnvoyPatchPolicyGroupKind},
},

Check warning on line 45 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L35-L45

Added lines #L35 - L45 were not covered by tests
}
}

func (r *envoyGatewayRateLimitClusterReconciler) Reconcile(ctx context.Context, _ []controller.ResourceEvent, topology *machinery.Topology, _ error, state *sync.Map) error {
logger := controller.LoggerFromContext(ctx).WithName("envoyGatewayRateLimitClusterReconciler")

Check warning on line 50 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L49-L50

Added lines #L49 - L50 were not covered by tests

logger.V(1).Info("building envoy gateway rate limit clusters")
defer logger.V(1).Info("finished building envoy gateway rate limit clusters")

Check warning on line 53 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L52-L53

Added lines #L52 - L53 were not covered by tests

kuadrant, err := GetKuadrantFromTopology(topology)
if err != nil {
if errors.Is(err, ErrMissingKuadrant) {
logger.V(1).Info(err.Error())
return nil

Check warning on line 59 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L55-L59

Added lines #L55 - L59 were not covered by tests
}
return err

Check warning on line 61 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L61

Added line #L61 was not covered by tests
}

limitadorObj, found := lo.Find(topology.Objects().Children(kuadrant), func(child machinery.Object) bool {
return child.GroupVersionKind().GroupKind() == kuadrantv1beta1.LimitadorGroupKind
})
if !found {
logger.V(1).Info(ErrMissingLimitador.Error())
return nil

Check warning on line 69 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L64-L69

Added lines #L64 - L69 were not covered by tests
}
limitador := limitadorObj.(*controller.RuntimeObject).Object.(*limitadorv1alpha1.Limitador)

Check warning on line 71 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L71

Added line #L71 was not covered by tests

effectivePolicies, ok := state.Load(StateEffectiveRateLimitPolicies)
if !ok {
logger.Error(ErrMissingStateEffectiveRateLimitPolicies, "failed to get effective rate limit policies from state")
return nil

Check warning on line 76 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L73-L76

Added lines #L73 - L76 were not covered by tests
}

gateways := lo.UniqBy(lo.FilterMap(lo.Values(effectivePolicies.(EffectiveRateLimitPolicies)), func(effectivePolicy EffectiveRateLimitPolicy, _ int) (*machinery.Gateway, bool) {

Check warning on line 79 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L79

Added line #L79 was not covered by tests
// assumes the path is always [gatewayclass, gateway, listener, httproute, httprouterule]
gatewayClass, _ := effectivePolicy.Path[0].(*machinery.GatewayClass)
gateway, _ := effectivePolicy.Path[1].(*machinery.Gateway)
return gateway, gatewayClass.Spec.ControllerName == envoyGatewayGatewayControllerName
}), func(gateway *machinery.Gateway) string {
return gateway.GetLocator()
})

Check warning on line 86 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L81-L86

Added lines #L81 - L86 were not covered by tests

desiredEnvoyPatchPolicies := make(map[k8stypes.NamespacedName]struct{})

Check warning on line 88 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L88

Added line #L88 was not covered by tests

// reconcile envoy gateway cluster for gateway
for _, gateway := range gateways {
gatewayKey := k8stypes.NamespacedName{Name: gateway.GetName(), Namespace: gateway.GetNamespace()}

Check warning on line 92 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L91-L92

Added lines #L91 - L92 were not covered by tests

desiredEnvoyPatchPolicy, err := r.buildDesiredEnvoyPatchPolicy(limitador, gateway)
if err != nil {
logger.Error(err, "failed to build desired envoy filter")
continue

Check warning on line 97 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L94-L97

Added lines #L94 - L97 were not covered by tests
}
desiredEnvoyPatchPolicies[k8stypes.NamespacedName{Name: desiredEnvoyPatchPolicy.GetName(), Namespace: desiredEnvoyPatchPolicy.GetNamespace()}] = struct{}{}

Check warning on line 99 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L99

Added line #L99 was not covered by tests

resource := r.client.Resource(kuadrantenvoygateway.EnvoyPatchPoliciesResource).Namespace(desiredEnvoyPatchPolicy.GetNamespace())

Check warning on line 101 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L101

Added line #L101 was not covered by tests

existingEnvoyPatchPolicyObj, found := lo.Find(topology.Objects().Children(gateway), func(child machinery.Object) bool {
return child.GroupVersionKind().GroupKind() == kuadrantenvoygateway.EnvoyPatchPolicyGroupKind && child.GetName() == desiredEnvoyPatchPolicy.GetName() && child.GetNamespace() == desiredEnvoyPatchPolicy.GetNamespace()
})

Check warning on line 105 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L103-L105

Added lines #L103 - L105 were not covered by tests

// create
if !found {
desiredEnvoyPatchPolicyUnstructured, err := controller.Destruct(desiredEnvoyPatchPolicy)
if err != nil {
logger.Error(err, "failed to destruct envoyfilter object", "gateway", gatewayKey.String(), "envoyfilter", desiredEnvoyPatchPolicy)
continue

Check warning on line 112 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L108-L112

Added lines #L108 - L112 were not covered by tests
}
if _, err = resource.Create(ctx, desiredEnvoyPatchPolicyUnstructured, metav1.CreateOptions{}); err != nil {
logger.Error(err, "failed to create envoyfilter object", "gateway", gatewayKey.String(), "envoyfilter", desiredEnvoyPatchPolicyUnstructured.Object)

Check warning on line 115 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L114-L115

Added lines #L114 - L115 were not covered by tests
// TODO: handle error
}
continue

Check warning on line 118 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L118

Added line #L118 was not covered by tests
}

existingEnvoyPatchPolicy := existingEnvoyPatchPolicyObj.(*controller.RuntimeObject).Object.(*envoygatewayv1alpha1.EnvoyPatchPolicy)

Check warning on line 121 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L121

Added line #L121 was not covered by tests

if equalEnvoyPatchPolicies(existingEnvoyPatchPolicy, desiredEnvoyPatchPolicy) {
logger.V(1).Info("envoyfilter object is up to date, nothing to do")
continue

Check warning on line 125 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L123-L125

Added lines #L123 - L125 were not covered by tests
}

// update
existingEnvoyPatchPolicy.Spec = envoygatewayv1alpha1.EnvoyPatchPolicySpec{
TargetRef: desiredEnvoyPatchPolicy.Spec.TargetRef,
Type: desiredEnvoyPatchPolicy.Spec.Type,
JSONPatches: desiredEnvoyPatchPolicy.Spec.JSONPatches,
Priority: desiredEnvoyPatchPolicy.Spec.Priority,

Check warning on line 133 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L129-L133

Added lines #L129 - L133 were not covered by tests
}

existingEnvoyPatchPolicyUnstructured, err := controller.Destruct(existingEnvoyPatchPolicy)
if err != nil {
logger.Error(err, "failed to destruct envoyfilter object", "gateway", gatewayKey.String(), "envoyfilter", existingEnvoyPatchPolicy)
continue

Check warning on line 139 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L136-L139

Added lines #L136 - L139 were not covered by tests
}
if _, err = resource.Update(ctx, existingEnvoyPatchPolicyUnstructured, metav1.UpdateOptions{}); err != nil {
logger.Error(err, "failed to update envoyfilter object", "gateway", gatewayKey.String(), "envoyfilter", existingEnvoyPatchPolicyUnstructured.Object)

Check warning on line 142 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L141-L142

Added lines #L141 - L142 were not covered by tests
// TODO: handle error
}
}

// cleanup envoy gateway clusters for gateways that are not in the effective policies
staleEnvoyPatchPolicies := topology.Objects().Items(func(o machinery.Object) bool {
_, desired := desiredEnvoyPatchPolicies[k8stypes.NamespacedName{Name: o.GetName(), Namespace: o.GetNamespace()}]
return o.GroupVersionKind().GroupKind() == kuadrantenvoygateway.EnvoyPatchPolicyGroupKind && !desired
})

Check warning on line 151 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L148-L151

Added lines #L148 - L151 were not covered by tests

for _, envoyPatchPolicy := range staleEnvoyPatchPolicies {
if err := r.client.Resource(kuadrantenvoygateway.EnvoyPatchPoliciesResource).Namespace(envoyPatchPolicy.GetNamespace()).Delete(ctx, envoyPatchPolicy.GetName(), metav1.DeleteOptions{}); err != nil {
logger.Error(err, "failed to delete envoyfilter object", "envoyfilter", fmt.Sprintf("%s/%s", envoyPatchPolicy.GetNamespace(), envoyPatchPolicy.GetName()))

Check warning on line 155 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L153-L155

Added lines #L153 - L155 were not covered by tests
// TODO: handle error
}
}

return nil

Check warning on line 160 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L160

Added line #L160 was not covered by tests
}

func (r *envoyGatewayRateLimitClusterReconciler) buildDesiredEnvoyPatchPolicy(limitador *limitadorv1alpha1.Limitador, gateway *machinery.Gateway) (*envoygatewayv1alpha1.EnvoyPatchPolicy, error) {
envoyPatchPolicy := &envoygatewayv1alpha1.EnvoyPatchPolicy{
TypeMeta: metav1.TypeMeta{
Kind: kuadrantenvoygateway.EnvoyPatchPolicyGroupKind.Kind,
APIVersion: envoygatewayv1alpha1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: RateLimitClusterName(gateway.GetName()),
Namespace: gateway.GetNamespace(),
Labels: map[string]string{rateLimitClusterLabelKey: "true"},
},
Spec: envoygatewayv1alpha1.EnvoyPatchPolicySpec{
TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{
Group: gatewayapiv1alpha2.Group(machinery.GatewayGroupKind.Group),
Kind: gatewayapiv1alpha2.Kind(machinery.GatewayGroupKind.Kind),
Name: gatewayapiv1alpha2.ObjectName(gateway.GetName()),
},
Type: envoygatewayv1alpha1.JSONPatchEnvoyPatchType,
},

Check warning on line 181 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L163-L181

Added lines #L163 - L181 were not covered by tests
}

jsonPatches, err := envoyGatewayEnvoyPatchPolicyClusterPatch(limitador.Status.Service.Host, int(limitador.Status.Service.Ports.GRPC))
if err != nil {
return nil, err

Check warning on line 186 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L184-L186

Added lines #L184 - L186 were not covered by tests
}
envoyPatchPolicy.Spec.JSONPatches = jsonPatches

Check warning on line 188 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L188

Added line #L188 was not covered by tests

if err := r.SetOwnerReference(gateway.Gateway, envoyPatchPolicy); err != nil {
return nil, err

Check warning on line 191 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L190-L191

Added lines #L190 - L191 were not covered by tests
}

return envoyPatchPolicy, nil

Check warning on line 194 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L194

Added line #L194 was not covered by tests
}

// envoyGatewayEnvoyPatchPolicyClusterPatch returns a set envoy config patch that defines the rate limit cluster for the gateway.
// The rate limit cluster configures the endpoint of the external rate limit service.
func envoyGatewayEnvoyPatchPolicyClusterPatch(host string, port int) ([]envoygatewayv1alpha1.EnvoyJSONPatchConfig, error) {
patchRaw, _ := json.Marshal(rateLimitClusterPatch(host, port))
patch := &apiextensionsv1.JSON{}
if err := patch.UnmarshalJSON(patchRaw); err != nil {
return nil, err

Check warning on line 203 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L199-L203

Added lines #L199 - L203 were not covered by tests
}

return []envoygatewayv1alpha1.EnvoyJSONPatchConfig{

Check warning on line 206 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L206

Added line #L206 was not covered by tests
{
Type: envoygatewayv1alpha1.ClusterEnvoyResourceType,
Name: common.KuadrantRateLimitClusterName,
Operation: envoygatewayv1alpha1.JSONPatchOperation{
Op: envoygatewayv1alpha1.JSONPatchOperationType("add"),
Path: "",
Value: patch,
},
},
}, nil

Check warning on line 216 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L208-L216

Added lines #L208 - L216 were not covered by tests
}

func equalEnvoyPatchPolicies(a, b *envoygatewayv1alpha1.EnvoyPatchPolicy) bool {
if a.Spec.Priority != b.Spec.Priority || a.Spec.TargetRef != b.Spec.TargetRef {
return false

Check warning on line 221 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L219-L221

Added lines #L219 - L221 were not covered by tests
}

aJSONPatches := a.Spec.JSONPatches
bJSONPatches := b.Spec.JSONPatches
if len(aJSONPatches) != len(bJSONPatches) {
return false

Check warning on line 227 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L224-L227

Added lines #L224 - L227 were not covered by tests
}
return lo.EveryBy(aJSONPatches, func(aJSONPatch envoygatewayv1alpha1.EnvoyJSONPatchConfig) bool {
return lo.SomeBy(bJSONPatches, func(bJSONPatch envoygatewayv1alpha1.EnvoyJSONPatchConfig) bool {
return aJSONPatch.Type == bJSONPatch.Type && aJSONPatch.Name == bJSONPatch.Name && aJSONPatch.Operation == bJSONPatch.Operation
})

Check warning on line 232 in controllers/envoy_gateway_rate_limit_cluster_reconciler.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoy_gateway_rate_limit_cluster_reconciler.go#L229-L232

Added lines #L229 - L232 were not covered by tests
})
}
Loading

0 comments on commit 83d1fd5

Please sign in to comment.