From 5af1665ef8b0d06f20f919b9cacce1f084933bd0 Mon Sep 17 00:00:00 2001 From: mathetake Date: Sun, 23 Feb 2020 13:39:11 +0900 Subject: [PATCH] pkg/controller: add unit test for daemonset target --- pkg/canary/daemonset_controller.go | 2 +- pkg/canary/daemonset_fixture_test.go | 34 - pkg/controller/controller.go | 2 +- pkg/controller/scheduler.go | 8 +- pkg/controller/scheduler_common_test.go | 10 + .../scheduler_daemonset_fixture_test.go | 694 ++++++++++++++++++ pkg/controller/scheduler_daemonset_test.go | 678 +++++++++++++++++ ...o => scheduler_deployment_fixture_test.go} | 85 +-- ...r_test.go => scheduler_deployment_test.go} | 68 +- pkg/controller/scheduler_svc_test.go | 4 +- 10 files changed, 1462 insertions(+), 123 deletions(-) create mode 100644 pkg/controller/scheduler_common_test.go create mode 100644 pkg/controller/scheduler_daemonset_fixture_test.go create mode 100644 pkg/controller/scheduler_daemonset_test.go rename pkg/controller/{fixture.go => scheduler_deployment_fixture_test.go} (91%) rename pkg/controller/{scheduler_test.go => scheduler_deployment_test.go} (91%) diff --git a/pkg/canary/daemonset_controller.go b/pkg/canary/daemonset_controller.go index 134c383e..ae9840a4 100644 --- a/pkg/canary/daemonset_controller.go +++ b/pkg/canary/daemonset_controller.go @@ -16,7 +16,7 @@ import ( ) var ( - daemonSetScaleDownNodeSelector = map[string]string{"flux.weave.works/non-exist": "true"} + daemonSetScaleDownNodeSelector = map[string]string{"flagger.weave.works/non-exist": "true"} ) // DaemonSetController is managing the operations for Kubernetes DaemonSet kind diff --git a/pkg/canary/daemonset_fixture_test.go b/pkg/canary/daemonset_fixture_test.go index 894406f9..e706556f 100644 --- a/pkg/canary/daemonset_fixture_test.go +++ b/pkg/canary/daemonset_fixture_test.go @@ -204,40 +204,6 @@ func newDaemonSetControllerTestCanary() *flaggerv1.Canary { return cd } -/*func newDaemonSetControllerWithout() *appsv1.DaemonSet { - n := "nginx-without-node-selector" - d := &appsv1.DaemonSet{ - TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: n, - }, - Spec: appsv1.DaemonSetSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "name": n, - }, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "name": n, - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: n, - Image: "nginx", - }, - }, - }, - }, - }, - } - return d -} -*/ func newDaemonSetControllerTestPodInfo() *appsv1.DaemonSet { d := &appsv1.DaemonSet{ TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()}, diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index e3677d71..5c0d1caa 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -222,7 +222,7 @@ func (c *Controller) syncHandler(key string) error { // set status condition for new canaries if cd.Status.Conditions == nil { - if ok, conditions := canary.MakeStatusConditions(cd.Status, flaggerv1.CanaryPhaseInitializing); ok { + if ok, conditions := canary.MakeStatusConditions(cd, flaggerv1.CanaryPhaseInitializing); ok { cdCopy := cd.DeepCopy() cdCopy.Status.Conditions = conditions cdCopy.Status.LastTransitionTime = metav1.Now() diff --git a/pkg/controller/scheduler.go b/pkg/controller/scheduler.go index 1fd4a54f..bfbef585 100644 --- a/pkg/controller/scheduler.go +++ b/pkg/controller/scheduler.go @@ -116,7 +116,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh return } - // create primary deployment and hpa + // create primary err = canaryController.Initialize(cd, skipLivenessChecks) if err != nil { c.recordEventWarningf(cd, "%v", err) @@ -161,7 +161,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh maxWeight = cd.Spec.CanaryAnalysis.MaxWeight } - // check primary deployment status + // check primary status if !skipLivenessChecks && !cd.Spec.SkipAnalysis { if _, err := canaryController.IsPrimaryReady(cd); err != nil { c.recordEventWarningf(cd, "%v", err) @@ -210,7 +210,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh return } - // check canary deployment status + // check canary status var retriable = true if !skipLivenessChecks { retriable, err = canaryController.IsCanaryReady(cd) @@ -617,7 +617,7 @@ func (c *Controller) checkCanaryStatus(canary *flaggerv1.Canary, canaryControlle } c.recorder.SetStatus(canary, flaggerv1.CanaryPhaseInitialized) c.recordEventInfof(canary, "Initialization done! %s.%s", canary.Name, canary.Namespace) - c.alert(canary, "New deployment detected, initialization completed.", + c.alert(canary, fmt.Sprintf("New %s detected, initialization completed.", canary.Spec.TargetRef.Kind), true, flaggerv1.SeverityInfo) return false } diff --git a/pkg/controller/scheduler_common_test.go b/pkg/controller/scheduler_common_test.go new file mode 100644 index 00000000..512a0f4c --- /dev/null +++ b/pkg/controller/scheduler_common_test.go @@ -0,0 +1,10 @@ +package controller + +func alwaysReady() bool { + return true +} + +func toFloatPtr(val int) *float64 { + v := float64(val) + return &v +} diff --git a/pkg/controller/scheduler_daemonset_fixture_test.go b/pkg/controller/scheduler_daemonset_fixture_test.go new file mode 100644 index 00000000..66c2fd15 --- /dev/null +++ b/pkg/controller/scheduler_daemonset_fixture_test.go @@ -0,0 +1,694 @@ +package controller + +import ( + "sync" + "time" + + "github.com/weaveworks/flagger/pkg/metrics/observers" + + "go.uber.org/zap" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/workqueue" + + flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1" + istiov1alpha1 "github.com/weaveworks/flagger/pkg/apis/istio/common/v1alpha1" + istiov1alpha3 "github.com/weaveworks/flagger/pkg/apis/istio/v1alpha3" + "github.com/weaveworks/flagger/pkg/canary" + clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned" + fakeFlagger "github.com/weaveworks/flagger/pkg/client/clientset/versioned/fake" + informers "github.com/weaveworks/flagger/pkg/client/informers/externalversions" + "github.com/weaveworks/flagger/pkg/logger" + "github.com/weaveworks/flagger/pkg/metrics" + "github.com/weaveworks/flagger/pkg/router" +) + +type daemonSetFixture struct { + canary *flaggerv1.Canary + kubeClient kubernetes.Interface + meshClient clientset.Interface + flaggerClient clientset.Interface + deployer canary.Controller + ctrl *Controller + logger *zap.SugaredLogger + router router.Interface +} + +func newDaemonSetFixture(c *flaggerv1.Canary) daemonSetFixture { + if c == nil { + c = newDaemonSetTestCanary() + } + + // init Flagger clientset and register objects + flaggerClient := fakeFlagger.NewSimpleClientset( + c, + newDaemonSetTestMetricTemplate(), + newDaemonSetTestAlertProvider(), + ) + + // init Kubernetes clientset and register objects + kubeClient := fake.NewSimpleClientset( + newDaemonSetTestDaemonSet(), + newDaemonSetTestService(), + newDaemonSetTestConfigMap(), + newDaemonSetTestConfigMapEnv(), + newDaemonSetTestConfigMapVol(), + newDaemonSetTestSecret(), + newDaemonSetTestSecretEnv(), + newDaemonSetTestSecretVol(), + newDaemonSetTestAlertProviderSecret(), + ) + + logger, _ := logger.NewLogger("debug") + + // init controller + flaggerInformerFactory := informers.NewSharedInformerFactory(flaggerClient, 0) + + fi := Informers{ + CanaryInformer: flaggerInformerFactory.Flagger().V1beta1().Canaries(), + MetricInformer: flaggerInformerFactory.Flagger().V1beta1().MetricTemplates(), + AlertInformer: flaggerInformerFactory.Flagger().V1beta1().AlertProviders(), + } + + // init router + rf := router.NewFactory(nil, kubeClient, flaggerClient, "annotationsPrefix", logger, flaggerClient) + + // init observer + observerFactory, _ := observers.NewFactory("fake") + + // init canary factory + configTracker := &canary.ConfigTracker{ + Logger: logger, + KubeClient: kubeClient, + FlaggerClient: flaggerClient, + } + canaryFactory := canary.NewFactory(kubeClient, flaggerClient, configTracker, []string{"app", "name"}, logger) + + ctrl := &Controller{ + kubeClient: kubeClient, + istioClient: flaggerClient, + flaggerClient: flaggerClient, + flaggerInformers: fi, + flaggerSynced: fi.CanaryInformer.Informer().HasSynced, + workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), controllerAgentName), + eventRecorder: &record.FakeRecorder{}, + logger: logger, + canaries: new(sync.Map), + flaggerWindow: time.Second, + canaryFactory: canaryFactory, + observerFactory: observerFactory, + recorder: metrics.NewRecorder(controllerAgentName, false), + routerFactory: rf, + } + ctrl.flaggerSynced = alwaysReady + ctrl.flaggerInformers.CanaryInformer.Informer().GetIndexer().Add(c) + ctrl.flaggerInformers.MetricInformer.Informer().GetIndexer().Add(newDaemonSetTestMetricTemplate()) + ctrl.flaggerInformers.AlertInformer.Informer().GetIndexer().Add(newDaemonSetTestAlertProvider()) + + meshRouter := rf.MeshRouter("istio") + + return daemonSetFixture{ + canary: c, + deployer: canaryFactory.Controller("DaemonSet"), + logger: logger, + flaggerClient: flaggerClient, + meshClient: flaggerClient, + kubeClient: kubeClient, + ctrl: ctrl, + router: meshRouter, + } +} + +func newDaemonSetTestConfigMap() *corev1.ConfigMap { + return &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo-config-env", + }, + Data: map[string]string{ + "color": "red", + }, + } +} + +func newDaemonSetTestConfigMapV2() *corev1.ConfigMap { + return &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo-config-env", + }, + Data: map[string]string{ + "color": "blue", + "output": "console", + }, + } +} + +func newDaemonSetTestConfigMapEnv() *corev1.ConfigMap { + return &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo-config-all-env", + }, + Data: map[string]string{ + "color": "red", + }, + } +} + +func newDaemonSetTestConfigMapVol() *corev1.ConfigMap { + return &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo-config-vol", + }, + Data: map[string]string{ + "color": "red", + }, + } +} + +func newDaemonSetTestSecret() *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo-secret-env", + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "apiKey": []byte("test"), + "username": []byte("test"), + "password": []byte("test"), + }, + } +} + +func newDaemonSetTestSecretV2() *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo-secret-env", + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "apiKey": []byte("test2"), + "username": []byte("test"), + "password": []byte("test"), + }, + } +} + +func newDaemonSetTestSecretEnv() *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo-secret-all-env", + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "apiKey": []byte("test"), + }, + } +} + +func newDaemonSetTestSecretVol() *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo-secret-vol", + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "apiKey": []byte("test"), + }, + } +} + +func newDaemonSetTestCanary() *flaggerv1.Canary { + cd := &flaggerv1.Canary{ + TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo", + }, + Spec: flaggerv1.CanarySpec{ + TargetRef: flaggerv1.CrossNamespaceObjectReference{ + Name: "podinfo", + APIVersion: "apps/v1", + Kind: "DaemonSet", + }, Service: flaggerv1.CanaryService{ + Port: 9898, + }, CanaryAnalysis: flaggerv1.CanaryAnalysis{ + Threshold: 10, + StepWeight: 10, + MaxWeight: 50, + Metrics: []flaggerv1.CanaryMetric{ + { + Name: "request-success-rate", + Threshold: 99, + Interval: "1m", + }, + { + Name: "request-duration", + ThresholdRange: &flaggerv1.CanaryThresholdRange{ + Min: toFloatPtr(0), + Max: toFloatPtr(500000), + }, + Interval: "1m", + }, + { + Name: "custom", + ThresholdRange: &flaggerv1.CanaryThresholdRange{ + Min: toFloatPtr(0), + Max: toFloatPtr(100), + }, + Interval: "1m", + TemplateRef: &flaggerv1.CrossNamespaceObjectReference{ + Name: "envoy", + Namespace: "default", + }, + }, + }, + }, + }, + } + return cd +} + +func newDaemonSetTestCanaryMirror() *flaggerv1.Canary { + cd := newDaemonSetTestCanary() + cd.Spec.CanaryAnalysis.Mirror = true + return cd +} + +func newDaemonSetTestCanaryAB() *flaggerv1.Canary { + cd := &flaggerv1.Canary{ + TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo", + }, + Spec: flaggerv1.CanarySpec{ + TargetRef: flaggerv1.CrossNamespaceObjectReference{ + Name: "podinfo", + APIVersion: "apps/v1", + Kind: "DaemonSet", + }, Service: flaggerv1.CanaryService{ + Port: 9898, + }, CanaryAnalysis: flaggerv1.CanaryAnalysis{ + Threshold: 10, + Iterations: 10, + Match: []istiov1alpha3.HTTPMatchRequest{ + { + Headers: map[string]istiov1alpha1.StringMatch{ + "x-user-type": { + Exact: "test", + }, + }, + }, + }, + Metrics: []flaggerv1.CanaryMetric{ + { + Name: "request-success-rate", + ThresholdRange: &flaggerv1.CanaryThresholdRange{ + Min: toFloatPtr(99), + Max: toFloatPtr(100), + }, + Interval: "1m", + }, + { + Name: "request-duration", + Threshold: 500000, + Interval: "1m", + }, + { + Name: "custom", + ThresholdRange: &flaggerv1.CanaryThresholdRange{ + Min: toFloatPtr(0), + Max: toFloatPtr(500000), + }, + Interval: "1m", + Query: "fake", + }, + }, + }, + }, + } + return cd +} + +func newDaemonSetTestDaemonSet() *appsv1.DaemonSet { + d := &appsv1.DaemonSet{ + TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo", + }, + Spec: appsv1.DaemonSetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "podinfo", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app": "podinfo", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "podinfo", + Image: "quay.io/stefanprodan/podinfo:1.2.0", + Command: []string{ + "./podinfo", + "--port=9898", + }, + Args: nil, + WorkingDir: "", + Ports: []corev1.ContainerPort{ + { + Name: "http", + ContainerPort: 9898, + Protocol: corev1.ProtocolTCP, + }, + { + Name: "http-metrics", + ContainerPort: 8080, + Protocol: corev1.ProtocolTCP, + }, + { + ContainerPort: 8888, + }, + }, + Env: []corev1.EnvVar{ + { + Name: "PODINFO_UI_COLOR", + ValueFrom: &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-config-env", + }, + Key: "color", + }, + }, + }, + { + Name: "API_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-secret-env", + }, + Key: "apiKey", + }, + }, + }, + }, + EnvFrom: []corev1.EnvFromSource{ + { + ConfigMapRef: &corev1.ConfigMapEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-config-all-env", + }, + }, + }, + { + SecretRef: &corev1.SecretEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-secret-all-env", + }, + }, + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "config", + MountPath: "/etc/podinfo/config", + ReadOnly: true, + }, + { + Name: "secret", + MountPath: "/etc/podinfo/secret", + ReadOnly: true, + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-config-vol", + }, + }, + }, + }, + { + Name: "secret", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "podinfo-secret-vol", + }, + }, + }, + }, + }, + }, + }, + } + + return d +} + +func newDaemonSetTestDaemonSetV2() *appsv1.DaemonSet { + d := &appsv1.DaemonSet{ + TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo", + }, + Spec: appsv1.DaemonSetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "podinfo", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app": "podinfo", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "podinfo", + Image: "quay.io/stefanprodan/podinfo:1.2.1", + Ports: []corev1.ContainerPort{ + { + Name: "http", + ContainerPort: 9898, + Protocol: corev1.ProtocolTCP, + }, + }, + Command: []string{ + "./podinfo", + "--port=9898", + }, + Env: []corev1.EnvVar{ + { + Name: "PODINFO_UI_COLOR", + ValueFrom: &corev1.EnvVarSource{ + ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-config-env", + }, + Key: "color", + }, + }, + }, + { + Name: "API_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-secret-env", + }, + Key: "apiKey", + }, + }, + }, + }, + EnvFrom: []corev1.EnvFromSource{ + { + ConfigMapRef: &corev1.ConfigMapEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-config-all-env", + }, + }, + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "config", + MountPath: "/etc/podinfo/config", + ReadOnly: true, + }, + { + Name: "secret", + MountPath: "/etc/podinfo/secret", + ReadOnly: true, + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "podinfo-config-vol", + }, + }, + }, + }, + { + Name: "secret", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "podinfo-secret-vol", + }, + }, + }, + }, + }, + }, + }, + } + + return d +} + +func newDaemonSetTestService() *corev1.Service { + d := &corev1.Service{ + TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo", + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{ + "app": "podinfo", + }, + Type: corev1.ServiceTypeClusterIP, + Ports: []corev1.ServicePort{ + { + Name: "http", + Port: 9898, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromString("http"), + }, + }, + }, + } + + return d +} + +func newDaemonSetTestServiceV2() *corev1.Service { + d := &corev1.Service{ + TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo", + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{ + "app": "podinfo-v2", + }, + Type: corev1.ServiceTypeClusterIP, + Ports: []corev1.ServicePort{ + { + Name: "http", + Port: 9898, + Protocol: corev1.ProtocolTCP, + TargetPort: intstr.FromString("http"), + }, + }, + }, + } + + return d +} + +func newDaemonSetTestMetricTemplate() *flaggerv1.MetricTemplate { + provider := flaggerv1.MetricTemplateProvider{ + Type: "prometheus", + Address: "fake", + SecretRef: &corev1.LocalObjectReference{ + Name: "podinfo-secret-env", + }, + } + + template := &flaggerv1.MetricTemplate{ + TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "envoy", + }, + Spec: flaggerv1.MetricTemplateSpec{ + Provider: provider, + Query: `sum(envoy_cluster_upstream_rq{envoy_cluster_name=~"{{ namespace }}_{{ target }}"})`, + }, + } + return template +} + +func newDaemonSetTestAlertProviderSecret() *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "alert-secret", + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "address": []byte("http://mock.slack"), + }, + } +} + +func newDaemonSetTestAlertProvider() *flaggerv1.AlertProvider { + return &flaggerv1.AlertProvider{ + TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "slack", + }, + Spec: flaggerv1.AlertProviderSpec{ + Type: "slack", + Address: "http://fake.slack", + SecretRef: &corev1.LocalObjectReference{ + Name: "alert-secret", + }, + }, + } +} diff --git a/pkg/controller/scheduler_daemonset_test.go b/pkg/controller/scheduler_daemonset_test.go new file mode 100644 index 00000000..89dfef51 --- /dev/null +++ b/pkg/controller/scheduler_daemonset_test.go @@ -0,0 +1,678 @@ +package controller + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1" + "github.com/weaveworks/flagger/pkg/notifier" +) + +func TestScheduler_DaemonSetInit(t *testing.T) { + mocks := newDaemonSetFixture(nil) + mocks.ctrl.advanceCanary("podinfo", "default", true) + + _, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } +} + +func TestScheduler_DaemonSetNewRevision(t *testing.T) { + mocks := newDaemonSetFixture(nil) + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // update + dep2 := newDaemonSetTestDaemonSetV2() + _, err := mocks.kubeClient.AppsV1().DaemonSets("default").Update(dep2) + if err != nil { + t.Fatal(err.Error()) + } + + // detect changes + mocks.ctrl.advanceCanary("podinfo", "default", true) + + _, err = mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } +} + +func TestScheduler_DaemonSetRollback(t *testing.T) { + mocks := newDaemonSetFixture(nil) + // init + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // update failed checks to max + err := mocks.deployer.SyncStatus(mocks.canary, flaggerv1.CanaryStatus{Phase: flaggerv1.CanaryPhaseProgressing, FailedChecks: 10}) + if err != nil { + t.Fatal(err.Error()) + } + + // set a metric check to fail + c, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + cd := c.DeepCopy() + cd.Spec.CanaryAnalysis.Metrics = append(c.Spec.CanaryAnalysis.Metrics, flaggerv1.CanaryMetric{ + Name: "fail", + Interval: "1m", + ThresholdRange: &flaggerv1.CanaryThresholdRange{ + Min: toFloatPtr(0), + Max: toFloatPtr(50), + }, + Query: "fail", + }) + _, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Update(cd) + if err != nil { + t.Fatal(err.Error()) + } + + // run metric checks + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // finalise analysis + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // check status + c, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if c.Status.Phase != flaggerv1.CanaryPhaseFailed { + t.Errorf("Got canary state %v wanted %v", c.Status.Phase, flaggerv1.CanaryPhaseFailed) + } +} + +func TestScheduler_DaemonSetSkipAnalysis(t *testing.T) { + mocks := newDaemonSetFixture(nil) + // init + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // enable skip + cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + cd.Spec.SkipAnalysis = true + _, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Update(cd) + if err != nil { + t.Fatal(err.Error()) + } + + // update + dep2 := newDaemonSetTestDaemonSetV2() + _, err = mocks.kubeClient.AppsV1().DaemonSets("default").Update(dep2) + if err != nil { + t.Fatal(err.Error()) + } + + // detect changes + mocks.ctrl.advanceCanary("podinfo", "default", true) + // advance + mocks.ctrl.advanceCanary("podinfo", "default", true) + + c, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + if !c.Spec.SkipAnalysis { + t.Errorf("Got skip analysis %v wanted %v", c.Spec.SkipAnalysis, true) + } + + if c.Status.Phase != flaggerv1.CanaryPhaseSucceeded { + t.Errorf("Got canary state %v wanted %v", c.Status.Phase, flaggerv1.CanaryPhaseSucceeded) + } +} + +func TestScheduler_DaemonSetNewRevisionReset(t *testing.T) { + mocks := newDaemonSetFixture(nil) + // init + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // first update + dep2 := newDaemonSetTestDaemonSetV2() + _, err := mocks.kubeClient.AppsV1().DaemonSets("default").Update(dep2) + if err != nil { + t.Fatal(err.Error()) + } + + // detect changes + mocks.ctrl.advanceCanary("podinfo", "default", true) + // advance + mocks.ctrl.advanceCanary("podinfo", "default", true) + + primaryWeight, canaryWeight, mirrored, err := mocks.router.GetRoutes(mocks.canary) + if err != nil { + t.Fatal(err.Error()) + } + + if primaryWeight != 90 { + t.Errorf("Got primary route %v wanted %v", primaryWeight, 90) + } + + if canaryWeight != 10 { + t.Errorf("Got canary route %v wanted %v", canaryWeight, 10) + } + + if mirrored != false { + t.Errorf("Got mirrored %v wanted %v", mirrored, false) + } + + // second update + dep2.Spec.Template.Spec.ServiceAccountName = "test" + _, err = mocks.kubeClient.AppsV1().DaemonSets("default").Update(dep2) + if err != nil { + t.Fatal(err.Error()) + } + + // detect changes + mocks.ctrl.advanceCanary("podinfo", "default", true) + + primaryWeight, canaryWeight, mirrored, err = mocks.router.GetRoutes(mocks.canary) + if err != nil { + t.Fatal(err.Error()) + } + + if primaryWeight != 100 { + t.Errorf("Got primary route %v wanted %v", primaryWeight, 100) + } + + if canaryWeight != 0 { + t.Errorf("Got canary route %v wanted %v", canaryWeight, 0) + } + + if mirrored != false { + t.Errorf("Got mirrored %v wanted %v", mirrored, false) + } +} + +func TestScheduler_DaemonSetPromotion(t *testing.T) { + mocks := newDaemonSetFixture(nil) + + // init + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // check initialized status + c, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if c.Status.Phase != flaggerv1.CanaryPhaseInitialized { + t.Errorf("Got canary state %v wanted %v", c.Status.Phase, flaggerv1.CanaryPhaseInitialized) + } + + // update + dep2 := newDaemonSetTestDaemonSetV2() + _, err = mocks.kubeClient.AppsV1().DaemonSets("default").Update(dep2) + if err != nil { + t.Fatal(err.Error()) + } + + // detect pod spec changes + mocks.ctrl.advanceCanary("podinfo", "default", true) + + config2 := newDaemonSetTestConfigMapV2() + _, err = mocks.kubeClient.CoreV1().ConfigMaps("default").Update(config2) + if err != nil { + t.Fatal(err.Error()) + } + + secret2 := newDaemonSetTestSecretV2() + _, err = mocks.kubeClient.CoreV1().Secrets("default").Update(secret2) + if err != nil { + t.Fatal(err.Error()) + } + + // detect configs changes + mocks.ctrl.advanceCanary("podinfo", "default", true) + + primaryWeight, canaryWeight, mirrored, err := mocks.router.GetRoutes(mocks.canary) + if err != nil { + t.Fatal(err.Error()) + } + + primaryWeight = 60 + canaryWeight = 40 + err = mocks.router.SetRoutes(mocks.canary, primaryWeight, canaryWeight, mirrored) + if err != nil { + t.Fatal(err.Error()) + } + + // advance + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // check progressing status + c, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if c.Status.Phase != flaggerv1.CanaryPhaseProgressing { + t.Errorf("Got canary state %v wanted %v", c.Status.Phase, flaggerv1.CanaryPhaseProgressing) + } + + // promote + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // check promoting status + c, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if c.Status.Phase != flaggerv1.CanaryPhasePromoting { + t.Errorf("Got canary state %v wanted %v", c.Status.Phase, flaggerv1.CanaryPhasePromoting) + } + + // finalise + mocks.ctrl.advanceCanary("podinfo", "default", true) + + primaryWeight, canaryWeight, mirrored, err = mocks.router.GetRoutes(mocks.canary) + if err != nil { + t.Fatal(err.Error()) + } + + if primaryWeight != 100 { + t.Errorf("Got primary route %v wanted %v", primaryWeight, 100) + } + + if canaryWeight != 0 { + t.Errorf("Got canary route %v wanted %v", canaryWeight, 0) + } + + if mirrored != false { + t.Errorf("Got mirrored %v wanted %v", mirrored, false) + } + + primaryDep, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + primaryImage := primaryDep.Spec.Template.Spec.Containers[0].Image + canaryImage := dep2.Spec.Template.Spec.Containers[0].Image + if primaryImage != canaryImage { + t.Errorf("Got primary image %v wanted %v", primaryImage, canaryImage) + } + + configPrimary, err := mocks.kubeClient.CoreV1().ConfigMaps("default").Get("podinfo-config-env-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if configPrimary.Data["color"] != config2.Data["color"] { + t.Errorf("Got primary ConfigMap color %s wanted %s", configPrimary.Data["color"], config2.Data["color"]) + } + + secretPrimary, err := mocks.kubeClient.CoreV1().Secrets("default").Get("podinfo-secret-env-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if string(secretPrimary.Data["apiKey"]) != string(secret2.Data["apiKey"]) { + t.Errorf("Got primary secret %s wanted %s", secretPrimary.Data["apiKey"], secret2.Data["apiKey"]) + } + + // check finalising status + c, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if c.Status.Phase != flaggerv1.CanaryPhaseFinalising { + t.Errorf("Got canary state %v wanted %v", c.Status.Phase, flaggerv1.CanaryPhaseFinalising) + } + + // scale canary to zero + mocks.ctrl.advanceCanary("podinfo", "default", true) + + c, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if c.Status.Phase != flaggerv1.CanaryPhaseSucceeded { + t.Errorf("Got canary state %v wanted %v", c.Status.Phase, flaggerv1.CanaryPhaseSucceeded) + } +} + +func TestScheduler_DaemonSetMirroring(t *testing.T) { + mocks := newDaemonSetFixture(newDaemonSetTestCanaryMirror()) + // init + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // update + dep2 := newDaemonSetTestDaemonSetV2() + _, err := mocks.kubeClient.AppsV1().DaemonSets("default").Update(dep2) + if err != nil { + t.Fatal(err.Error()) + } + + // detect pod spec changes + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // advance + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // check if traffic is mirrored to canary + primaryWeight, canaryWeight, mirrored, err := mocks.router.GetRoutes(mocks.canary) + if err != nil { + t.Fatal(err.Error()) + } + + if primaryWeight != 100 { + t.Errorf("Got primary route %v wanted %v", primaryWeight, 100) + } + + if canaryWeight != 0 { + t.Errorf("Got canary route %v wanted %v", canaryWeight, 0) + } + + if mirrored != true { + t.Errorf("Got mirrored %v wanted %v", mirrored, true) + } + + // advance + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // check if traffic is mirrored to canary + primaryWeight, canaryWeight, mirrored, err = mocks.router.GetRoutes(mocks.canary) + if err != nil { + t.Fatal(err.Error()) + } + + if primaryWeight != 90 { + t.Errorf("Got primary route %v wanted %v", primaryWeight, 90) + } + + if canaryWeight != 10 { + t.Errorf("Got canary route %v wanted %v", canaryWeight, 10) + } + + if mirrored != false { + t.Errorf("Got mirrored %v wanted %v", mirrored, false) + } +} + +func TestScheduler_DaemonSetABTesting(t *testing.T) { + mocks := newDaemonSetFixture(newDaemonSetTestCanaryAB()) + // init + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // update + dep2 := newDaemonSetTestDaemonSetV2() + _, err := mocks.kubeClient.AppsV1().DaemonSets("default").Update(dep2) + if err != nil { + t.Fatal(err.Error()) + } + + // detect pod spec changes + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // advance + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // check if traffic is routed to canary + primaryWeight, canaryWeight, mirrored, err := mocks.router.GetRoutes(mocks.canary) + if err != nil { + t.Fatal(err.Error()) + } + + if primaryWeight != 0 { + t.Errorf("Got primary route %v wanted %v", primaryWeight, 0) + } + + if canaryWeight != 100 { + t.Errorf("Got canary route %v wanted %v", canaryWeight, 100) + } + + if mirrored != false { + t.Errorf("Got mirrored %v wanted %v", mirrored, false) + } + + cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + // set max iterations + if err := mocks.deployer.SetStatusIterations(cd, 10); err != nil { + t.Fatal(err.Error()) + } + + // advance + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // finalising + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // check finalising status + c, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if c.Status.Phase != flaggerv1.CanaryPhaseFinalising { + t.Errorf("Got canary state %v wanted %v", c.Status.Phase, flaggerv1.CanaryPhaseFinalising) + } + + // check if the container image tag was updated + primaryDep, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo-primary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + primaryImage := primaryDep.Spec.Template.Spec.Containers[0].Image + canaryImage := dep2.Spec.Template.Spec.Containers[0].Image + if primaryImage != canaryImage { + t.Errorf("Got primary image %v wanted %v", primaryImage, canaryImage) + } + + // shutdown canary + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // check rollout status + c, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if c.Status.Phase != flaggerv1.CanaryPhaseSucceeded { + t.Errorf("Got canary state %v wanted %v", c.Status.Phase, flaggerv1.CanaryPhaseSucceeded) + } +} + +func TestScheduler_DaemonSetPortDiscovery(t *testing.T) { + mocks := newDaemonSetFixture(nil) + + // enable port discovery + cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + cd.Spec.Service.PortDiscovery = true + _, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Update(cd) + if err != nil { + t.Fatal(err.Error()) + } + + mocks.ctrl.advanceCanary("podinfo", "default", true) + + canarySvc, err := mocks.kubeClient.CoreV1().Services("default").Get("podinfo-canary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if len(canarySvc.Spec.Ports) != 3 { + t.Fatalf("Got svc port count %v wanted %v", len(canarySvc.Spec.Ports), 3) + } + + matchPorts := func(lookup string) bool { + switch lookup { + case + "http 9898", + "http-metrics 8080", + "tcp-podinfo-2 8888": + return true + } + return false + } + + for _, port := range canarySvc.Spec.Ports { + if !matchPorts(fmt.Sprintf("%s %v", port.Name, port.Port)) { + t.Fatalf("Got wrong svc port %v", port.Name) + } + + } +} + +func TestScheduler_DaemonSetTargetPortNumber(t *testing.T) { + mocks := newDaemonSetFixture(nil) + + cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + cd.Spec.Service.Port = 80 + cd.Spec.Service.TargetPort = intstr.FromInt(9898) + cd.Spec.Service.PortDiscovery = true + _, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Update(cd) + if err != nil { + t.Fatal(err.Error()) + } + + mocks.ctrl.advanceCanary("podinfo", "default", true) + + canarySvc, err := mocks.kubeClient.CoreV1().Services("default").Get("podinfo-canary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if len(canarySvc.Spec.Ports) != 3 { + t.Fatalf("Got svc port count %v wanted %v", len(canarySvc.Spec.Ports), 3) + } + + matchPorts := func(lookup string) bool { + switch lookup { + case + "http 80", + "http-metrics 8080", + "tcp-podinfo-2 8888": + return true + } + return false + } + + for _, port := range canarySvc.Spec.Ports { + if !matchPorts(fmt.Sprintf("%s %v", port.Name, port.Port)) { + t.Fatalf("Got wrong svc port %v", port.Name) + } + + } +} + +func TestScheduler_DaemonSetTargetPortName(t *testing.T) { + mocks := newDaemonSetFixture(nil) + + cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + cd.Spec.Service.Port = 8080 + cd.Spec.Service.TargetPort = intstr.FromString("http") + cd.Spec.Service.PortDiscovery = true + _, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Update(cd) + if err != nil { + t.Fatal(err.Error()) + } + + mocks.ctrl.advanceCanary("podinfo", "default", true) + + canarySvc, err := mocks.kubeClient.CoreV1().Services("default").Get("podinfo-canary", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if len(canarySvc.Spec.Ports) != 3 { + t.Fatalf("Got svc port count %v wanted %v", len(canarySvc.Spec.Ports), 3) + } + + matchPorts := func(lookup string) bool { + switch lookup { + case + "http 8080", + "http-metrics 8080", + "tcp-podinfo-2 8888": + return true + } + return false + } + + for _, port := range canarySvc.Spec.Ports { + if !matchPorts(fmt.Sprintf("%s %v", port.Name, port.Port)) { + t.Fatalf("Got wrong svc port %v", port.Name) + } + + } +} + +func TestScheduler_DaemonSetAlerts(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + var payload = notifier.SlackPayload{} + err = json.Unmarshal(b, &payload) + if err != nil { + t.Fatal(err) + } + if payload.Attachments[0].AuthorName != "podinfo.default" { + t.Fatal("wrong author name") + } + })) + defer ts.Close() + + canary := newDaemonSetTestCanary() + canary.Spec.CanaryAnalysis.Alerts = []flaggerv1.CanaryAlert{ + { + Name: "slack-dev", + Severity: "info", + ProviderRef: flaggerv1.CrossNamespaceObjectReference{ + Name: "slack", + Namespace: "default", + }, + }, + { + Name: "slack-prod", + Severity: "info", + ProviderRef: flaggerv1.CrossNamespaceObjectReference{ + Name: "slack", + }, + }, + } + mocks := newDaemonSetFixture(canary) + + secret := newDaemonSetTestAlertProviderSecret() + secret.Data = map[string][]byte{ + "address": []byte(ts.URL), + } + _, err := mocks.kubeClient.CoreV1().Secrets("default").Update(secret) + if err != nil { + t.Fatal(err.Error()) + } + + // init canary and send alerts + mocks.ctrl.advanceCanary("podinfo", "default", true) +} diff --git a/pkg/controller/fixture.go b/pkg/controller/scheduler_deployment_fixture_test.go similarity index 91% rename from pkg/controller/fixture.go rename to pkg/controller/scheduler_deployment_fixture_test.go index 8b5c94ad..666a4ab8 100644 --- a/pkg/controller/fixture.go +++ b/pkg/controller/scheduler_deployment_fixture_test.go @@ -1,10 +1,11 @@ package controller import ( - "github.com/weaveworks/flagger/pkg/metrics/observers" "sync" "time" + "github.com/weaveworks/flagger/pkg/metrics/observers" + "go.uber.org/zap" appsv1 "k8s.io/api/apps/v1" hpav2 "k8s.io/api/autoscaling/v2beta1" @@ -28,11 +29,6 @@ import ( "github.com/weaveworks/flagger/pkg/router" ) -var ( - alwaysReady = func() bool { return true } - noResyncPeriodFunc = func() time.Duration { return 0 } -) - type fixture struct { canary *flaggerv1.Canary kubeClient kubernetes.Interface @@ -44,30 +40,30 @@ type fixture struct { router router.Interface } -func newFixture(c *flaggerv1.Canary) fixture { +func newDeploymentFixture(c *flaggerv1.Canary) fixture { if c == nil { - c = newTestCanary() + c = newDeploymentTestCanary() } // init Flagger clientset and register objects flaggerClient := fakeFlagger.NewSimpleClientset( c, - newTestMetricTemplate(), - newTestAlertProvider(), + newDeploymentTestMetricTemplate(), + newDeploymentTestAlertProvider(), ) // init Kubernetes clientset and register objects kubeClient := fake.NewSimpleClientset( - newTestDeployment(), - newTestService(), - newTestHPA(), - newTestConfigMap(), - newTestConfigMapEnv(), - newTestConfigMapVol(), - newTestSecret(), - newTestSecretEnv(), - newTestSecretVol(), - newTestAlertProviderSecret(), + newDeploymentTestDeployment(), + newDeploymentTestService(), + newDeploymentTestHPA(), + newDeploymentTestConfigMap(), + newDeploymentTestConfigMapEnv(), + newDeploymentTestConfigMapVol(), + newDeploymentTestSecret(), + newDeploymentTestSecretEnv(), + newDeploymentTestSecretVol(), + newDeploymentTestAlertProviderSecret(), ) logger, _ := logger.NewLogger("debug") @@ -113,8 +109,8 @@ func newFixture(c *flaggerv1.Canary) fixture { } ctrl.flaggerSynced = alwaysReady ctrl.flaggerInformers.CanaryInformer.Informer().GetIndexer().Add(c) - ctrl.flaggerInformers.MetricInformer.Informer().GetIndexer().Add(newTestMetricTemplate()) - ctrl.flaggerInformers.AlertInformer.Informer().GetIndexer().Add(newTestAlertProvider()) + ctrl.flaggerInformers.MetricInformer.Informer().GetIndexer().Add(newDeploymentTestMetricTemplate()) + ctrl.flaggerInformers.AlertInformer.Informer().GetIndexer().Add(newDeploymentTestAlertProvider()) meshRouter := rf.MeshRouter("istio") @@ -130,7 +126,7 @@ func newFixture(c *flaggerv1.Canary) fixture { } } -func newTestConfigMap() *corev1.ConfigMap { +func newDeploymentTestConfigMap() *corev1.ConfigMap { return &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -143,7 +139,7 @@ func newTestConfigMap() *corev1.ConfigMap { } } -func newTestConfigMapV2() *corev1.ConfigMap { +func newDeploymentTestConfigMapV2() *corev1.ConfigMap { return &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -157,7 +153,7 @@ func newTestConfigMapV2() *corev1.ConfigMap { } } -func newTestConfigMapEnv() *corev1.ConfigMap { +func newDeploymentTestConfigMapEnv() *corev1.ConfigMap { return &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -170,7 +166,7 @@ func newTestConfigMapEnv() *corev1.ConfigMap { } } -func newTestConfigMapVol() *corev1.ConfigMap { +func newDeploymentTestConfigMapVol() *corev1.ConfigMap { return &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -183,7 +179,7 @@ func newTestConfigMapVol() *corev1.ConfigMap { } } -func newTestSecret() *corev1.Secret { +func newDeploymentTestSecret() *corev1.Secret { return &corev1.Secret{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -199,7 +195,7 @@ func newTestSecret() *corev1.Secret { } } -func newTestSecretV2() *corev1.Secret { +func newDeploymentTestSecretV2() *corev1.Secret { return &corev1.Secret{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -215,7 +211,7 @@ func newTestSecretV2() *corev1.Secret { } } -func newTestSecretEnv() *corev1.Secret { +func newDeploymentTestSecretEnv() *corev1.Secret { return &corev1.Secret{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -229,7 +225,7 @@ func newTestSecretEnv() *corev1.Secret { } } -func newTestSecretVol() *corev1.Secret { +func newDeploymentTestSecretVol() *corev1.Secret { return &corev1.Secret{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -243,7 +239,7 @@ func newTestSecretVol() *corev1.Secret { } } -func newTestCanary() *flaggerv1.Canary { +func newDeploymentTestCanary() *flaggerv1.Canary { cd := &flaggerv1.Canary{ TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -299,18 +295,13 @@ func newTestCanary() *flaggerv1.Canary { return cd } -func toFloatPtr(val int) *float64 { - v := float64(val) - return &v -} - -func newTestCanaryMirror() *flaggerv1.Canary { - cd := newTestCanary() +func newDeploymentTestCanaryMirror() *flaggerv1.Canary { + cd := newDeploymentTestCanary() cd.Spec.CanaryAnalysis.Mirror = true return cd } -func newTestCanaryAB() *flaggerv1.Canary { +func newDeploymentTestCanaryAB() *flaggerv1.Canary { cd := &flaggerv1.Canary{ TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -371,7 +362,7 @@ func newTestCanaryAB() *flaggerv1.Canary { return cd } -func newTestDeployment() *appsv1.Deployment { +func newDeploymentTestDeployment() *appsv1.Deployment { d := &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -498,7 +489,7 @@ func newTestDeployment() *appsv1.Deployment { return d } -func newTestDeploymentV2() *appsv1.Deployment { +func newDeploymentTestDeploymentV2() *appsv1.Deployment { d := &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -608,7 +599,7 @@ func newTestDeploymentV2() *appsv1.Deployment { return d } -func newTestService() *corev1.Service { +func newDeploymentTestService() *corev1.Service { d := &corev1.Service{ TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -634,7 +625,7 @@ func newTestService() *corev1.Service { return d } -func newTestServiceV2() *corev1.Service { +func newDeploymentTestServiceV2() *corev1.Service { d := &corev1.Service{ TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -660,7 +651,7 @@ func newTestServiceV2() *corev1.Service { return d } -func newTestHPA() *hpav2.HorizontalPodAutoscaler { +func newDeploymentTestHPA() *hpav2.HorizontalPodAutoscaler { h := &hpav2.HorizontalPodAutoscaler{ TypeMeta: metav1.TypeMeta{APIVersion: hpav2.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -688,7 +679,7 @@ func newTestHPA() *hpav2.HorizontalPodAutoscaler { return h } -func newTestMetricTemplate() *flaggerv1.MetricTemplate { +func newDeploymentTestMetricTemplate() *flaggerv1.MetricTemplate { provider := flaggerv1.MetricTemplateProvider{ Type: "prometheus", Address: "fake", @@ -711,7 +702,7 @@ func newTestMetricTemplate() *flaggerv1.MetricTemplate { return template } -func newTestAlertProviderSecret() *corev1.Secret { +func newDeploymentTestAlertProviderSecret() *corev1.Secret { return &corev1.Secret{ TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ @@ -725,7 +716,7 @@ func newTestAlertProviderSecret() *corev1.Secret { } } -func newTestAlertProvider() *flaggerv1.AlertProvider { +func newDeploymentTestAlertProvider() *flaggerv1.AlertProvider { return &flaggerv1.AlertProvider{ TypeMeta: metav1.TypeMeta{APIVersion: flaggerv1.SchemeGroupVersion.String()}, ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/controller/scheduler_test.go b/pkg/controller/scheduler_deployment_test.go similarity index 91% rename from pkg/controller/scheduler_test.go rename to pkg/controller/scheduler_deployment_test.go index f49a44f5..da72f375 100644 --- a/pkg/controller/scheduler_test.go +++ b/pkg/controller/scheduler_deployment_test.go @@ -15,8 +15,8 @@ import ( "github.com/weaveworks/flagger/pkg/notifier" ) -func TestScheduler_Init(t *testing.T) { - mocks := newFixture(nil) +func TestScheduler_DeploymentInit(t *testing.T) { + mocks := newDeploymentFixture(nil) mocks.ctrl.advanceCanary("podinfo", "default", true) _, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo-primary", metav1.GetOptions{}) @@ -25,12 +25,12 @@ func TestScheduler_Init(t *testing.T) { } } -func TestScheduler_NewRevision(t *testing.T) { - mocks := newFixture(nil) +func TestScheduler_DeploymentNewRevision(t *testing.T) { + mocks := newDeploymentFixture(nil) mocks.ctrl.advanceCanary("podinfo", "default", true) // update - dep2 := newTestDeploymentV2() + dep2 := newDeploymentTestDeploymentV2() _, err := mocks.kubeClient.AppsV1().Deployments("default").Update(dep2) if err != nil { t.Fatal(err.Error()) @@ -49,8 +49,8 @@ func TestScheduler_NewRevision(t *testing.T) { } } -func TestScheduler_Rollback(t *testing.T) { - mocks := newFixture(nil) +func TestScheduler_DeploymentRollback(t *testing.T) { + mocks := newDeploymentFixture(nil) // init mocks.ctrl.advanceCanary("podinfo", "default", true) @@ -103,8 +103,8 @@ func TestScheduler_Rollback(t *testing.T) { } } -func TestScheduler_SkipAnalysis(t *testing.T) { - mocks := newFixture(nil) +func TestScheduler_DeploymentSkipAnalysis(t *testing.T) { + mocks := newDeploymentFixture(nil) // init mocks.ctrl.advanceCanary("podinfo", "default", true) @@ -120,7 +120,7 @@ func TestScheduler_SkipAnalysis(t *testing.T) { } // update - dep2 := newTestDeploymentV2() + dep2 := newDeploymentTestDeploymentV2() _, err = mocks.kubeClient.AppsV1().Deployments("default").Update(dep2) if err != nil { t.Fatal(err.Error()) @@ -144,13 +144,13 @@ func TestScheduler_SkipAnalysis(t *testing.T) { } } -func TestScheduler_NewRevisionReset(t *testing.T) { - mocks := newFixture(nil) +func TestScheduler_DeploymentNewRevisionReset(t *testing.T) { + mocks := newDeploymentFixture(nil) // init mocks.ctrl.advanceCanary("podinfo", "default", true) // first update - dep2 := newTestDeploymentV2() + dep2 := newDeploymentTestDeploymentV2() _, err := mocks.kubeClient.AppsV1().Deployments("default").Update(dep2) if err != nil { t.Fatal(err.Error()) @@ -206,8 +206,8 @@ func TestScheduler_NewRevisionReset(t *testing.T) { } } -func TestScheduler_Promotion(t *testing.T) { - mocks := newFixture(nil) +func TestScheduler_DeploymentPromotion(t *testing.T) { + mocks := newDeploymentFixture(nil) // init mocks.ctrl.advanceCanary("podinfo", "default", true) @@ -223,7 +223,7 @@ func TestScheduler_Promotion(t *testing.T) { } // update - dep2 := newTestDeploymentV2() + dep2 := newDeploymentTestDeploymentV2() _, err = mocks.kubeClient.AppsV1().Deployments("default").Update(dep2) if err != nil { t.Fatal(err.Error()) @@ -232,13 +232,13 @@ func TestScheduler_Promotion(t *testing.T) { // detect pod spec changes mocks.ctrl.advanceCanary("podinfo", "default", true) - config2 := newTestConfigMapV2() + config2 := newDeploymentTestConfigMapV2() _, err = mocks.kubeClient.CoreV1().ConfigMaps("default").Update(config2) if err != nil { t.Fatal(err.Error()) } - secret2 := newTestSecretV2() + secret2 := newDeploymentTestSecretV2() _, err = mocks.kubeClient.CoreV1().Secrets("default").Update(secret2) if err != nil { t.Fatal(err.Error()) @@ -357,13 +357,13 @@ func TestScheduler_Promotion(t *testing.T) { } } -func TestScheduler_Mirroring(t *testing.T) { - mocks := newFixture(newTestCanaryMirror()) +func TestScheduler_DeploymentMirroring(t *testing.T) { + mocks := newDeploymentFixture(newDeploymentTestCanaryMirror()) // init mocks.ctrl.advanceCanary("podinfo", "default", true) // update - dep2 := newTestDeploymentV2() + dep2 := newDeploymentTestDeploymentV2() _, err := mocks.kubeClient.AppsV1().Deployments("default").Update(dep2) if err != nil { t.Fatal(err.Error()) @@ -415,13 +415,13 @@ func TestScheduler_Mirroring(t *testing.T) { } } -func TestScheduler_ABTesting(t *testing.T) { - mocks := newFixture(newTestCanaryAB()) +func TestScheduler_DeploymentABTesting(t *testing.T) { + mocks := newDeploymentFixture(newDeploymentTestCanaryAB()) // init mocks.ctrl.advanceCanary("podinfo", "default", true) // update - dep2 := newTestDeploymentV2() + dep2 := newDeploymentTestDeploymentV2() _, err := mocks.kubeClient.AppsV1().Deployments("default").Update(dep2) if err != nil { t.Fatal(err.Error()) @@ -503,8 +503,8 @@ func TestScheduler_ABTesting(t *testing.T) { } } -func TestScheduler_PortDiscovery(t *testing.T) { - mocks := newFixture(nil) +func TestScheduler_DeploymentPortDiscovery(t *testing.T) { + mocks := newDeploymentFixture(nil) // enable port discovery cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) @@ -547,8 +547,8 @@ func TestScheduler_PortDiscovery(t *testing.T) { } } -func TestScheduler_TargetPortNumber(t *testing.T) { - mocks := newFixture(nil) +func TestScheduler_DeploymentTargetPortNumber(t *testing.T) { + mocks := newDeploymentFixture(nil) cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) if err != nil { @@ -592,8 +592,8 @@ func TestScheduler_TargetPortNumber(t *testing.T) { } } -func TestScheduler_TargetPortName(t *testing.T) { - mocks := newFixture(nil) +func TestScheduler_DeploymentTargetPortName(t *testing.T) { + mocks := newDeploymentFixture(nil) cd, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{}) if err != nil { @@ -637,7 +637,7 @@ func TestScheduler_TargetPortName(t *testing.T) { } } -func TestScheduler_Alerts(t *testing.T) { +func TestScheduler_DeploymentAlerts(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil { @@ -654,7 +654,7 @@ func TestScheduler_Alerts(t *testing.T) { })) defer ts.Close() - canary := newTestCanary() + canary := newDeploymentTestCanary() canary.Spec.CanaryAnalysis.Alerts = []flaggerv1.CanaryAlert{ { Name: "slack-dev", @@ -672,9 +672,9 @@ func TestScheduler_Alerts(t *testing.T) { }, }, } - mocks := newFixture(canary) + mocks := newDeploymentFixture(canary) - secret := newTestAlertProviderSecret() + secret := newDeploymentTestAlertProviderSecret() secret.Data = map[string][]byte{ "address": []byte(ts.URL), } diff --git a/pkg/controller/scheduler_svc_test.go b/pkg/controller/scheduler_svc_test.go index fa2a4691..5e11d9fc 100644 --- a/pkg/controller/scheduler_svc_test.go +++ b/pkg/controller/scheduler_svc_test.go @@ -9,7 +9,7 @@ import ( ) func TestScheduler_ServicePromotion(t *testing.T) { - mocks := newFixture(newTestServiceCanary()) + mocks := newDeploymentFixture(newTestServiceCanary()) // init mocks.ctrl.advanceCanary("podinfo", "default", true) @@ -25,7 +25,7 @@ func TestScheduler_ServicePromotion(t *testing.T) { } // update - svc2 := newTestServiceV2() + svc2 := newDeploymentTestServiceV2() _, err = mocks.kubeClient.CoreV1().Services("default").Update(svc2) if err != nil { t.Fatal(err.Error())