Skip to content

Commit

Permalink
Cache runtime objects only created by the EventingManager (#273)
Browse files Browse the repository at this point in the history
* Cache runtime objects only created by the EventingManager

* Update test case name
  • Loading branch information
marcobebway authored and friedrichwilken committed Dec 14, 2023
1 parent 2916c7c commit 8ac92c6
Show file tree
Hide file tree
Showing 7 changed files with 337 additions and 10 deletions.
20 changes: 10 additions & 10 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
Expand Down
36 changes: 36 additions & 0 deletions internal/controller/cache/cache.go
Original file line number Diff line number Diff line change
@@ -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}
}
134 changes: 134 additions & 0 deletions internal/controller/cache/cache_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
23 changes: 23 additions & 0 deletions internal/controller/client/client.go
Original file line number Diff line number Diff line change
@@ -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
}
92 changes: 92 additions & 0 deletions internal/controller/client/client_test.go
Original file line number Diff line number Diff line change
@@ -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
}
}
}
8 changes: 8 additions & 0 deletions internal/label/label.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package label

import (
"k8s.io/apimachinery/pkg/labels"
)

const (
KeyComponent = "app.kubernetes.io/component"
KeyCreatedBy = "app.kubernetes.io/created-by"
Expand All @@ -14,3 +18,7 @@ const (
ValueEventingManager = "eventing-manager"
ValueEventing = "eventing"
)

func SelectorInstanceEventing() labels.Selector {
return labels.SelectorFromSet(map[string]string{KeyInstance: ValueEventing})
}
34 changes: 34 additions & 0 deletions internal/label/label_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}

0 comments on commit 8ac92c6

Please sign in to comment.