diff --git a/cmd/main.go b/cmd/main.go index 38df4ee30..857d6814e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -22,26 +22,24 @@ import ( "log" "os" - eventingcontroller "github.com/kyma-project/eventing-manager/internal/controller/operator.kyma-project.io/eventing" - - istiopeerauthentication "github.com/kyma-project/eventing-manager/pkg/istio/peerauthentication" - "github.com/go-logr/zapr" - subscriptionv1alpha1 "github.com/kyma-project/kyma/components/eventing-controller/api/v1alpha1" - subscriptionv1alpha2 "github.com/kyma-project/kyma/components/eventing-controller/api/v1alpha2" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - apigatewayv1beta1 "github.com/kyma-incubator/api-gateway/api/v1beta1" - - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + apigatewayv1beta1 "github.com/kyma-incubator/api-gateway/api/v1beta1" + subscriptionv1alpha1 "github.com/kyma-project/eventing-manager/api/eventing/v1alpha1" + subscriptionv1alpha2 "github.com/kyma-project/eventing-manager/api/eventing/v1alpha2" + controllercache "github.com/kyma-project/eventing-manager/internal/controller/cache" + controllerclient "github.com/kyma-project/eventing-manager/internal/controller/client" + eventingcontroller "github.com/kyma-project/eventing-manager/internal/controller/operator/eventing" "github.com/kyma-project/eventing-manager/options" backendmetrics "github.com/kyma-project/eventing-manager/pkg/backend/metrics" "github.com/kyma-project/eventing-manager/pkg/env" "github.com/kyma-project/eventing-manager/pkg/eventing" + istiopeerauthentication "github.com/kyma-project/eventing-manager/pkg/istio/peerauthentication" "github.com/kyma-project/eventing-manager/pkg/k8s" "github.com/kyma-project/eventing-manager/pkg/logger" "github.com/kyma-project/eventing-manager/pkg/subscriptionmanager" @@ -123,6 +121,8 @@ func main() { //nolint:funlen // main function needs to initialize many object WebhookServer: webhook.NewServer(webhook.Options{Port: 9443}), Cache: cache.Options{SyncPeriod: &opts.ReconcilePeriod}, Metrics: server.Options{BindAddress: opts.MetricsAddr}, + NewCache: controllercache.New, + NewClient: controllerclient.New, }) if err != nil { setupLog.Error(err, "unable to start manager") diff --git a/internal/controller/cache/cache.go b/internal/controller/cache/cache.go new file mode 100644 index 000000000..3a6ef1f17 --- /dev/null +++ b/internal/controller/cache/cache.go @@ -0,0 +1,36 @@ +package cache + +import ( + appsv1 "k8s.io/api/apps/v1" + autoscalingv1 "k8s.io/api/autoscaling/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kyma-project/eventing-manager/internal/label" +) + +func New(config *rest.Config, options cache.Options) (cache.Cache, error) { + return cache.New(config, applySelectors(options)) +} + +// applySelectors applies label selectors to runtime objects created by the EventingManager. +func applySelectors(options cache.Options) cache.Options { + // TODO(marcobebway) filter by label "app.kubernetes.io/created-by=eventing-manager" when it is released + instanceEventing := fromLabelSelector(label.SelectorInstanceEventing()) + options.ByObject = map[client.Object]cache.ByObject{ + &appsv1.Deployment{}: instanceEventing, + &corev1.ServiceAccount{}: instanceEventing, + &rbacv1.ClusterRole{}: instanceEventing, + &rbacv1.ClusterRoleBinding{}: instanceEventing, + &autoscalingv1.HorizontalPodAutoscaler{}: instanceEventing, + } + return options +} + +func fromLabelSelector(selector labels.Selector) cache.ByObject { + return cache.ByObject{Label: selector} +} diff --git a/internal/controller/cache/cache_test.go b/internal/controller/cache/cache_test.go new file mode 100644 index 000000000..0e921241b --- /dev/null +++ b/internal/controller/cache/cache_test.go @@ -0,0 +1,134 @@ +package cache + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + autoscalingv1 "k8s.io/api/autoscaling/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func Test_applySelectors(t *testing.T) { + // given + selector := cache.ByObject{ + Label: labels.SelectorFromSet( + map[string]string{ + "app.kubernetes.io/instance": "eventing", + }, + ), + } + type args struct { + options cache.Options + } + tests := []struct { + name string + args args + want cache.Options + }{ + { + name: "should apply the correct selectors", + args: args{ + options: cache.Options{}, + }, + want: cache.Options{ + ByObject: map[client.Object]cache.ByObject{ + &appsv1.Deployment{}: selector, + &corev1.ServiceAccount{}: selector, + &rbacv1.ClusterRole{}: selector, + &rbacv1.ClusterRoleBinding{}: selector, + &autoscalingv1.HorizontalPodAutoscaler{}: selector, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // when + got := applySelectors(tt.args.options) + + // then + require.True(t, deepEqualOptions(tt.want, got)) + }) + } +} + +func deepEqualOptions(a, b cache.Options) bool { + // we only care about the ByObject comparison + return deepEqualByObject(a.ByObject, b.ByObject) +} + +func deepEqualByObject(a, b map[client.Object]cache.ByObject) bool { + if len(a) != len(b) { + return false + } + + aTypeMap := make(map[string]cache.ByObject, len(a)) + bTypeMap := make(map[string]cache.ByObject, len(a)) + computeTypeMap(a, aTypeMap) + computeTypeMap(b, bTypeMap) + return reflect.DeepEqual(aTypeMap, bTypeMap) +} + +func computeTypeMap(byObjectMap map[client.Object]cache.ByObject, typeMap map[string]cache.ByObject) { + keyOf := func(i interface{}) string { return fmt.Sprintf(">>> %T", i) } + for k, v := range byObjectMap { + if obj, ok := k.(*appsv1.Deployment); ok { + key := keyOf(obj) + typeMap[key] = v + } + if obj, ok := k.(*corev1.ServiceAccount); ok { + key := keyOf(obj) + typeMap[key] = v + } + if obj, ok := k.(*rbacv1.ClusterRole); ok { + key := keyOf(obj) + typeMap[key] = v + } + if obj, ok := k.(*rbacv1.ClusterRoleBinding); ok { + key := keyOf(obj) + typeMap[key] = v + } + if obj, ok := k.(*autoscalingv1.HorizontalPodAutoscaler); ok { + key := keyOf(obj) + typeMap[key] = v + } + } +} + +func Test_fromLabelSelector(t *testing.T) { + // given + type args struct { + label labels.Selector + } + tests := []struct { + name string + args args + want cache.ByObject + }{ + { + name: "should return the correct selector", + args: args{ + label: labels.SelectorFromSet(map[string]string{"key": "value"}), + }, + want: cache.ByObject{ + Label: labels.SelectorFromSet(map[string]string{"key": "value"}), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // when + got := fromLabelSelector(tt.args.label) + + // then + require.Equal(t, tt.want, got) + }) + } +} diff --git a/internal/controller/client/client.go b/internal/controller/client/client.go new file mode 100644 index 000000000..861c204c4 --- /dev/null +++ b/internal/controller/client/client.go @@ -0,0 +1,23 @@ +package client + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func New(config *rest.Config, options client.Options) (client.Client, error) { + return client.New(config, disableCacheForObjects(options)) +} + +// disableCacheForObjects disables caching for runtime objects that are not created by the EventingManager. +func disableCacheForObjects(options client.Options) client.Options { + options.Cache = &client.CacheOptions{ + DisableFor: []client.Object{ + &corev1.Secret{}, + &corev1.Service{}, + &corev1.ConfigMap{}, + }, + } + return options +} diff --git a/internal/controller/client/client_test.go b/internal/controller/client/client_test.go new file mode 100644 index 000000000..84e7eec6e --- /dev/null +++ b/internal/controller/client/client_test.go @@ -0,0 +1,92 @@ +package client + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func Test_disableCacheForObjects(t *testing.T) { + // given + type args struct { + options client.Options + } + tests := []struct { + name string + args args + want client.Options + }{ + { + name: "should disable cache for the correct objects", + args: args{ + options: client.Options{}, + }, + want: client.Options{ + Cache: &client.CacheOptions{ + DisableFor: []client.Object{ + &corev1.Secret{}, + &corev1.Service{}, + &corev1.ConfigMap{}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // when + got := disableCacheForObjects(tt.args.options) + + // then + require.True(t, deepEqualOptions(tt.want, got)) + }) + } +} + +func deepEqualOptions(a, b client.Options) bool { + // we only care about the Cache comparison + return deepEqualCacheOptions(a.Cache, b.Cache) +} + +func deepEqualCacheOptions(a, b *client.CacheOptions) bool { + if a == b { + return true + } + + if a == nil || b == nil { + return false + } + + // we only care about the DisableFor comparison + if len(a.DisableFor) != len(b.DisableFor) { + return false + } + + aTypeMap := make(map[string]interface{}, len(a.DisableFor)) + bTypeMap := make(map[string]interface{}, len(a.DisableFor)) + computeDisableForMap(a, aTypeMap) + computeDisableForMap(b, bTypeMap) + return reflect.DeepEqual(aTypeMap, bTypeMap) +} + +func computeDisableForMap(cacheOptions *client.CacheOptions, disableForMap map[string]interface{}) { + keyOf := func(i interface{}) string { return fmt.Sprintf(">>> %T", i) } + for _, obj := range cacheOptions.DisableFor { + if obj, ok := obj.(*corev1.Secret); ok { + key := keyOf(obj) + disableForMap[key] = nil + } + if obj, ok := obj.(*corev1.Service); ok { + key := keyOf(obj) + disableForMap[key] = nil + } + if obj, ok := obj.(*corev1.ConfigMap); ok { + key := keyOf(obj) + disableForMap[key] = nil + } + } +} diff --git a/internal/label/label.go b/internal/label/label.go index f7a759e19..9eb900d79 100644 --- a/internal/label/label.go +++ b/internal/label/label.go @@ -1,5 +1,9 @@ package label +import ( + "k8s.io/apimachinery/pkg/labels" +) + const ( KeyComponent = "app.kubernetes.io/component" KeyCreatedBy = "app.kubernetes.io/created-by" @@ -14,3 +18,7 @@ const ( ValueEventingManager = "eventing-manager" ValueEventing = "eventing" ) + +func SelectorInstanceEventing() labels.Selector { + return labels.SelectorFromSet(map[string]string{KeyInstance: ValueEventing}) +} diff --git a/internal/label/label_test.go b/internal/label/label_test.go new file mode 100644 index 000000000..23b0e2030 --- /dev/null +++ b/internal/label/label_test.go @@ -0,0 +1,34 @@ +package label + +import ( + "testing" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/labels" +) + +func TestSelectorInstanceEventing(t *testing.T) { + // given + tests := []struct { + name string + want labels.Selector + }{ + { + name: "should return the correct selector", + want: labels.SelectorFromSet( + map[string]string{ + "app.kubernetes.io/instance": "eventing", + }, + ), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // when + got := SelectorInstanceEventing() + + // then + require.Equal(t, tt.want, got) + }) + } +}