diff --git a/cmd/craned/app/manager.go b/cmd/craned/app/manager.go index dcad14be3..d81158ef1 100644 --- a/cmd/craned/app/manager.go +++ b/cmd/craned/app/manager.go @@ -300,7 +300,7 @@ func initControllers(ctx context.Context, mgr ctrl.Manager, opts *options.Option klog.Exit(err, "unable to create controller", "controller", "AnalyticsController") } - if err := (&recommendation.Controller{ + if err := (&recommendation.RecommendationController{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), RestMapper: mgr.GetRESTMapper(), diff --git a/go.mod b/go.mod index 78b0445d2..5dda26e31 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.17 require ( github.com/go-echarts/go-echarts/v2 v2.2.4 - github.com/gocrane/api v0.6.1-0.20220719082537-b7bdeaf2fb17 + github.com/gocrane/api v0.6.1-0.20220721081535-2cf15fc58bf3 github.com/google/cadvisor v0.39.2 github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 github.com/prometheus/client_golang v1.11.0 @@ -181,7 +181,6 @@ require ( golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/tools v0.1.8 // indirect google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect - google.golang.org/protobuf v1.27.1 ) replace ( diff --git a/go.sum b/go.sum index 1129905e1..6a6092177 100644 --- a/go.sum +++ b/go.sum @@ -312,8 +312,8 @@ github.com/gobwas/ws v1.1.0-rc.5 h1:QOAag7FoBaBYYHRqzqkhhd8fq5RTubvI4v3Ft/gDVVQ= github.com/gobwas/ws v1.1.0-rc.5/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= github.com/gocrane/api v0.5.1-0.20220706040335-eaadbb4b99ed h1:aARCU+Hs1ZKTqJFJT/4/or/iGR6qYwMcG99CGmBFJpg= github.com/gocrane/api v0.5.1-0.20220706040335-eaadbb4b99ed/go.mod h1:GxI+t9AW8+NsHkz2JkPBIJN//9eLUjTZl1ScYAbXMbk= -github.com/gocrane/api v0.6.1-0.20220719082537-b7bdeaf2fb17 h1:b/DZZFrJ5g3TtUb9DNhzavKrXPi/fSFJoXWwR4cCZiE= -github.com/gocrane/api v0.6.1-0.20220719082537-b7bdeaf2fb17/go.mod h1:GxI+t9AW8+NsHkz2JkPBIJN//9eLUjTZl1ScYAbXMbk= +github.com/gocrane/api v0.6.1-0.20220721081535-2cf15fc58bf3 h1:cKnH9KoQzzBg3/bzYUfRZAhFW7rndswc+TB0rj+uzng= +github.com/gocrane/api v0.6.1-0.20220721081535-2cf15fc58bf3/go.mod h1:GxI+t9AW8+NsHkz2JkPBIJN//9eLUjTZl1ScYAbXMbk= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= diff --git a/pkg/controller/analytics/analytics_controller.go b/pkg/controller/analytics/analytics_controller.go index 73c768c69..f829c4e42 100644 --- a/pkg/controller/analytics/analytics_controller.go +++ b/pkg/controller/analytics/analytics_controller.go @@ -496,7 +496,10 @@ func objRefKey(kind, apiVersion, namespace, name, recommendType string) string { return fmt.Sprintf("%s#%s#%s#%s#%s", kind, apiVersion, namespace, name, recommendType) } -func match(labelSelector metav1.LabelSelector, matchLabels map[string]string) bool { +func match(labelSelector *metav1.LabelSelector, matchLabels map[string]string) bool { + if labelSelector == nil { + return true + } for k, v := range labelSelector.MatchLabels { if matchLabels[k] != v { return false diff --git a/pkg/controller/analytics/analytics_controller_test.go b/pkg/controller/analytics/analytics_controller_test.go index c114ef108..00d5e217c 100644 --- a/pkg/controller/analytics/analytics_controller_test.go +++ b/pkg/controller/analytics/analytics_controller_test.go @@ -16,13 +16,13 @@ func TestMatch(t *testing.T) { tests := []struct { description string matchLabels map[string]string - labelSelector metav1.LabelSelector + labelSelector *metav1.LabelSelector expect bool }{ { description: "match labels", matchLabels: matchLabels, - labelSelector: metav1.LabelSelector{ + labelSelector: &metav1.LabelSelector{ MatchLabels: unmatchLabels, MatchExpressions: []metav1.LabelSelectorRequirement{}, }, @@ -31,7 +31,7 @@ func TestMatch(t *testing.T) { { description: "match expression key2 exists", matchLabels: matchLabels, - labelSelector: metav1.LabelSelector{ + labelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "key2", @@ -44,7 +44,7 @@ func TestMatch(t *testing.T) { { description: "match expression key1 exists", matchLabels: matchLabels, - labelSelector: metav1.LabelSelector{ + labelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "key1", @@ -57,7 +57,7 @@ func TestMatch(t *testing.T) { { description: "match expression key1 doesNotExists", matchLabels: matchLabels, - labelSelector: metav1.LabelSelector{ + labelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "key1", @@ -70,7 +70,7 @@ func TestMatch(t *testing.T) { { description: "match expression key2 doesNotExists", matchLabels: matchLabels, - labelSelector: metav1.LabelSelector{ + labelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "key2", @@ -83,7 +83,7 @@ func TestMatch(t *testing.T) { { description: "match expression key2 in", matchLabels: matchLabels, - labelSelector: metav1.LabelSelector{ + labelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "key2", @@ -96,7 +96,7 @@ func TestMatch(t *testing.T) { { description: "match expression key1 in value1", matchLabels: matchLabels, - labelSelector: metav1.LabelSelector{ + labelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "key1", @@ -109,7 +109,7 @@ func TestMatch(t *testing.T) { { description: "match expression key1 in value2", matchLabels: matchLabels, - labelSelector: metav1.LabelSelector{ + labelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "key1", @@ -122,7 +122,7 @@ func TestMatch(t *testing.T) { { description: "match expression key1 notIn value1", matchLabels: matchLabels, - labelSelector: metav1.LabelSelector{ + labelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "key1", @@ -135,7 +135,7 @@ func TestMatch(t *testing.T) { { description: "match expression key1 notIn Value2", matchLabels: matchLabels, - labelSelector: metav1.LabelSelector{ + labelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, MatchExpressions: []metav1.LabelSelectorRequirement{{ Key: "key1", diff --git a/pkg/controller/recommendation/recommendation_controller.go b/pkg/controller/recommendation/recommendation_controller.go index 479aa52a2..6cc5bb495 100644 --- a/pkg/controller/recommendation/recommendation_controller.go +++ b/pkg/controller/recommendation/recommendation_controller.go @@ -21,8 +21,8 @@ import ( "github.com/gocrane/crane/pkg/providers" ) -// Controller is responsible for reconcile Recommendation -type Controller struct { +// RecommendationController is responsible for reconcile Recommendation +type RecommendationController struct { client.Client ConfigSet *analysisv1alph1.ConfigSet Scheme *runtime.Scheme @@ -33,7 +33,7 @@ type Controller struct { Provider providers.History } -func (c *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (c *RecommendationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { klog.V(4).Infof("Got Recommendation %s", req.NamespacedName) recommendation := &analysisv1alph1.Recommendation{} @@ -73,7 +73,7 @@ func (c *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return ctrl.Result{}, nil } -func (c *Controller) UpdateStatus(ctx context.Context, recommendation *analysisv1alph1.Recommendation, newStatus *analysisv1alph1.RecommendationStatus) { +func (c *RecommendationController) UpdateStatus(ctx context.Context, recommendation *analysisv1alph1.Recommendation, newStatus *analysisv1alph1.RecommendationStatus) { if !equality.Semantic.DeepEqual(&recommendation.Status, newStatus) { recommendation.Status = *newStatus timeNow := metav1.Now() @@ -90,7 +90,7 @@ func (c *Controller) UpdateStatus(ctx context.Context, recommendation *analysisv } } -func (c *Controller) SetupWithManager(mgr ctrl.Manager) error { +func (c *RecommendationController) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&analysisv1alph1.Recommendation{}). Complete(c) diff --git a/pkg/controller/recommendation/recommendation_rule_controller.go b/pkg/controller/recommendation/recommendation_rule_controller.go new file mode 100644 index 000000000..4c9325c44 --- /dev/null +++ b/pkg/controller/recommendation/recommendation_rule_controller.go @@ -0,0 +1,469 @@ +package recommendation + +import ( + "context" + "fmt" + "reflect" + "strings" + "sync" + "time" + + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + unstructuredv1 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/version" + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/scale" + "k8s.io/client-go/tools/record" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/yaml" + + analysisv1alph1 "github.com/gocrane/api/analysis/v1alpha1" + + "github.com/gocrane/crane/pkg/known" + predictormgr "github.com/gocrane/crane/pkg/predictor" + "github.com/gocrane/crane/pkg/providers" + "github.com/gocrane/crane/pkg/recommend" + "github.com/gocrane/crane/pkg/utils" +) + +type Controller struct { + client.Client + Scheme *runtime.Scheme + RestMapper meta.RESTMapper + Recorder record.EventRecorder + kubeClient kubernetes.Interface + dynamicClient dynamic.Interface + discoveryClient discovery.DiscoveryInterface + K8SVersion *version.Version + ScaleClient scale.ScalesGetter + PredictorMgr predictormgr.Manager + Provider providers.History +} + +func (c *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + klog.V(4).InfoS("Got a RecommendationRule resource.", "RecommendationRule", req.NamespacedName) + + recommendationRule := &analysisv1alph1.RecommendationRule{} + + err := c.Client.Get(ctx, req.NamespacedName, recommendationRule) + if err != nil { + if errors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + if recommendationRule.DeletionTimestamp != nil { + return ctrl.Result{}, nil + } + + lastUpdateTime := recommendationRule.Status.LastUpdateTime + if len(strings.TrimSpace(recommendationRule.Spec.RunInterval)) == 0 { + if lastUpdateTime != nil { + // This is a one-off recommendationRule task which has been completed. + return ctrl.Result{}, nil + } + } + + interval, err := time.ParseDuration(recommendationRule.Spec.RunInterval) + if err != nil { + c.Recorder.Event(recommendationRule, v1.EventTypeNormal, "FailedParseRunInterval", err.Error()) + klog.Errorf("Failed to parse RunInterval, recommendationRule %s", klog.KObj(recommendationRule)) + return ctrl.Result{}, err + } + + if lastUpdateTime != nil { + planingTime := lastUpdateTime.Add(interval) + now := time.Now() + if now.Before(planingTime) { + return ctrl.Result{ + RequeueAfter: planingTime.Sub(now), + }, nil + } + } + + finished := c.doReconcile(ctx, recommendationRule, interval) + if finished && len(strings.TrimSpace(recommendationRule.Spec.RunInterval)) != 0 { + klog.V(4).InfoS("Will re-sync", "after", interval) + // Arrange for next round. + return ctrl.Result{ + RequeueAfter: interval, + }, nil + } + + return ctrl.Result{RequeueAfter: time.Second * 1}, nil +} + +func (c *Controller) doReconcile(ctx context.Context, recommendationRule *analysisv1alph1.RecommendationRule, interval time.Duration) bool { + newStatus := recommendationRule.Status.DeepCopy() + + identities, err := c.getIdentities(ctx, recommendationRule) + if err != nil { + c.Recorder.Event(recommendationRule, corev1.EventTypeNormal, "FailedSelectResource", err.Error()) + msg := fmt.Sprintf("Failed to get idenitities, RecommendationRule %s error %v", klog.KObj(recommendationRule), err) + klog.Errorf(msg) + c.UpdateStatus(ctx, recommendationRule, newStatus) + return false + } + + timeNow := metav1.Now() + + // if the first mission start time is last round, reset currMissions here + currMissions := newStatus.Recommendations + if currMissions != nil && len(currMissions) > 0 { + firstMissionStartTime := currMissions[0].LastStartTime + if firstMissionStartTime.IsZero() { + currMissions = nil + } else { + planingTime := firstMissionStartTime.Add(interval) + if time.Now().After(planingTime) { + currMissions = nil // reset missions to trigger creation for missions + } + } + } + + if currMissions == nil { + // create recommendation missions for this round + for _, id := range identities { + currMissions = append(currMissions, analysisv1alph1.RecommendationMission{ + TargetRef: id.GetObjectReference(), + }) + } + } + + var currRecommendations analysisv1alph1.RecommendationList + opts := []client.ListOption{ + client.MatchingLabels(map[string]string{known.RecommendationRuleUidLabel: string(recommendationRule.UID)}), + } + err = c.Client.List(ctx, &currRecommendations, opts...) + if err != nil { + c.Recorder.Event(recommendationRule, corev1.EventTypeNormal, "FailedSelectResource", err.Error()) + msg := fmt.Sprintf("Failed to get recomendations, RecommendationRule %s error %v", klog.KObj(recommendationRule), err) + klog.Errorf(msg) + c.UpdateStatus(ctx, recommendationRule, newStatus) + return false + } + + if klog.V(6).Enabled() { + // Print identities + for k, id := range identities { + klog.V(6).InfoS("identities", "RecommendationRule", klog.KObj(recommendationRule), "key", k, "apiVersion", id.APIVersion, "kind", id.Kind, "namespace", id.Namespace, "name", id.Name) + } + } + + maxConcurrency := 10 + executionIndex := -1 + var concurrency int + for index, mission := range currMissions { + if mission.LastStartTime != nil { + continue + } + if executionIndex == -1 { + executionIndex = index + } + if concurrency < maxConcurrency { + concurrency++ + } + } + + wg := sync.WaitGroup{} + wg.Add(concurrency) + for index := executionIndex; index < len(currMissions) && index < concurrency+executionIndex; index++ { + var existingRecommendation *analysisv1alph1.Recommendation + for _, r := range currRecommendations.Items { + if reflect.DeepEqual(currMissions[index].TargetRef, r.Spec.TargetRef) { + existingRecommendation = &r + break + } + } + + go c.executeMission(ctx, &wg, recommendationRule, identities, &currMissions[index], existingRecommendation, timeNow) + } + + wg.Wait() + + finished := false + if executionIndex+concurrency == len(currMissions) || len(currMissions) == 0 { + finished = true + } + + if finished { + newStatus.LastUpdateTime = &timeNow + + // clean orphan recommendations + for _, recommendation := range currRecommendations.Items { + exist := false + for _, mission := range currMissions { + if recommendation.UID == mission.UID { + exist = true + break + } + } + + if !exist { + err = c.Client.Delete(ctx, &recommendation) + if err != nil { + klog.ErrorS(err, "Failed to delete recommendation.", "recommendation", klog.KObj(&recommendation)) + } else { + klog.Infof("Deleted orphan recommendation %v.", klog.KObj(&recommendation)) + } + } + } + + } + + newStatus.Recommendations = currMissions + + c.UpdateStatus(ctx, recommendationRule, newStatus) + return finished +} + +func (c *Controller) CreateRecommendationObject(recommendationRule *analysisv1alph1.RecommendationRule, + target corev1.ObjectReference, id ObjectIdentity, recommenderName string) *analysisv1alph1.Recommendation { + + recommendation := &analysisv1alph1.Recommendation{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: fmt.Sprintf("%s-%s-", recommendationRule.Name, strings.ToLower(recommenderName)), + Namespace: id.Namespace, + OwnerReferences: []metav1.OwnerReference{ + *newOwnerRef(recommendationRule), + }, + Labels: id.Labels, + }, + Spec: analysisv1alph1.RecommendationSpec{ + TargetRef: target, + }, + } + + if recommendation.Labels == nil { + recommendation.Labels = map[string]string{} + } + recommendation.Labels[known.RecommendationRuleNameLabel] = recommendationRule.Name + recommendation.Labels[known.RecommendationRuleUidLabel] = string(recommendationRule.UID) + recommendation.Labels[known.RecommendationRuleRecommenderLabel] = recommenderName + + // todo: set recommendation.Spec.type + + return recommendation +} + +func (c *Controller) SetupWithManager(mgr ctrl.Manager) error { + c.kubeClient = kubernetes.NewForConfigOrDie(mgr.GetConfig()) + c.discoveryClient = discovery.NewDiscoveryClientForConfigOrDie(mgr.GetConfig()) + c.dynamicClient = dynamic.NewForConfigOrDie(mgr.GetConfig()) + + serverVersion, err := c.discoveryClient.ServerVersion() + if err != nil { + return err + } + c.K8SVersion = version.MustParseGeneric(serverVersion.GitVersion) + + return ctrl.NewControllerManagedBy(mgr). + For(&analysisv1alph1.RecommendationRule{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Complete(c) +} + +func (c *Controller) getIdentities(ctx context.Context, recommendationRule *analysisv1alph1.RecommendationRule) (map[string]ObjectIdentity, error) { + identities := map[string]ObjectIdentity{} + + for _, rs := range recommendationRule.Spec.ResourceSelectors { + if rs.Kind == "" { + return nil, fmt.Errorf("empty kind") + } + + resList, err := c.discoveryClient.ServerResourcesForGroupVersion(rs.APIVersion) + if err != nil { + return nil, err + } + + var resName string + for _, res := range resList.APIResources { + if rs.Kind == res.Kind { + resName = res.Name + break + } + } + if resName == "" { + return nil, fmt.Errorf("invalid kind %s", rs.Kind) + } + + gv, err := schema.ParseGroupVersion(rs.APIVersion) + if err != nil { + return nil, err + } + gvr := gv.WithResource(resName) + + var unstructureds []unstructuredv1.Unstructured + if recommendationRule.Spec.NamespaceSelector.Any { + unstructuredList, err := c.dynamicClient.Resource(gvr).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, err + } + unstructureds = append(unstructureds, unstructuredList.Items...) + } else { + for _, namespace := range recommendationRule.Spec.NamespaceSelector.MatchNames { + unstructuredList, err := c.dynamicClient.Resource(gvr).Namespace(namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, err + } + + unstructureds = append(unstructureds, unstructuredList.Items...) + } + } + + var filterdUnstructureds []unstructuredv1.Unstructured + for _, unstructed := range unstructureds { + if rs.Name != "" && unstructed.GetName() != rs.Name { + // filter Name that not match + continue + } + + if match, _ := utils.LabelSelectorMatched(unstructed.GetLabels(), rs.LabelSelector); !match { + // filter that not match labelSelector + continue + } + filterdUnstructureds = append(filterdUnstructureds, unstructed) + } + + for i := range filterdUnstructureds { + k := objRefKey(rs.Kind, rs.APIVersion, unstructureds[i].GetNamespace(), unstructureds[i].GetName()) + if _, exists := identities[k]; !exists { + identities[k] = ObjectIdentity{ + Namespace: unstructureds[i].GetNamespace(), + Name: unstructureds[i].GetName(), + Kind: rs.Kind, + APIVersion: rs.APIVersion, + Labels: unstructureds[i].GetLabels(), + Object: unstructureds[i], + } + } + } + } + + return identities, nil +} + +func (c *Controller) executeMission(ctx context.Context, wg *sync.WaitGroup, recommendationRule *analysisv1alph1.RecommendationRule, identities map[string]ObjectIdentity, mission *analysisv1alph1.RecommendationMission, existingRecommendation *analysisv1alph1.Recommendation, timeNow metav1.Time) { + defer func() { + mission.LastStartTime = &timeNow + klog.Infof("Mission message: %s", mission.Message) + + wg.Done() + }() + + k := objRefKey(mission.TargetRef.Kind, mission.TargetRef.APIVersion, mission.TargetRef.Namespace, mission.TargetRef.Name) + if id, exist := identities[k]; !exist { + mission.Message = fmt.Sprintf("Failed to get identity, key %s. ", k) + return + } else { + recommendation := existingRecommendation + if recommendation == nil { + recommendation = c.CreateRecommendationObject(recommendationRule, mission.TargetRef, id, "") + } + // do recommendation + //recommender, err := recommend.NewRecommender(c.Client, c.RestMapper, c.ScaleClient, recommendation, c.PredictorMgr, c.Provider, c.configSet, analytics.Spec.Config) + //if err != nil { + // mission.Message = fmt.Sprintf("Failed to create recommender, Recommendation %s error %v", klog.KObj(recommendation), err) + // return + //} + + var recommender recommend.Recommender + + proposed, err := recommender.Offer() + if err != nil { + mission.Message = fmt.Sprintf("Failed to offer recommendation: %s", err.Error()) + return + } + + var value string + valueBytes, err := yaml.Marshal(proposed) + if err != nil { + mission.Message = err.Error() + return + } + value = string(valueBytes) + + recommendation.Status.RecommendedValue = value + recommendation.Status.LastUpdateTime = &timeNow + if existingRecommendation != nil { + klog.Infof("Update recommendation %s", klog.KObj(recommendation)) + if err := c.Update(ctx, recommendation); err != nil { + mission.Message = fmt.Sprintf("Failed to create recommendation %s: %v", klog.KObj(recommendation), err) + return + } + + klog.Infof("Successfully to update Recommendation %s", klog.KObj(recommendation)) + } else { + klog.Infof("Create recommendation %s", klog.KObj(recommendation)) + if err := c.Create(ctx, recommendation); err != nil { + mission.Message = fmt.Sprintf("Failed to create recommendation %s: %v", klog.KObj(recommendation), err) + return + } + + klog.Infof("Successfully to create Recommendation %s", klog.KObj(recommendation)) + } + + mission.Message = "Success" + mission.UID = recommendation.UID + mission.Name = recommendation.Name + mission.Namespace = recommendation.Namespace + mission.Kind = recommendation.Kind + mission.APIVersion = recommendation.APIVersion + } +} + +func (c *Controller) UpdateStatus(ctx context.Context, recommendationRule *analysisv1alph1.RecommendationRule, newStatus *analysisv1alph1.RecommendationRuleStatus) { + if !equality.Semantic.DeepEqual(&recommendationRule.Status, newStatus) { + recommendationRule.Status = *newStatus + err := c.Update(ctx, recommendationRule) + if err != nil { + c.Recorder.Event(recommendationRule, corev1.EventTypeNormal, "FailedUpdateStatus", err.Error()) + klog.Errorf("Failed to update status, RecommendationRule %s error %v", klog.KObj(recommendationRule), err) + return + } + + klog.Infof("Update RecommendationRule status successful, RecommendationRule %s", klog.KObj(recommendationRule)) + } +} + +type ObjectIdentity struct { + Namespace string + APIVersion string + Kind string + Name string + Labels map[string]string + Object unstructuredv1.Unstructured +} + +func (id ObjectIdentity) GetObjectReference() corev1.ObjectReference { + return corev1.ObjectReference{Kind: id.Kind, APIVersion: id.APIVersion, Namespace: id.Namespace, Name: id.Name} +} + +func newOwnerRef(a *analysisv1alph1.RecommendationRule) *metav1.OwnerReference { + blockOwnerDeletion, isController := false, false + return &metav1.OwnerReference{ + APIVersion: a.APIVersion, + Kind: a.Kind, + Name: a.GetName(), + UID: a.GetUID(), + BlockOwnerDeletion: &blockOwnerDeletion, + Controller: &isController, + } +} + +func objRefKey(kind, apiVersion, namespace, name string) string { + return fmt.Sprintf("%s#%s#%s#%s", kind, apiVersion, namespace, name) +} diff --git a/pkg/controller/recommendation/updater.go b/pkg/controller/recommendation/updater.go index 32c6614e3..a1514d70b 100644 --- a/pkg/controller/recommendation/updater.go +++ b/pkg/controller/recommendation/updater.go @@ -22,7 +22,7 @@ import ( "github.com/gocrane/crane/pkg/utils" ) -func (c *Controller) UpdateRecommendation(ctx context.Context, recommendation *analysisapi.Recommendation) (bool, error) { +func (c *RecommendationController) UpdateRecommendation(ctx context.Context, recommendation *analysisapi.Recommendation) (bool, error) { var proposedRecommendation recommendtypes.ProposedRecommendation needUpdate := false diff --git a/pkg/known/label.go b/pkg/known/label.go index 7334f8d97..8aec55e3f 100644 --- a/pkg/known/label.go +++ b/pkg/known/label.go @@ -15,3 +15,9 @@ const ( AnalyticsUidLabel = "analysis.crane.io/analytics-uid" AnalyticsTypeLabel = "analysis.crane.io/analytics-type" ) + +const ( + RecommendationRuleNameLabel = "analysis.crane.io/recommendation-rule-name" + RecommendationRuleUidLabel = "analysis.crane.io/recommendation-rule-uid" + RecommendationRuleRecommenderLabel = "analysis.crane.io/recommendation-rule-recommender" +)