diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index 7aefe34a..deacdeca 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -2,6 +2,8 @@ package controller import ( "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha3" + istiov1alpha1 "github.com/stefanprodan/flagger/pkg/apis/istio/common/v1alpha1" + istiov1alpha3 "github.com/stefanprodan/flagger/pkg/apis/istio/v1alpha3" clientset "github.com/stefanprodan/flagger/pkg/client/clientset/versioned" fakeFlagger "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/fake" informers "github.com/stefanprodan/flagger/pkg/client/informers/externalversions" @@ -38,9 +40,12 @@ type Mocks struct { router router.Interface } -func SetupMocks() Mocks { +func SetupMocks(abtest bool) Mocks { // init canary canary := newTestCanary() + if abtest { + canary = newTestCanaryAB() + } flaggerClient := fakeFlagger.NewSimpleClientset(canary) // init kube clientset and register mock objects @@ -261,6 +266,55 @@ func newTestCanary() *v1alpha3.Canary { return cd } +func newTestCanaryAB() *v1alpha3.Canary { + cd := &v1alpha3.Canary{ + TypeMeta: metav1.TypeMeta{APIVersion: v1alpha3.SchemeGroupVersion.String()}, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "podinfo", + }, + Spec: v1alpha3.CanarySpec{ + TargetRef: hpav1.CrossVersionObjectReference{ + Name: "podinfo", + APIVersion: "apps/v1", + Kind: "Deployment", + }, + AutoscalerRef: &hpav1.CrossVersionObjectReference{ + Name: "podinfo", + APIVersion: "autoscaling/v2beta1", + Kind: "HorizontalPodAutoscaler", + }, Service: v1alpha3.CanaryService{ + Port: 9898, + }, CanaryAnalysis: v1alpha3.CanaryAnalysis{ + Threshold: 10, + Iterations: 10, + Match: []istiov1alpha3.HTTPMatchRequest{ + { + Headers: map[string]istiov1alpha1.StringMatch{ + "x-user-type": { + Exact: "test", + }, + }, + }, + }, + Metrics: []v1alpha3.CanaryMetric{ + { + Name: "istio_requests_total", + Threshold: 99, + Interval: "1m", + }, + { + Name: "istio_request_duration_seconds_bucket", + Threshold: 500, + Interval: "1m", + }, + }, + }, + }, + } + return cd +} + func newTestDeployment() *appsv1.Deployment { d := &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String()}, diff --git a/pkg/controller/deployer_test.go b/pkg/controller/deployer_test.go index e7bdd0ba..9f6eac07 100644 --- a/pkg/controller/deployer_test.go +++ b/pkg/controller/deployer_test.go @@ -8,7 +8,7 @@ import ( ) func TestCanaryDeployer_Sync(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) err := mocks.deployer.Sync(mocks.canary) if err != nil { t.Fatal(err.Error()) @@ -94,7 +94,7 @@ func TestCanaryDeployer_Sync(t *testing.T) { } func TestCanaryDeployer_IsNewSpec(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) err := mocks.deployer.Sync(mocks.canary) if err != nil { t.Fatal(err.Error()) @@ -117,7 +117,7 @@ func TestCanaryDeployer_IsNewSpec(t *testing.T) { } func TestCanaryDeployer_Promote(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) err := mocks.deployer.Sync(mocks.canary) if err != nil { t.Fatal(err.Error()) @@ -162,7 +162,7 @@ func TestCanaryDeployer_Promote(t *testing.T) { } func TestCanaryDeployer_IsReady(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) err := mocks.deployer.Sync(mocks.canary) if err != nil { t.Fatal(err.Error()) @@ -180,7 +180,7 @@ func TestCanaryDeployer_IsReady(t *testing.T) { } func TestCanaryDeployer_SetFailedChecks(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) err := mocks.deployer.Sync(mocks.canary) if err != nil { t.Fatal(err.Error()) @@ -202,7 +202,7 @@ func TestCanaryDeployer_SetFailedChecks(t *testing.T) { } func TestCanaryDeployer_SetState(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) err := mocks.deployer.Sync(mocks.canary) if err != nil { t.Fatal(err.Error()) @@ -224,7 +224,7 @@ func TestCanaryDeployer_SetState(t *testing.T) { } func TestCanaryDeployer_SyncStatus(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) err := mocks.deployer.Sync(mocks.canary) if err != nil { t.Fatal(err.Error()) @@ -263,7 +263,7 @@ func TestCanaryDeployer_SyncStatus(t *testing.T) { } func TestCanaryDeployer_Scale(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) err := mocks.deployer.Sync(mocks.canary) if err != nil { t.Fatal(err.Error()) diff --git a/pkg/controller/scheduler_test.go b/pkg/controller/scheduler_test.go index e774ee2d..888defb1 100644 --- a/pkg/controller/scheduler_test.go +++ b/pkg/controller/scheduler_test.go @@ -7,7 +7,7 @@ import ( ) func TestScheduler_Init(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) mocks.ctrl.advanceCanary("podinfo", "default", false) _, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo-primary", metav1.GetOptions{}) @@ -17,7 +17,7 @@ func TestScheduler_Init(t *testing.T) { } func TestScheduler_NewRevision(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) mocks.ctrl.advanceCanary("podinfo", "default", false) // update @@ -41,7 +41,7 @@ func TestScheduler_NewRevision(t *testing.T) { } func TestScheduler_Rollback(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) // init mocks.ctrl.advanceCanary("podinfo", "default", true) @@ -65,7 +65,7 @@ func TestScheduler_Rollback(t *testing.T) { } func TestScheduler_SkipAnalysis(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) // init mocks.ctrl.advanceCanary("podinfo", "default", false) @@ -106,7 +106,7 @@ func TestScheduler_SkipAnalysis(t *testing.T) { } func TestScheduler_NewRevisionReset(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) // init mocks.ctrl.advanceCanary("podinfo", "default", false) @@ -160,7 +160,7 @@ func TestScheduler_NewRevisionReset(t *testing.T) { } func TestScheduler_Promotion(t *testing.T) { - mocks := SetupMocks() + mocks := SetupMocks(false) // init mocks.ctrl.advanceCanary("podinfo", "default", false) @@ -258,3 +258,74 @@ func TestScheduler_Promotion(t *testing.T) { t.Errorf("Got canary state %v wanted %v", c.Status.Phase, v1alpha3.CanarySucceeded) } } + +func TestScheduler_ABTesting(t *testing.T) { + mocks := SetupMocks(true) + // init + mocks.ctrl.advanceCanary("podinfo", "default", false) + + // update + dep2 := newTestDeploymentV2() + _, err := mocks.kubeClient.AppsV1().Deployments("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, 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) + } + + cd, err := mocks.flaggerClient.FlaggerV1alpha3().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()) + } + + // promote + mocks.ctrl.advanceCanary("podinfo", "default", true) + + // check if the container image tag was updated + primaryDep, err := mocks.kubeClient.AppsV1().Deployments("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.FlaggerV1alpha3().Canaries("default").Get("podinfo", metav1.GetOptions{}) + if err != nil { + t.Fatal(err.Error()) + } + + if c.Status.Phase != v1alpha3.CanarySucceeded { + t.Errorf("Got canary state %v wanted %v", c.Status.Phase, v1alpha3.CanarySucceeded) + } +}