Skip to content

Commit

Permalink
add cache options
Browse files Browse the repository at this point in the history
  • Loading branch information
friedrichwilken committed Dec 29, 2023
1 parent e8aea48 commit 15fe2b4
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 0 deletions.
38 changes: 38 additions & 0 deletions internal/controller/cache/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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/nats-manager/internal/label"
)

// New returns a cache with the cache-options applied, generade form the rest-config.
func New(config *rest.Config, options cache.Options) (cache.Cache, error) {
return cache.New(config, applySelectors(options))
}

func applySelectors(options cache.Options) cache.Options {
// The only objects we allow are the ones with the 'created-by: nats-manager' label applied.
createdByNATSManager := fromLabelSelector(label.SelectorCreatedByNATS())

// Apply the label selector to all relevant objects.
options.ByObject = map[client.Object]cache.ByObject{
&appsv1.Deployment{}: createdByNATSManager,
&autoscalingv1.HorizontalPodAutoscaler{}: createdByNATSManager,
&corev1.ServiceAccount{}: createdByNATSManager,
&rbacv1.ClusterRole{}: createdByNATSManager,
&rbacv1.ClusterRoleBinding{}: createdByNATSManager,
}
return options
}

func fromLabelSelector(selector labels.Selector) cache.ByObject {
return cache.ByObject{Label: selector}
}
158 changes: 158 additions & 0 deletions internal/controller/cache/cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package cache

import (
"fmt"
"reflect"
"testing"
"time"

"github.com/kyma-project/nats-manager/internal/label"
"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
syncPeriod := 30 * time.Second
selector := cache.ByObject{
Label: labels.SelectorFromSet(
map[string]string{
label.KeyCreatedBy: label.ValueNATSManager,
},
),
}

type args struct {
options cache.Options
}
testCases := []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,
},
},
},
{
name: "should not remove existing options",
args: args{
options: cache.Options{
SyncPeriod: &syncPeriod,
},
},
want: cache.Options{
SyncPeriod: &syncPeriod,
ByObject: map[client.Object]cache.ByObject{
&appsv1.Deployment{}: selector,
&corev1.ServiceAccount{}: selector,
&rbacv1.ClusterRole{}: selector,
&rbacv1.ClusterRoleBinding{}: selector,
&autoscalingv1.HorizontalPodAutoscaler{}: selector,
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// when
got := applySelectors(tc.args.options)

// then
require.True(t, deepEqualOptions(tc.want, got))
})
}
}

func deepEqualOptions(a, b cache.Options) bool {
// we only care about the ByObject comparison
o := deepEqualByObject(a.ByObject, b.ByObject)
s := *a.SyncPeriod == *b.SyncPeriod
return o && s
}

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)
})
}
}

0 comments on commit 15fe2b4

Please sign in to comment.