From 6a1b765a77f497b6b4208801a69309f0df1928c8 Mon Sep 17 00:00:00 2001 From: Sanskar Jaiswal Date: Thu, 9 Jun 2022 21:38:08 +0530 Subject: [PATCH] add unit tests for ScaledObjectReconciler Signed-off-by: Sanskar Jaiswal --- pkg/canary/deployment_fixture_test.go | 2 +- pkg/canary/scaled_object_reconciler.go | 29 ++-- pkg/canary/scaled_object_reconciler_test.go | 128 ++++++++++++++++++ pkg/canary/scaler_reconciler_fixture_test.go | 52 ++++++- .../scheduler_deployment_fixture_test.go | 4 +- 5 files changed, 194 insertions(+), 21 deletions(-) create mode 100644 pkg/canary/scaled_object_reconciler_test.go diff --git a/pkg/canary/deployment_fixture_test.go b/pkg/canary/deployment_fixture_test.go index c14bc5b8..ecdf7bf2 100644 --- a/pkg/canary/deployment_fixture_test.go +++ b/pkg/canary/deployment_fixture_test.go @@ -395,7 +395,7 @@ func newDeploymentControllerTestCanary(cc canaryConfigs) *flaggerv1.Canary { APIVersion: "apps/v1", Kind: "Deployment", }, - AutoscalerRef: &flaggerv1.LocalObjectReference{ + AutoscalerRef: &flaggerv1.AutoscalerRefernce{ Name: "podinfo", APIVersion: "autoscaling/v2beta2", Kind: "HorizontalPodAutoscaler", diff --git a/pkg/canary/scaled_object_reconciler.go b/pkg/canary/scaled_object_reconciler.go index 5c7f8bbd..c1ac5dfb 100644 --- a/pkg/canary/scaled_object_reconciler.go +++ b/pkg/canary/scaled_object_reconciler.go @@ -37,38 +37,39 @@ func (sor *ScaledObjectReconciler) ReconcilePrimaryScaler(cd *flaggerv1.Canary, } func (sor *ScaledObjectReconciler) reconcilePrimaryScaler(cd *flaggerv1.Canary, init bool) error { - primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name) + primaryName := fmt.Sprintf("%s-primary", cd.Spec.AutoscalerRef.Name) targetSo, err := sor.flaggerClient.KedaV1alpha1().ScaledObjects(cd.Namespace).Get(context.TODO(), cd.Spec.AutoscalerRef.Name, metav1.GetOptions{}) if err != nil { return fmt.Errorf("Keda ScaledObject %s.%s get query error: %w", cd.Spec.AutoscalerRef.Name, cd.Namespace, err) } + targetSoClone := targetSo.DeepCopy() - for _, trigger := range targetSo.Spec.Triggers { + for _, trigger := range targetSoClone.Spec.Triggers { setPrimaryScaledObjectQuery(cd, trigger.Metadata) } soSpec := keda.ScaledObjectSpec{ ScaleTargetRef: &keda.ScaleTarget{ Name: primaryName, - Kind: targetSo.Spec.ScaleTargetRef.Kind, - APIVersion: targetSo.Spec.ScaleTargetRef.APIVersion, - EnvSourceContainerName: targetSo.Spec.ScaleTargetRef.EnvSourceContainerName, + Kind: targetSoClone.Spec.ScaleTargetRef.Kind, + APIVersion: targetSoClone.Spec.ScaleTargetRef.APIVersion, + EnvSourceContainerName: targetSoClone.Spec.ScaleTargetRef.EnvSourceContainerName, }, - PollingInterval: targetSo.Spec.PollingInterval, - CooldownPeriod: targetSo.Spec.CooldownPeriod, - MinReplicaCount: targetSo.Spec.MinReplicaCount, - MaxReplicaCount: targetSo.Spec.MaxReplicaCount, - Advanced: targetSo.Spec.Advanced, - Triggers: targetSo.Spec.Triggers, - Fallback: targetSo.Spec.Fallback, - IdleReplicaCount: targetSo.Spec.IdleReplicaCount, + PollingInterval: targetSoClone.Spec.PollingInterval, + CooldownPeriod: targetSoClone.Spec.CooldownPeriod, + MinReplicaCount: targetSoClone.Spec.MinReplicaCount, + MaxReplicaCount: targetSoClone.Spec.MaxReplicaCount, + Advanced: targetSoClone.Spec.Advanced, + Triggers: targetSoClone.Spec.Triggers, + Fallback: targetSoClone.Spec.Fallback, + IdleReplicaCount: targetSoClone.Spec.IdleReplicaCount, } primarySoName := fmt.Sprintf("%s-primary", cd.Spec.AutoscalerRef.Name) primarySo, err := sor.flaggerClient.KedaV1alpha1().ScaledObjects(cd.Namespace).Get(context.TODO(), primarySoName, metav1.GetOptions{}) if errors.IsNotFound(err) { primarySo = &keda.ScaledObject{ - ObjectMeta: makeObjectMeta(primarySoName, primarySo.Labels, cd), + ObjectMeta: makeObjectMeta(primarySoName, targetSoClone.Labels, cd), Spec: soSpec, } _, err = sor.flaggerClient.KedaV1alpha1().ScaledObjects(cd.Namespace).Create(context.TODO(), primarySo, metav1.CreateOptions{}) diff --git a/pkg/canary/scaled_object_reconciler_test.go b/pkg/canary/scaled_object_reconciler_test.go new file mode 100644 index 00000000..ab1a7cf7 --- /dev/null +++ b/pkg/canary/scaled_object_reconciler_test.go @@ -0,0 +1,128 @@ +package canary + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1" + keda "github.com/fluxcd/flagger/pkg/apis/keda/v1alpha1" +) + +func Test_reconcilePrimaryScaledObject(t *testing.T) { + mocks := newScalerReconcilerFixture(scalerConfig{ + targetName: "podinfo", + scaler: "ScaledObject", + }) + + soReconciler := mocks.scalerReconciler.(*ScaledObjectReconciler) + + so, err := mocks.flaggerClient.KedaV1alpha1().ScaledObjects("default").Get(context.TODO(), "podinfo", metav1.GetOptions{}) + require.NoError(t, err) + + err = soReconciler.reconcilePrimaryScaler(mocks.canary, true) + require.NoError(t, err) + + primarySO, err := mocks.flaggerClient.KedaV1alpha1().ScaledObjects("default").Get(context.TODO(), "podinfo-primary", metav1.GetOptions{}) + require.NoError(t, err) + assert.Equal(t, int(*primarySO.Spec.PollingInterval), 10) + assert.Equal(t, int(*primarySO.Spec.MinReplicaCount), 1) + assert.Equal(t, primarySO.Spec.Triggers[0].Metadata["query"], `sum(rate(http_requests_total{deployment="podinfo-primary"}[2m]))`) + + so.Spec.PollingInterval = int32p(20) + so.Spec.Triggers[0].Metadata["query"] = `sum(rate(http_requests_total{deployment="podinfo-canary"}[10m]))` + _, err = mocks.flaggerClient.KedaV1alpha1().ScaledObjects("default").Update(context.TODO(), so, metav1.UpdateOptions{}) + require.NoError(t, err) + + err = soReconciler.reconcilePrimaryScaler(mocks.canary, false) + require.NoError(t, err) + + primarySO, err = mocks.flaggerClient.KedaV1alpha1().ScaledObjects("default").Get(context.TODO(), "podinfo-primary", metav1.GetOptions{}) + require.NoError(t, err) + assert.Equal(t, int(*primarySO.Spec.PollingInterval), 20) + assert.Equal(t, primarySO.Spec.Triggers[0].Metadata["query"], `sum(rate(http_requests_total{deployment="podinfo-primary"}[10m]))`) +} + +func Test_pauseScaledObject(t *testing.T) { + mocks := newScalerReconcilerFixture(scalerConfig{ + targetName: "podinfo", + scaler: "ScaledObject", + }) + + soReconciler := mocks.scalerReconciler.(*ScaledObjectReconciler) + err := soReconciler.PauseTargetScaler(mocks.canary) + require.NoError(t, err) + + so, err := mocks.flaggerClient.KedaV1alpha1().ScaledObjects("default").Get(context.TODO(), "podinfo", metav1.GetOptions{}) + require.NoError(t, err) + assert.Equal(t, so.Annotations[keda.PausedReplicasAnnotation], "0") +} + +func Test_resumeScaledObject(t *testing.T) { + mocks := newScalerReconcilerFixture(scalerConfig{ + targetName: "podinfo", + scaler: "ScaledObject", + }) + + soReconciler := mocks.scalerReconciler.(*ScaledObjectReconciler) + err := soReconciler.ResumeTargetScaler(mocks.canary) + require.NoError(t, err) + + so, err := mocks.flaggerClient.KedaV1alpha1().ScaledObjects("default").Get(context.TODO(), "podinfo", metav1.GetOptions{}) + require.NoError(t, err) + _, exists := so.Annotations[keda.PausedReplicasAnnotation] + assert.False(t, exists) +} + +func Test_setPrimaryScaledObjectQuery(t *testing.T) { + cd := &flaggerv1.Canary{ + Spec: flaggerv1.CanarySpec{ + TargetRef: flaggerv1.LocalObjectReference{ + Name: "podinfo", + }, + AutoscalerRef: &flaggerv1.AutoscalerRefernce{ + Name: "podinfo", + }, + }, + } + tests := []struct { + name string + query string + wantQuery string + }{ + { + name: "query only has 'podinfo'", + query: `sum(rate(http_requests_total{deployment="podinfo"}[2m]))`, + wantQuery: `sum(rate(http_requests_total{deployment="podinfo-primary"}[2m]))`, + }, + { + name: "query only has 'podinfo-canary'", + query: `sum(rate(http_requests_total{deployment="podinfo-canary"}[2m]))`, + wantQuery: `sum(rate(http_requests_total{deployment="podinfo-primary"}[2m]))`, + }, + { + name: "query has both 'podinfo-canary' and 'podinfo'", + query: `sum(rate(http_requests_total{deployment="podinfo-canary", svc="podinfo"}[2m]))`, + wantQuery: `sum(rate(http_requests_total{deployment="podinfo-primary", svc="podinfo-primary"}[2m]))`, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + metadata := make(map[string]string) + metadata["query"] = test.query + setPrimaryScaledObjectQuery(cd, metadata) + assert.Equal(t, metadata["query"], test.wantQuery) + }) + } + + primaryQuery := `sum(rate(envoy_cluster_upstream_rq{ envoy_cluster_name="test_podinfo-primary_80" }[30s]))` + cd.Spec.AutoscalerRef.PrimaryScalerQuery = primaryQuery + metadata := make(map[string]string) + metadata["query"] = "" + + setPrimaryScaledObjectQuery(cd, metadata) + assert.Equal(t, primaryQuery, metadata["query"]) +} diff --git a/pkg/canary/scaler_reconciler_fixture_test.go b/pkg/canary/scaler_reconciler_fixture_test.go index 8f5fc0ed..77fa9631 100644 --- a/pkg/canary/scaler_reconciler_fixture_test.go +++ b/pkg/canary/scaler_reconciler_fixture_test.go @@ -9,6 +9,8 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" + keda "github.com/fluxcd/flagger/pkg/apis/keda/v1alpha1" + flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1" clientset "github.com/fluxcd/flagger/pkg/client/clientset/versioned" fakeFlagger "github.com/fluxcd/flagger/pkg/client/clientset/versioned/fake" @@ -31,7 +33,10 @@ type scalerConfig struct { func newScalerReconcilerFixture(cfg scalerConfig) scalerReconcilerFixture { canary := newDeploymentControllerTestCanary(canaryConfigs{targetName: cfg.targetName}) - flaggerClient := fakeFlagger.NewSimpleClientset(canary) + flaggerClient := fakeFlagger.NewSimpleClientset( + canary, + newScaledObject(), + ) kubeClient := fake.NewSimpleClientset( newScalerReconcilerTestHPAV2(), @@ -55,10 +60,18 @@ func newScalerReconcilerFixture(cfg scalerConfig) scalerReconcilerFixture { } logger, _ := logger.NewLogger("debug") - var hpaReconciler HPAReconciler + var scalerReconciler ScalerReconciler if cfg.scaler == "HorizontalPodAutoscaler" { - hpaReconciler = HPAReconciler{ + scalerReconciler = &HPAReconciler{ + kubeClient: kubeClient, + flaggerClient: flaggerClient, + logger: logger, + includeLabelPrefix: []string{"app.kubernetes.io"}, + } + } + if cfg.scaler == "ScaledObject" { + scalerReconciler = &ScaledObjectReconciler{ kubeClient: kubeClient, flaggerClient: flaggerClient, logger: logger, @@ -70,7 +83,7 @@ func newScalerReconcilerFixture(cfg scalerConfig) scalerReconcilerFixture { canary: canary, kubeClient: kubeClient, flaggerClient: flaggerClient, - scalerReconciler: &hpaReconciler, + scalerReconciler: scalerReconciler, logger: logger, } } @@ -134,3 +147,34 @@ func newScalerReconcilerTestHPAV2() *hpav2.HorizontalPodAutoscaler { return h } + +func newScaledObject() *keda.ScaledObject { + so := &keda.ScaledObject{ + TypeMeta: metav1.TypeMeta{APIVersion: keda.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo", + }, + Spec: keda.ScaledObjectSpec{ + ScaleTargetRef: &keda.ScaleTarget{ + Name: "podinfo", + }, + PollingInterval: int32p(10), + MinReplicaCount: int32p(1), + MaxReplicaCount: int32p(4), + Triggers: []keda.ScaleTriggers{ + { + Type: "prometheus", + Metadata: map[string]string{ + "serverAddress": "http://flagger-prometheus.projectcontour:9090", + "metricName": "http_requests_total", + "query": `sum(rate(http_requests_total{deployment="podinfo-canary"}[2m]))`, + "threshold": "100", + }, + }, + }, + }, + } + + return so +} diff --git a/pkg/controller/scheduler_deployment_fixture_test.go b/pkg/controller/scheduler_deployment_fixture_test.go index db54e7bc..575cfdb2 100644 --- a/pkg/controller/scheduler_deployment_fixture_test.go +++ b/pkg/controller/scheduler_deployment_fixture_test.go @@ -294,7 +294,7 @@ func newDeploymentTestCanary() *flaggerv1.Canary { APIVersion: "apps/v1", Kind: "Deployment", }, - AutoscalerRef: &flaggerv1.LocalObjectReference{ + AutoscalerRef: &flaggerv1.AutoscalerRefernce{ Name: "podinfo", APIVersion: "autoscaling/v2beta2", Kind: "HorizontalPodAutoscaler", @@ -356,7 +356,7 @@ func newDeploymentTestCanaryAB() *flaggerv1.Canary { APIVersion: "apps/v1", Kind: "Deployment", }, - AutoscalerRef: &flaggerv1.LocalObjectReference{ + AutoscalerRef: &flaggerv1.AutoscalerRefernce{ Name: "podinfo", APIVersion: "autoscaling/v2beta2", Kind: "HorizontalPodAutoscaler",