From a303280975008d569d7d1f43aeecdd12977f5582 Mon Sep 17 00:00:00 2001 From: asingh51 Date: Thu, 11 Jan 2024 12:41:28 -0800 Subject: [PATCH] add analysisRun to the rollout notification Signed-off-by: asingh51 add analysisRun to the rollout notification Signed-off-by: asingh51 deploy rollout ext in wave4 and 5 --- utils/record/record.go | 79 ++++++++++------- utils/record/record_test.go | 167 ++++++++++++++++++++++++++++++++---- 2 files changed, 196 insertions(+), 50 deletions(-) diff --git a/utils/record/record.go b/utils/record/record.go index 4b08610548..afe4304c1a 100644 --- a/utils/record/record.go +++ b/utils/record/record.go @@ -6,7 +6,7 @@ import ( "encoding/base64" "encoding/json" "fmt" - informers "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions/rollouts/v1alpha1" + argoinformers "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions/rollouts/v1alpha1" timeutil "github.com/argoproj/argo-rollouts/utils/time" "github.com/argoproj/notifications-engine/pkg/api" "github.com/argoproj/notifications-engine/pkg/services" @@ -43,9 +43,10 @@ func init() { } const ( - controllerAgentName = "rollouts-controller" - NotificationConfigMap = "argo-rollouts-notification-configmap" - NotificationSecret = "argo-rollouts-notification-secret" + controllerAgentName = "rollouts-controller" + NotificationConfigMap = "argo-rollouts-notification-configmap" + NotificationSecret = "argo-rollouts-notification-secret" + RolloutsPodTemplateHash = "rollouts-pod-template-hash" ) type EventOptions struct { @@ -228,47 +229,61 @@ func (e *EventRecorderAdapter) K8sRecorder() record.EventRecorder { return e.Recorder } -func NewAPIFactorySettings(arInformer informers.AnalysisRunInformer) api.Settings { +func getAnalysisRunsMatchLabels(ro v1alpha1.Rollout, arInformer argoinformers.AnalysisRunInformer) (interface{}, error) { + + set := labels.Set(map[string]string{ + RolloutsPodTemplateHash: ro.Status.CurrentPodHash, + }) + + ars, err := arInformer.Lister().AnalysisRuns(ro.Namespace).List(labels.SelectorFromSet(set)) + + if err != nil || len(ars) == 0 { + return nil, err + } + + sort.Slice(ars[:], func(i, j int) bool { + ts1 := ars[i].ObjectMeta.CreationTimestamp.Time + ts2 := ars[j].ObjectMeta.CreationTimestamp.Time + return ts1.After(ts2) + }) + + var arsObj interface{} + arBytes, _ := json.Marshal(ars) + err = json.Unmarshal(arBytes, &arsObj) + + if err != nil { + return nil, err + } + return arsObj, nil +} + +func NewAPIFactorySettings(arInformer argoinformers.AnalysisRunInformer) api.Settings { return api.Settings{ SecretName: NotificationSecret, ConfigMapName: NotificationConfigMap, - InitGetVars: func(cfg *api.Config, configMap *corev1.ConfigMap, secret *corev1.Secret) (getVars api.GetVars, err error) { - - getVars = func(obj map[string]interface{}, dest services.Destination) (result map[string]any) { - result = map[string]any{"rollout": obj, "time": timeExprs} + InitGetVars: func(cfg *api.Config, configMap *corev1.ConfigMap, secret *corev1.Secret) (api.GetVars, error) { + return func(obj map[string]any, dest services.Destination) map[string]any { + var vars = map[string]any{"rollout": obj, "time": timeExprs} if arInformer == nil { - return + return vars } - un := unstructured.Unstructured{Object: obj} var ro v1alpha1.Rollout - err = runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &ro) - if err != nil { - log.Errorf("failed to convert obj to rollout: %v", err) - result = map[string]any{"rollout": nil, "time": timeExprs} - return + err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj, &ro) + + if err != nil || ro.Namespace == "" { + return vars } + arsObj, err := getAnalysisRunsMatchLabels(ro, arInformer) - ars, err := arInformer.Lister().AnalysisRuns(ro.Namespace).List(labels.Everything()) if err != nil { - log.Errorf("failed to fetch analysisRuns in the given namespace: %v", err) - result = map[string]any{"rollout": obj, "time": timeExprs} - return + return vars } - if len(ars) != 0 { - sort.Slice(ars[:], func(i, j int) bool { - ts1 := ars[i].ObjectMeta.CreationTimestamp.Time - ts2 := ars[j].ObjectMeta.CreationTimestamp.Time - return ts1.After(ts2) - }) - result = map[string]any{"rollout": obj, "analysisRun": ars[0], "time": timeExprs} - } - return - } - - return getVars, nil + vars = map[string]any{"rollout": obj, "analysisRuns": arsObj, "time": timeExprs} + return vars + }, nil }, } } diff --git a/utils/record/record_test.go b/utils/record/record_test.go index 84e4fd94e8..1e9a792868 100644 --- a/utils/record/record_test.go +++ b/utils/record/record_test.go @@ -2,17 +2,15 @@ package record import ( "bytes" + "encoding/json" "errors" "fmt" - "strings" - "testing" - "time" - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" argofake "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/fake" - argoinformers "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions" + argoinformersfactory "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions" + argoinformers "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions/rollouts/v1alpha1" "github.com/argoproj/argo-rollouts/utils/defaults" - + timeutil "github.com/argoproj/argo-rollouts/utils/time" "github.com/argoproj/notifications-engine/pkg/api" notificationapi "github.com/argoproj/notifications-engine/pkg/api" "github.com/argoproj/notifications-engine/pkg/mocks" @@ -24,11 +22,17 @@ import ( dto "github.com/prometheus/client_model/go" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" + "strings" + "testing" + "time" ) var ( @@ -187,7 +191,7 @@ func TestSendNotificationsWhenConditionTime(t *testing.T) { sharedInformers := informers.NewSharedInformerFactory(k8sClient, 0) f := argofake.NewSimpleClientset() - rolloutsI := argoinformers.NewSharedInformerFactory(f, noResyncPeriodFunc()) + rolloutsI := argoinformersfactory.NewSharedInformerFactory(f, noResyncPeriodFunc()) arInformer := rolloutsI.Argoproj().V1alpha1().AnalysisRuns() cmInformer := sharedInformers.Core().V1().ConfigMaps().Informer() @@ -237,7 +241,7 @@ func TestSendNotificationsWhenConditionTime(t *testing.T) { secretInformer.GetIndexer().Add(secret) cmInformer.GetIndexer().Add(cm) f := argofake.NewSimpleClientset() - rolloutsI := argoinformers.NewSharedInformerFactory(f, noResyncPeriodFunc()) + rolloutsI := argoinformersfactory.NewSharedInformerFactory(f, noResyncPeriodFunc()) arInformer := rolloutsI.Argoproj().V1alpha1().AnalysisRuns() apiFactory := notificationapi.NewFactory(NewAPIFactorySettings(arInformer), defaults.Namespace(), secretInformer, cmInformer) @@ -438,20 +442,147 @@ func TestSendNotificationsNoTrigger(t *testing.T) { } func TestNewAPIFactorySettings(t *testing.T) { + + ars := []*v1alpha1.AnalysisRun{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "analysis-run-1", + CreationTimestamp: metav1.NewTime(timeutil.Now().Add(-1 * time.Hour)), + Namespace: "default", + Labels: map[string]string{"rollouts-pod-template-hash": "85659df978"}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "analysis-run-2", + CreationTimestamp: metav1.NewTime(timeutil.Now().Add(-2 * time.Hour)), + Namespace: "default", + Labels: map[string]string{"rollouts-pod-template-hash": "85659df978"}, + }, + }, + } + f := argofake.NewSimpleClientset() - rolloutsI := argoinformers.NewSharedInformerFactory(f, noResyncPeriodFunc()) + rolloutsI := argoinformersfactory.NewSharedInformerFactory(f, noResyncPeriodFunc()) arInformer := rolloutsI.Argoproj().V1alpha1().AnalysisRuns() + ro := v1alpha1.Rollout{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rollout", + Namespace: "default", + }, + Status: v1alpha1.RolloutStatus{ + CurrentPodHash: "85659df978", + }, + } - settings := NewAPIFactorySettings(arInformer) - assert.Equal(t, NotificationConfigMap, settings.ConfigMapName) - assert.Equal(t, NotificationSecret, settings.SecretName) - getVars, err := settings.InitGetVars(nil, nil, nil) - assert.NoError(t, err) - - rollout := map[string]any{"name": "hello"} - vars := getVars(rollout, services.Destination{}) + type expectedFunc func(obj map[string]interface{}, ar interface{}) map[string]interface{} + testcase := []struct { + name string + arInformer argoinformers.AnalysisRunInformer + rollout v1alpha1.Rollout + ars []*v1alpha1.AnalysisRun + expected expectedFunc + }{ + { + name: "Send notification for rollout and analysisRun matches label", + arInformer: arInformer, + rollout: ro, + ars: ars, + expected: func(obj map[string]interface{}, ar interface{}) map[string]interface{} { + return map[string]interface{}{ + "rollout": obj, + "analysisRuns": ar, + "time": timeExprs, + } + }, + }, + { + name: "Send notification only rollout when pod hash doesn't match", + arInformer: arInformer, + rollout: ro, + ars: append(ars, &v1alpha1.AnalysisRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "analysis-run-2", + CreationTimestamp: metav1.Now(), + Namespace: "default-1", + Labels: map[string]string{"rollouts-pod-template-hash": "123456"}, + }, + }), + expected: func(obj map[string]interface{}, ar interface{}) map[string]interface{} { + return map[string]interface{}{ + "rollout": obj, + "analysisRuns": ar, + "time": timeExprs, + } + }, + }, + { + name: "arInformer is nil", + arInformer: nil, + rollout: ro, + ars: ars, + expected: func(obj map[string]interface{}, ar interface{}) map[string]interface{} { + return map[string]interface{}{ + "rollout": obj, + "time": timeExprs, + } + }, + }, + { + name: "rollout object namespace and pod-hash not matched", + rollout: v1alpha1.Rollout{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rollout", + }, + }, + ars: ars, + expected: func(obj map[string]interface{}, ar interface{}) map[string]interface{} { + ro.Namespace = "" + ro.Status.CurrentPodHash = "1234" + return map[string]interface{}{ + "rollout": obj, + "time": timeExprs, + } + }, + }, + { + name: "no analysisRuns", + arInformer: nil, + rollout: ro, + ars: []*v1alpha1.AnalysisRun{}, + expected: func(obj map[string]interface{}, ar interface{}) map[string]interface{} { + return map[string]interface{}{ + "rollout": obj, + "time": timeExprs, + } + }, + }, + } - assert.Equal(t, map[string]any{"rollout": rollout, "time": timeExprs}, vars) + for _, test := range testcase { + t.Run(test.name, func(t *testing.T) { + for _, ar := range test.ars { + _ = arInformer.Informer().GetStore().Add(ar) + } + settings := NewAPIFactorySettings(test.arInformer) + getVars, err := settings.InitGetVars(nil, nil, nil) + require.NoError(t, err) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + obj, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(&test.rollout) + + arBytes, err := json.Marshal(ars) + var arsObj interface{} + _ = json.Unmarshal(arBytes, &arsObj) + vars := getVars(obj, services.Destination{}) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + assert.Equal(t, test.expected(obj, arsObj), vars) + }) + } } func TestWorkloadRefObjectMap(t *testing.T) {