Skip to content

Commit 4f4e3cd

Browse files
authored
Merge pull request #900 from Cloudzp/bugfix
fix issues #898
2 parents 557db96 + d8d5dd2 commit 4f4e3cd

5 files changed

Lines changed: 250 additions & 9 deletions

File tree

go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ require (
99
github.com/google/cadvisor v0.41.0
1010
github.com/jaypipes/ghw v0.9.0
1111
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12
12+
github.com/onsi/ginkgo v1.16.5
13+
github.com/onsi/gomega v1.15.0
14+
github.com/pkg/errors v0.9.1
1215
github.com/prometheus/client_golang v1.11.0
1316
github.com/prometheus/common v0.26.0
1417
github.com/shirou/gopsutil v3.21.10+incompatible
@@ -78,6 +81,7 @@ require (
7881
github.com/ghodss/yaml v1.0.0 // indirect
7982
github.com/gin-contrib/sse v0.1.0 // indirect
8083
github.com/go-logr/logr v0.4.0 // indirect
84+
github.com/go-logr/zapr v0.4.0 // indirect
8185
github.com/go-ole/go-ole v1.2.6 // indirect
8286
github.com/go-openapi/jsonpointer v0.19.5 // indirect
8387
github.com/go-openapi/jsonreference v0.19.5 // indirect
@@ -113,12 +117,12 @@ require (
113117
github.com/modern-go/reflect2 v1.0.2 // indirect
114118
github.com/mrunalp/fileutils v0.5.0 // indirect
115119
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
120+
github.com/nxadm/tail v1.4.8 // indirect
116121
github.com/opencontainers/go-digest v1.0.0 // indirect
117122
github.com/opencontainers/image-spec v1.0.1 // indirect
118123
github.com/opencontainers/runc v1.0.2 // indirect
119124
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect
120125
github.com/opencontainers/selinux v1.8.2 // indirect
121-
github.com/pkg/errors v0.9.1 // indirect
122126
github.com/pmezard/go-difflib v1.0.0 // indirect
123127
github.com/prometheus/client_model v0.2.0 // indirect
124128
github.com/prometheus/procfs v0.6.0 // indirect
@@ -155,6 +159,7 @@ require (
155159
google.golang.org/appengine v1.6.7 // indirect
156160
gopkg.in/inf.v0 v0.9.1 // indirect
157161
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
162+
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
158163
gopkg.in/warnings.v0 v0.1.2 // indirect
159164
gopkg.in/yaml.v2 v2.4.0 // indirect
160165
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect

pkg/controller/recommendation/recommendation_checker.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ func (r Checker) runChecker() {
6060
"owner_name": recommend.Spec.TargetRef.Name,
6161
"update_status": updateStatus,
6262
"result_status": resultStatus,
63-
}).Set(1)
63+
}).Set(time.Now().Sub(recommend.Status.LastUpdateTime.Time).Seconds())
6464
}
6565
}

pkg/controller/recommendation/recommendation_rule_controller.go

Lines changed: 117 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@ import (
1515
"k8s.io/apimachinery/pkg/api/meta"
1616
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1717
unstructuredv1 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
18+
"k8s.io/apimachinery/pkg/labels"
1819
"k8s.io/apimachinery/pkg/runtime"
20+
"k8s.io/apimachinery/pkg/runtime/schema"
1921
"k8s.io/apimachinery/pkg/types"
2022
"k8s.io/client-go/discovery"
2123
"k8s.io/client-go/dynamic"
24+
"k8s.io/client-go/dynamic/dynamicinformer"
2225
"k8s.io/client-go/kubernetes"
2326
"k8s.io/client-go/scale"
27+
"k8s.io/client-go/tools/cache"
2428
"k8s.io/client-go/tools/record"
2529
"k8s.io/client-go/util/retry"
2630
"k8s.io/klog/v2"
@@ -30,7 +34,6 @@ import (
3034
"sigs.k8s.io/controller-runtime/pkg/predicate"
3135

3236
analysisv1alph1 "github.com/gocrane/api/analysis/v1alpha1"
33-
3437
"github.com/gocrane/crane/pkg/known"
3538
"github.com/gocrane/crane/pkg/metrics"
3639
"github.com/gocrane/crane/pkg/oom"
@@ -54,6 +57,7 @@ type RecommendationRuleController struct {
5457
dynamicClient dynamic.Interface
5558
discoveryClient discovery.DiscoveryInterface
5659
Provider providers.History
60+
dynamicLister DynamicLister
5761
}
5862

5963
func (c *RecommendationRuleController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
@@ -147,9 +151,10 @@ func (c *RecommendationRuleController) doReconcile(ctx context.Context, recommen
147151
keys = append(keys, k)
148152
}
149153
sort.Strings(keys) // sort key to get a certain order
154+
recommendationIndex := NewRecommendationIndex(currRecommendations)
150155
for _, key := range keys {
151156
id := identities[key]
152-
id.Recommendation = GetRecommendationFromIdentity(identities[key], currRecommendations)
157+
id.Recommendation = recommendationIndex.GetRecommendation(id)
153158
identitiesArray = append(identitiesArray, id)
154159
}
155160

@@ -243,6 +248,8 @@ func (c *RecommendationRuleController) SetupWithManager(mgr ctrl.Manager) error
243248
c.kubeClient = kubernetes.NewForConfigOrDie(mgr.GetConfig())
244249
c.discoveryClient = discovery.NewDiscoveryClientForConfigOrDie(mgr.GetConfig())
245250
c.dynamicClient = dynamic.NewForConfigOrDie(mgr.GetConfig())
251+
dynamicInformerFactory := dynamicinformer.NewDynamicSharedInformerFactory(c.dynamicClient, 0)
252+
c.dynamicLister = NewDynamicInformerLister(dynamicInformerFactory)
246253

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

265272
var unstructureds []unstructuredv1.Unstructured
266273
if recommendationRule.Spec.NamespaceSelector.Any {
267-
unstructuredList, err := c.dynamicClient.Resource(*gvr).List(ctx, metav1.ListOptions{})
274+
unstructuredList, err := c.dynamicLister.List(ctx, *gvr, "")
268275
if err != nil {
269276
return nil, err
270277
}
271-
unstructureds = append(unstructureds, unstructuredList.Items...)
278+
unstructureds = append(unstructureds, unstructuredList...)
272279
} else {
273280
for _, namespace := range recommendationRule.Spec.NamespaceSelector.MatchNames {
274-
unstructuredList, err := c.dynamicClient.Resource(*gvr).Namespace(namespace).List(ctx, metav1.ListOptions{})
281+
unstructuredList, err := c.dynamicLister.List(ctx, *gvr, namespace)
275282
if err != nil {
276283
return nil, err
277284
}
278285

279-
unstructureds = append(unstructureds, unstructuredList.Items...)
286+
unstructureds = append(unstructureds, unstructuredList...)
280287
}
281288
}
282289

@@ -453,6 +460,7 @@ func executeIdentity(ctx context.Context, wg *sync.WaitGroup, recommenderMgr rec
453460
defer func() {
454461
if wg != nil {
455462
wg.Done()
463+
metrics.RecommendationExecutionCounter.WithLabelValues(id.APIVersion, id.Kind, id.Namespace, id.Name, id.Recommender).Inc()
456464
}
457465
}()
458466
var message string
@@ -528,3 +536,106 @@ func IsConvertFromAnalytics(recommendationRule *analysisv1alph1.RecommendationRu
528536

529537
return false, ""
530538
}
539+
540+
// DynamicLister is a lister for dynamic resources.
541+
type DynamicLister interface {
542+
// List returns a list of resources matching the given groupVersionResource.
543+
List(ctx context.Context, gvk schema.GroupVersionResource, namespace string) ([]unstructuredv1.Unstructured, error)
544+
}
545+
546+
type dynamicInformerLister struct {
547+
dynamicLister map[schema.GroupVersionResource]cache.GenericLister
548+
dynamicInformerFactory dynamicinformer.DynamicSharedInformerFactory
549+
stopCh <-chan struct{}
550+
}
551+
552+
func NewDynamicInformerLister(dynamicInformerFactory dynamicinformer.DynamicSharedInformerFactory) DynamicLister {
553+
return &dynamicInformerLister{
554+
dynamicLister: map[schema.GroupVersionResource]cache.GenericLister{},
555+
dynamicInformerFactory: dynamicInformerFactory,
556+
stopCh: make(chan struct{}),
557+
}
558+
}
559+
560+
func (d *dynamicInformerLister) List(ctx context.Context, gvr schema.GroupVersionResource, namespace string) ([]unstructuredv1.Unstructured, error) {
561+
var (
562+
objects []runtime.Object
563+
err error
564+
)
565+
566+
lister, exists := d.dynamicLister[gvr]
567+
if !exists {
568+
lister = d.dynamicInformerFactory.ForResource(gvr).Lister()
569+
d.dynamicLister[gvr] = lister
570+
d.dynamicInformerFactory.Start(d.stopCh)
571+
if !d.dynamicInformerFactory.WaitForCacheSync(d.stopCh)[gvr] {
572+
return nil, fmt.Errorf("failed to sync informer for %s", gvr)
573+
}
574+
}
575+
if namespace != "" {
576+
objects, err = lister.ByNamespace(namespace).List(labels.Everything())
577+
} else {
578+
objects, err = lister.List(labels.Everything())
579+
}
580+
if err != nil {
581+
return nil, err
582+
}
583+
584+
var unstructuredObjects []unstructuredv1.Unstructured
585+
for _, obj := range objects {
586+
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
587+
if err != nil {
588+
return nil, err
589+
}
590+
unstructuredObjects = append(unstructuredObjects, unstructuredv1.Unstructured{Object: unstructuredObj})
591+
}
592+
return unstructuredObjects, nil
593+
}
594+
595+
type IndexKey struct {
596+
Namespace string
597+
APIVersion string
598+
Kind string
599+
Name string
600+
Recommender string
601+
}
602+
603+
type RecommendationIndex struct {
604+
mtx sync.RWMutex
605+
idx map[IndexKey]*analysisv1alph1.Recommendation
606+
}
607+
608+
func NewRecommendationIndex(recommendations analysisv1alph1.RecommendationList) *RecommendationIndex {
609+
idx := make(map[IndexKey]*analysisv1alph1.Recommendation, len(recommendations.Items))
610+
for i := range recommendations.Items {
611+
r := &recommendations.Items[i]
612+
idx[createIndexKey(r)] = r
613+
}
614+
615+
return &RecommendationIndex{
616+
idx: idx,
617+
}
618+
}
619+
620+
func createIndexKey(r *analysisv1alph1.Recommendation) IndexKey {
621+
return IndexKey{
622+
Kind: r.Spec.TargetRef.Kind,
623+
APIVersion: r.Spec.TargetRef.APIVersion,
624+
Namespace: r.Spec.TargetRef.Namespace,
625+
Name: r.Spec.TargetRef.Name,
626+
Recommender: string(r.Spec.Type),
627+
}
628+
}
629+
630+
func (idx *RecommendationIndex) GetRecommendation(id ObjectIdentity) *analysisv1alph1.Recommendation {
631+
key := IndexKey{
632+
Kind: id.Kind,
633+
APIVersion: id.APIVersion,
634+
Namespace: id.Namespace,
635+
Name: id.Name,
636+
Recommender: id.Recommender,
637+
}
638+
idx.mtx.RLock()
639+
defer idx.mtx.RUnlock()
640+
return idx.idx[key]
641+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package recommendation
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
analysisv1alph1 "github.com/gocrane/api/analysis/v1alpha1"
8+
corev1 "k8s.io/api/core/v1"
9+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
)
11+
12+
func TestRecommendationIndex_GetRecommendation(t *testing.T) {
13+
type fields struct {
14+
recommendationList analysisv1alph1.RecommendationList
15+
}
16+
type args struct {
17+
id ObjectIdentity
18+
}
19+
20+
tests := []struct {
21+
name string
22+
fields fields
23+
args args
24+
want *analysisv1alph1.Recommendation
25+
}{
26+
{
27+
name: "TestRecommendationIndex_GetRecommendation good case",
28+
fields: fields{
29+
recommendationList: analysisv1alph1.RecommendationList{
30+
Items: []analysisv1alph1.Recommendation{
31+
{
32+
ObjectMeta: v1.ObjectMeta{
33+
Name: "test-recommendation-rule",
34+
Namespace: "test-namespace",
35+
},
36+
Spec: analysisv1alph1.RecommendationSpec{
37+
TargetRef: corev1.ObjectReference{
38+
Namespace: "test-namespace",
39+
Kind: "Deployment",
40+
Name: "test-deployment-bar",
41+
APIVersion: "app/v1",
42+
},
43+
Type: analysisv1alph1.AnalysisTypeResource,
44+
},
45+
},
46+
{
47+
ObjectMeta: v1.ObjectMeta{
48+
Name: "test-recommendation-rule",
49+
Namespace: "test-namespace",
50+
},
51+
Spec: analysisv1alph1.RecommendationSpec{
52+
TargetRef: corev1.ObjectReference{
53+
Namespace: "test-namespace",
54+
Kind: "Deployment",
55+
Name: "test-deployment-foo",
56+
APIVersion: "app/v1",
57+
},
58+
Type: analysisv1alph1.AnalysisTypeResource,
59+
},
60+
},
61+
},
62+
},
63+
},
64+
want: &analysisv1alph1.Recommendation{
65+
ObjectMeta: v1.ObjectMeta{
66+
Name: "test-recommendation-rule",
67+
Namespace: "test-namespace",
68+
},
69+
Spec: analysisv1alph1.RecommendationSpec{
70+
TargetRef: corev1.ObjectReference{
71+
Namespace: "test-namespace",
72+
Kind: "Deployment",
73+
Name: "test-deployment-bar",
74+
APIVersion: "app/v1",
75+
},
76+
Type: analysisv1alph1.AnalysisTypeResource,
77+
},
78+
},
79+
args: args{
80+
id: ObjectIdentity{
81+
Name: "test-deployment-bar",
82+
Namespace: "test-namespace",
83+
APIVersion: "app/v1",
84+
Kind: "Deployment",
85+
Recommender: "Resource",
86+
},
87+
},
88+
},
89+
{
90+
name: "TestRecommendationIndex_GetRecommendation empty case",
91+
fields: fields{
92+
recommendationList: analysisv1alph1.RecommendationList{
93+
Items: []analysisv1alph1.Recommendation{},
94+
},
95+
},
96+
args: args{
97+
id: ObjectIdentity{
98+
Name: "test-deployment-name",
99+
Namespace: "test-namespace",
100+
APIVersion: "app/v1",
101+
Kind: "Deployment",
102+
Recommender: "Resources",
103+
},
104+
},
105+
},
106+
}
107+
for _, tt := range tests {
108+
t.Run(tt.name, func(t *testing.T) {
109+
idx := NewRecommendationIndex(tt.fields.recommendationList)
110+
if got := idx.GetRecommendation(tt.args.id); !reflect.DeepEqual(got, tt.want) {
111+
t.Errorf("GetRecommendation() = %v, want %v", got, tt.want)
112+
}
113+
})
114+
}
115+
}

pkg/metrics/analysis.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ import (
66
)
77

88
var (
9+
RecommendationExecutionCounter = prometheus.NewCounterVec(
10+
prometheus.CounterOpts{
11+
Namespace: "crane",
12+
Subsystem: "analysis",
13+
Name: "recommendation_execution_total",
14+
Help: "The number of times Recommendation has been executed",
15+
},
16+
[]string{"apiversion", "owner_kind", "namespace", "owner_name", "type"},
17+
)
18+
919
ResourceRecommendation = prometheus.NewGaugeVec(
1020
prometheus.GaugeOpts{
1121
Namespace: "crane",
@@ -48,5 +58,5 @@ var (
4858
)
4959

5060
func init() {
51-
metrics.Registry.MustRegister(ResourceRecommendation, ReplicasRecommendation, SelectTargets, RecommendationsStatus)
61+
metrics.Registry.MustRegister(RecommendationExecutionCounter, ResourceRecommendation, ReplicasRecommendation, SelectTargets, RecommendationsStatus)
5262
}

0 commit comments

Comments
 (0)