Skip to content

Commit

Permalink
fix issues gocrane#898
Browse files Browse the repository at this point in the history
(cherry picked from commit c48a90b)
  • Loading branch information
Cloudzp committed Aug 21, 2024
1 parent 73d3b0c commit a8ca3b3
Show file tree
Hide file tree
Showing 2 changed files with 229 additions and 5 deletions.
121 changes: 116 additions & 5 deletions pkg/controller/recommendation/recommendation_rule_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package recommendation
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/tools/cache"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -54,6 +58,7 @@ type RecommendationRuleController struct {
dynamicClient dynamic.Interface
discoveryClient discovery.DiscoveryInterface
Provider providers.History
dynamicLister DynamicLister
}

func (c *RecommendationRuleController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
Expand Down Expand Up @@ -147,9 +152,10 @@ func (c *RecommendationRuleController) doReconcile(ctx context.Context, recommen
keys = append(keys, k)
}
sort.Strings(keys) // sort key to get a certain order
recommendationIndex := NewRecommendationIndex(currRecommendations)
for _, key := range keys {
id := identities[key]
id.Recommendation = GetRecommendationFromIdentity(identities[key], currRecommendations)
id.Recommendation = recommendationIndex.GetRecommendation(id)
identitiesArray = append(identitiesArray, id)
}

Expand Down Expand Up @@ -243,6 +249,8 @@ func (c *RecommendationRuleController) SetupWithManager(mgr ctrl.Manager) error
c.kubeClient = kubernetes.NewForConfigOrDie(mgr.GetConfig())
c.discoveryClient = discovery.NewDiscoveryClientForConfigOrDie(mgr.GetConfig())
c.dynamicClient = dynamic.NewForConfigOrDie(mgr.GetConfig())
dynamicInformerFactory := dynamicinformer.NewDynamicSharedInformerFactory(c.dynamicClient, 0)
c.dynamicLister = NewDynamicInformerLister(dynamicInformerFactory)

return ctrl.NewControllerManagedBy(mgr).
For(&analysisv1alph1.RecommendationRule{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Expand All @@ -264,19 +272,19 @@ func (c *RecommendationRuleController) getIdentities(ctx context.Context, recomm

var unstructureds []unstructuredv1.Unstructured
if recommendationRule.Spec.NamespaceSelector.Any {
unstructuredList, err := c.dynamicClient.Resource(*gvr).List(ctx, metav1.ListOptions{})
unstructuredList, err := c.dynamicLister.List(ctx, *gvr, "")
if err != nil {
return nil, err
}
unstructureds = append(unstructureds, unstructuredList.Items...)
unstructureds = append(unstructureds, unstructuredList...)
} else {
for _, namespace := range recommendationRule.Spec.NamespaceSelector.MatchNames {
unstructuredList, err := c.dynamicClient.Resource(*gvr).Namespace(namespace).List(ctx, metav1.ListOptions{})
unstructuredList, err := c.dynamicLister.List(ctx, *gvr, namespace)
if err != nil {
return nil, err
}

unstructureds = append(unstructureds, unstructuredList.Items...)
unstructureds = append(unstructureds, unstructuredList...)
}
}

Expand Down Expand Up @@ -528,3 +536,106 @@ func IsConvertFromAnalytics(recommendationRule *analysisv1alph1.RecommendationRu

return false, ""
}

// DynamicLister is a lister for dynamic resources.
type DynamicLister interface {
// List returns a list of resources matching the given groupVersionResource.
List(ctx context.Context, gvk schema.GroupVersionResource, namespace string) ([]unstructuredv1.Unstructured, error)
}

type dynamicInformerLister struct {
dynamicLister map[schema.GroupVersionResource]cache.GenericLister
dynamicInformerFactory dynamicinformer.DynamicSharedInformerFactory
stopCh <-chan struct{}
}

func NewDynamicInformerLister(dynamicInformerFactory dynamicinformer.DynamicSharedInformerFactory) DynamicLister {
return &dynamicInformerLister{
dynamicLister: map[schema.GroupVersionResource]cache.GenericLister{},
dynamicInformerFactory: dynamicInformerFactory,
stopCh: make(chan struct{}),
}
}

func (d *dynamicInformerLister) List(ctx context.Context, gvr schema.GroupVersionResource, namespace string) ([]unstructuredv1.Unstructured, error) {
var (
objects []runtime.Object
err error
)

lister, exists := d.dynamicLister[gvr]
if !exists {
lister = d.dynamicInformerFactory.ForResource(gvr).Lister()
d.dynamicLister[gvr] = lister
d.dynamicInformerFactory.Start(d.stopCh)
if !d.dynamicInformerFactory.WaitForCacheSync(d.stopCh)[gvr] {
return nil, fmt.Errorf("failed to sync informer for %s", gvr)
}
}
if namespace != "" {
objects, err = lister.ByNamespace(namespace).List(labels.Everything())
} else {
objects, err = lister.List(labels.Everything())
}
if err != nil {
return nil, err
}

var unstructuredObjects []unstructuredv1.Unstructured
for _, obj := range objects {
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, err
}
unstructuredObjects = append(unstructuredObjects, unstructuredv1.Unstructured{Object: unstructuredObj})
}
return unstructuredObjects, nil
}

type IndexKey struct {
Namespace string
APIVersion string
Kind string
Name string
Recommender string
}

type RecommendationIndex struct {
mtx sync.RWMutex
idx map[IndexKey]*analysisv1alph1.Recommendation
}

func NewRecommendationIndex(recommendations analysisv1alph1.RecommendationList) *RecommendationIndex {
idx := make(map[IndexKey]*analysisv1alph1.Recommendation, len(recommendations.Items))
for i := range recommendations.Items {
r := &recommendations.Items[i]
idx[createIndexKey(r)] = r
}

return &RecommendationIndex{
idx: idx,
}
}

func createIndexKey(r *analysisv1alph1.Recommendation) IndexKey {
return IndexKey{
Kind: r.Spec.TargetRef.Kind,
APIVersion: r.Spec.TargetRef.APIVersion,
Namespace: r.Spec.TargetRef.Namespace,
Name: r.Spec.TargetRef.Name,
Recommender: string(r.Spec.Type),
}
}

func (idx *RecommendationIndex) GetRecommendation(id ObjectIdentity) *analysisv1alph1.Recommendation {
key := IndexKey{
Kind: id.Kind,
APIVersion: id.APIVersion,
Namespace: id.Namespace,
Name: id.Name,
Recommender: id.Recommender,
}
idx.mtx.RLock()
defer idx.mtx.RUnlock()
return idx.idx[key]
}
113 changes: 113 additions & 0 deletions pkg/controller/recommendation/recommendation_rule_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package recommendation

import (
analysisv1alph1 "github.com/gocrane/api/analysis/v1alpha1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"reflect"
"testing"
)

func TestRecommendationIndex_GetRecommendation(t *testing.T) {
type fields struct {
recommendationList analysisv1alph1.RecommendationList
}
type args struct {
id ObjectIdentity
}

tests := []struct {
name string
fields fields
args args
want *analysisv1alph1.Recommendation
}{
{
name: "TestRecommendationIndex_GetRecommendation good case",
fields: fields{
recommendationList: analysisv1alph1.RecommendationList{
Items: []analysisv1alph1.Recommendation{
{
ObjectMeta: v1.ObjectMeta{
Name: "test-recommendation-rule",
Namespace: "test-namespace",
},
Spec: analysisv1alph1.RecommendationSpec{
TargetRef: corev1.ObjectReference{
Namespace: "test-namespace",
Kind: "Deployment",
Name: "test-deployment-bar",
APIVersion: "app/v1",
},
Type: analysisv1alph1.AnalysisTypeResource,
},
},
{
ObjectMeta: v1.ObjectMeta{
Name: "test-recommendation-rule",
Namespace: "test-namespace",
},
Spec: analysisv1alph1.RecommendationSpec{
TargetRef: corev1.ObjectReference{
Namespace: "test-namespace",
Kind: "Deployment",
Name: "test-deployment-foo",
APIVersion: "app/v1",
},
Type: analysisv1alph1.AnalysisTypeResource,
},
},
},
},
},
want: &analysisv1alph1.Recommendation{
ObjectMeta: v1.ObjectMeta{
Name: "test-recommendation-rule",
Namespace: "test-namespace",
},
Spec: analysisv1alph1.RecommendationSpec{
TargetRef: corev1.ObjectReference{
Namespace: "test-namespace",
Kind: "Deployment",
Name: "test-deployment-name",
APIVersion: "app/v1",
},
},
},
args: args{
id: ObjectIdentity{
Name: "test-deployment-name",
Namespace: "test-namespace",
APIVersion: "app/v1",
Kind: "Deployment",
Recommender: "Resource",
},
},
},
{
name: "TestRecommendationIndex_GetRecommendation empty case",
fields: fields{
recommendationList: analysisv1alph1.RecommendationList{
Items: []analysisv1alph1.Recommendation{},
},
},
args: args{
id: ObjectIdentity{
Name: "test-deployment-name",
Namespace: "test-namespace",
APIVersion: "app/v1",
Kind: "Deployment",
Recommender: "Resources",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
idx := NewRecommendationIndex(tt.fields.recommendationList)
if got := idx.GetRecommendation(tt.args.id); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetRecommendation() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit a8ca3b3

Please sign in to comment.