From 9f1463a9c9e3578add41025bdfa4d73e4b09d7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Ma=C5=82ek?= Date: Wed, 2 Oct 2024 17:00:12 +0200 Subject: [PATCH] feat(konnect): manage finalizers for KongRoute in KonnectEntityPluginBindingFinalizerReconciler (#683) --- ...conciler_generic_pluginbindingfinalizer.go | 41 ++++++ ...ler_generic_pluginbindingfinalizer_rbac.go | 2 + .../kongplugincleanupfinalizer_test.go | 125 +++++++++--------- test/helpers/deploy/deploy_resources.go | 23 ++++ 4 files changed, 132 insertions(+), 59 deletions(-) diff --git a/controller/konnect/reconciler_generic_pluginbindingfinalizer.go b/controller/konnect/reconciler_generic_pluginbindingfinalizer.go index 9cb745383..5ae42ad7c 100644 --- a/controller/konnect/reconciler_generic_pluginbindingfinalizer.go +++ b/controller/konnect/reconciler_generic_pluginbindingfinalizer.go @@ -87,6 +87,31 @@ func enqueueKongServiceForKongPluginBinding() func( } } +func enqueueKongRouteForKongPluginBinding() func( + ctx context.Context, obj client.Object) []reconcile.Request { + return func(ctx context.Context, obj client.Object) []reconcile.Request { + kpb, ok := obj.(*configurationv1alpha1.KongPluginBinding) + if !ok { + return nil + } + + if kpb.Spec.Targets.RouteReference == nil || + kpb.Spec.Targets.RouteReference.Kind != "KongRoute" || + kpb.Spec.Targets.RouteReference.Group != configurationv1alpha1.GroupVersion.Group { + return nil + } + + return []ctrl.Request{ + { + NamespacedName: types.NamespacedName{ + Namespace: kpb.Namespace, + Name: kpb.Spec.Targets.RouteReference.Name, + }, + }, + } + } +} + // Reconcile reconciles the Konnect entity that can be set as KongPluginBinding target. // Its purpose is to: // - check if the entity is marked for deletion and mark KongPluginBindings @@ -189,6 +214,8 @@ func (r *KonnectEntityPluginBindingFinalizerReconciler[T, TEnt]) getKongPluginBi switch any(ent).(type) { case *configurationv1alpha1.KongService: return IndexFieldKongPluginBindingKongServiceReference + case *configurationv1alpha1.KongRoute: + return IndexFieldKongPluginBindingKongRouteReference default: panic(fmt.Sprintf("unsupported entity type %s", constraints.EntityTypeName[T]())) } @@ -217,6 +244,20 @@ func (r *KonnectEntityPluginBindingFinalizerReconciler[T, TEnt]) setControllerBu enqueueKongServiceForKongPluginBinding(), ), ) + case *configurationv1alpha1.KongRoute: + b. + For(&configurationv1alpha1.KongRoute{}, + builder.WithPredicates( + predicate.NewPredicateFuncs(objRefersToKonnectGatewayControlPlane[configurationv1alpha1.KongRoute]), + kongPluginsAnnotationChangedPredicate, + ), + ). + Watches( + &configurationv1alpha1.KongPluginBinding{}, + handler.EnqueueRequestsFromMapFunc( + enqueueKongRouteForKongPluginBinding(), + ), + ) default: panic(fmt.Sprintf("unsupported entity type %s", constraints.EntityTypeName[T]())) } diff --git a/controller/konnect/reconciler_generic_pluginbindingfinalizer_rbac.go b/controller/konnect/reconciler_generic_pluginbindingfinalizer_rbac.go index ffdf07c1c..43aded6ee 100644 --- a/controller/konnect/reconciler_generic_pluginbindingfinalizer_rbac.go +++ b/controller/konnect/reconciler_generic_pluginbindingfinalizer_rbac.go @@ -2,4 +2,6 @@ package konnect //+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongservices,verbs=get;update +//+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongroutes,verbs=get;update + //+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongpluginbindings,verbs=delete;list diff --git a/test/envtest/kongplugincleanupfinalizer_test.go b/test/envtest/kongplugincleanupfinalizer_test.go index f308b4f9e..b174d3640 100644 --- a/test/envtest/kongplugincleanupfinalizer_test.go +++ b/test/envtest/kongplugincleanupfinalizer_test.go @@ -7,8 +7,6 @@ import ( "testing" "github.com/stretchr/testify/require" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/watch" "sigs.k8s.io/controller-runtime/pkg/client" @@ -18,7 +16,6 @@ import ( "github.com/kong/gateway-operator/pkg/consts" "github.com/kong/gateway-operator/test/helpers/deploy" - configurationv1 "github.com/kong/kubernetes-configuration/api/configuration/v1" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" ) @@ -42,64 +39,74 @@ func TestKongPluginFinalizer(t *testing.T) { cp := deploy.KonnectGatewayControlPlaneWithID(t, ctx, clientNamespaced, apiAuth) require.NoError(t, manager.SetupCacheIndicesForKonnectTypes(ctx, mgr, false)) - reconcilers := []Reconciler{ + + StartReconcilers(ctx, t, mgr, logs, konnect.NewKonnectEntityPluginReconciler[configurationv1alpha1.KongService](false, mgr.GetClient()), - } - - StartReconcilers(ctx, t, mgr, logs, reconcilers...) - - rateLimitingkongPlugin := &configurationv1.KongPlugin{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "rate-limiting-kp-", - }, - PluginName: "rate-limiting", - Config: apiextensionsv1.JSON{ - Raw: []byte(`{"minute": 5, "policy": "local"}`), - }, - } - require.NoError(t, clientNamespaced.Create(ctx, rateLimitingkongPlugin)) - t.Logf("deployed %s KongPlugin (%s) resource", client.ObjectKeyFromObject(rateLimitingkongPlugin), rateLimitingkongPlugin.PluginName) - - wKongService := setupWatch[configurationv1alpha1.KongServiceList](t, ctx, clientWithWatch, client.InNamespace(ns.Name)) - kongService := deploy.KongServiceAttachedToCP(t, ctx, clientNamespaced, cp) - kpb := deploy.KongPluginBinding(t, ctx, clientNamespaced, - &configurationv1alpha1.KongPluginBinding{ - Spec: configurationv1alpha1.KongPluginBindingSpec{ - ControlPlaneRef: &configurationv1alpha1.ControlPlaneRef{ - Type: configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef, - KonnectNamespacedRef: &configurationv1alpha1.KonnectNamespacedRef{ - Name: cp.Name, - }, - }, - PluginReference: configurationv1alpha1.PluginRef{ - Name: rateLimitingkongPlugin.Name, - }, - Targets: configurationv1alpha1.KongPluginBindingTargets{ - ServiceReference: &configurationv1alpha1.TargetRefWithGroupKind{ - Group: configurationv1alpha1.GroupVersion.Group, - Kind: "KongService", - Name: kongService.Name, - }, - }, - }, - }, + konnect.NewKonnectEntityPluginReconciler[configurationv1alpha1.KongRoute](false, mgr.GetClient()), ) - _ = watchFor(t, ctx, wKongService, watch.Modified, - func(svc *configurationv1alpha1.KongService) bool { - return svc.Name == kongService.Name && - slices.Contains(svc.GetFinalizers(), consts.CleanupPluginBindingFinalizer) - }, - fmt.Sprintf("KongService doesn't have the %s finalizer set", consts.CleanupPluginBindingFinalizer), - ) + t.Run("KongService", func(t *testing.T) { + rateLimitingkongPlugin := deploy.RateLimitingPlugin(t, ctx, clientNamespaced) - wKongService = setupWatch[configurationv1alpha1.KongServiceList](t, ctx, clientWithWatch, client.InNamespace(ns.Name)) - require.NoError(t, clientNamespaced.Delete(ctx, kpb)) - _ = watchFor(t, ctx, wKongService, watch.Modified, - func(svc *configurationv1alpha1.KongService) bool { - return svc.Name == kongService.Name && - !slices.Contains(svc.GetFinalizers(), consts.CleanupPluginBindingFinalizer) - }, - fmt.Sprintf("KongService has the %s finalizer set but it shouldn't", consts.CleanupPluginBindingFinalizer), - ) + wKongService := setupWatch[configurationv1alpha1.KongServiceList](t, ctx, clientWithWatch, client.InNamespace(ns.Name)) + kongService := deploy.KongServiceAttachedToCP(t, ctx, clientNamespaced, cp) + kpb := deploy.KongPluginBinding(t, ctx, clientNamespaced, + konnect.NewKongPluginBindingBuilder(). + WithControlPlaneRefKonnectNamespaced(cp.Name). + WithPluginRef(rateLimitingkongPlugin.Name). + WithServiceTarget(kongService.Name). + Build(), + ) + + _ = watchFor(t, ctx, wKongService, watch.Modified, + func(svc *configurationv1alpha1.KongService) bool { + return svc.Name == kongService.Name && + slices.Contains(svc.GetFinalizers(), consts.CleanupPluginBindingFinalizer) + }, + fmt.Sprintf("KongService doesn't have the %s finalizer set", consts.CleanupPluginBindingFinalizer), + ) + + wKongService = setupWatch[configurationv1alpha1.KongServiceList](t, ctx, clientWithWatch, client.InNamespace(ns.Name)) + require.NoError(t, clientNamespaced.Delete(ctx, kpb)) + _ = watchFor(t, ctx, wKongService, watch.Modified, + func(svc *configurationv1alpha1.KongService) bool { + return svc.Name == kongService.Name && + !slices.Contains(svc.GetFinalizers(), consts.CleanupPluginBindingFinalizer) + }, + fmt.Sprintf("KongService has the %s finalizer set but it shouldn't", consts.CleanupPluginBindingFinalizer), + ) + }) + + t.Run("KongRoute", func(t *testing.T) { + rateLimitingkongPlugin := deploy.RateLimitingPlugin(t, ctx, clientNamespaced) + + kongService := deploy.KongServiceAttachedToCP(t, ctx, clientNamespaced, cp) + wKongRoute := setupWatch[configurationv1alpha1.KongRouteList](t, ctx, clientWithWatch, client.InNamespace(ns.Name)) + kongRoute := deploy.KongRouteAttachedToService(t, ctx, clientNamespaced, kongService) + kpb := deploy.KongPluginBinding(t, ctx, clientNamespaced, + konnect.NewKongPluginBindingBuilder(). + WithControlPlaneRefKonnectNamespaced(cp.Name). + WithPluginRef(rateLimitingkongPlugin.Name). + WithRouteTarget(kongRoute.Name). + Build(), + ) + + _ = watchFor(t, ctx, wKongRoute, watch.Modified, + func(svc *configurationv1alpha1.KongRoute) bool { + return svc.Name == kongRoute.Name && + slices.Contains(svc.GetFinalizers(), consts.CleanupPluginBindingFinalizer) + }, + fmt.Sprintf("KongRoute doesn't have the %s finalizer set", consts.CleanupPluginBindingFinalizer), + ) + + wKongRoute = setupWatch[configurationv1alpha1.KongRouteList](t, ctx, clientWithWatch, client.InNamespace(ns.Name)) + require.NoError(t, clientNamespaced.Delete(ctx, kpb)) + _ = watchFor(t, ctx, wKongRoute, watch.Modified, + func(svc *configurationv1alpha1.KongRoute) bool { + return svc.Name == kongRoute.Name && + !slices.Contains(svc.GetFinalizers(), consts.CleanupPluginBindingFinalizer) + }, + fmt.Sprintf("KongRoute has the %s finalizer set but it shouldn't", consts.CleanupPluginBindingFinalizer), + ) + }) } diff --git a/test/helpers/deploy/deploy_resources.go b/test/helpers/deploy/deploy_resources.go index 1926046d5..902bec157 100644 --- a/test/helpers/deploy/deploy_resources.go +++ b/test/helpers/deploy/deploy_resources.go @@ -683,6 +683,29 @@ func ProxyCachePlugin( return plugin } +// RateLimitingPlugin deploys the rate-limiting KongPlugin resource and returns the resource. +// The provided client should be namespaced, i.e. created with `client.NewNamespacedClient(client, ns)` +func RateLimitingPlugin( + t *testing.T, + ctx context.Context, + cl client.Client, +) *configurationv1.KongPlugin { + t.Helper() + + plugin := &configurationv1.KongPlugin{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "rate-limiting-kp-", + }, + PluginName: "rate-limiting", + Config: apiextensionsv1.JSON{ + Raw: []byte(`{"minute": 5, "policy": "local"}`), + }, + } + require.NoError(t, cl.Create(ctx, plugin)) + t.Logf("deployed new %s KongPlugin (%s)", client.ObjectKeyFromObject(plugin), plugin.PluginName) + return plugin +} + // KongKeySetAttachedToCP deploys a KongKeySet resource attached to a CP and returns the resource. func KongKeySetAttachedToCP( t *testing.T,