Repository: weaveworks/flagger
Pull request #507 (2020-03-17 20:55:49)

loadtester: add concord test support
Hi,

Here's a PR for Concord support in Flagger's loadtester.

Fix: #477 

Example query
```
{
    "metadata": {
        "type": "concord",
        "server": "https://concord.prod.foo.com/",
        "org": "my-concord-repo-org",
        "project": ""my-concord-project",
        "repo": "my-concord-repo",
        "entrypoint": ""my-concord-entrypoint",
        "apiKeyPath": "/tmp/concord-api-key",
        "endpoint": "https://canary-endpoint/",
        "pollInterval": "5",
        "pollTimeout": "60"
    }
}
```

Example logs
```
{"level":"info","ts":"2020-03-17T13:54:07.723-0700","caller":"loadtester/concord.go:173","msg":"calling endpoint https://concord.prod.foo.com/api/v1/process","canary":""}
{"level":"info","ts":"2020-03-17T13:54:07.874-0700","caller":"loadtester/concord.go:208","msg":"created process id [4e7f4245-85aa-4c33-a56a-d4df5b63d3fe] OK status is true","canary":""}
{"level":"info","ts":"2020-03-17T13:54:12.875-0700","caller":"loadtester/concord.go:173","msg":"calling endpoint https://concord.prod.foo.com/api/v1/process/4e7f4245-85aa-4c33-a56a-d4df5b63d3fe","canary":""}
{"level":"info","ts":"2020-03-17T13:54:13.010-0700","caller":"loadtester/concord.go:227","msg":"process id [4e7f4245-85aa-4c33-a56a-d4df5b63d3fe] current status is STARTING","canary":""}
{"level":"info","ts":"2020-03-17T13:54:17.874-0700","caller":"loadtester/concord.go:173","msg":"calling endpoint https://concord.prod.foo.com/api/v1/process/4e7f4245-85aa-4c33-a56a-d4df5b63d3fe","canary":""}
{"level":"info","ts":"2020-03-17T13:54:17.985-0700","caller":"loadtester/concord.go:227","msg":"process id [4e7f4245-85aa-4c33-a56a-d4df5b63d3fe] current status is STARTING","canary":""}
{"level":"info","ts":"2020-03-17T13:54:22.874-0700","caller":"loadtester/concord.go:173","msg":"calling endpoint https://concord.prod.foo.com/api/v1/process/4e7f4245-85aa-4c33-a56a-d4df5b63d3fe","canary":""}
{"level":"info","ts":"2020-03-17T13:54:23.061-0700","caller":"loadtester/concord.go:227","msg":"process id [4e7f4245-85aa-4c33-a56a-d4df5b63d3fe] current status is STARTING","canary":""}
{"level":"info","ts":"2020-03-17T13:54:27.878-0700","caller":"loadtester/concord.go:173","msg":"calling endpoint https://concord.prod.foo.com/api/v1/process/4e7f4245-85aa-4c33-a56a-d4df5b63d3fe","canary":""}
{"level":"info","ts":"2020-03-17T13:54:27.986-0700","caller":"loadtester/concord.go:227","msg":"process id [4e7f4245-85aa-4c33-a56a-d4df5b63d3fe] current status is RUNNING","canary":""}
{"level":"info","ts":"2020-03-17T13:54:32.876-0700","caller":"loadtester/concord.go:173","msg":"calling endpoint https://concord.prod.foo.com/api/v1/process/4e7f4245-85aa-4c33-a56a-d4df5b63d3fe","canary":""}
{"level":"info","ts":"2020-03-17T13:54:32.992-0700","caller":"loadtester/concord.go:227","msg":"process id [4e7f4245-85aa-4c33-a56a-d4df5b63d3fe] current status is FINISHED","canary":""}
```

**Regarding Authentication**
This implementation requires to have a Concord API key present in a file directly on the container. This can be done via mounting a k8s secret in the Loadtester's Deployment. 
I went with a file over an environment variable since it's arguably more secured. Happy to switch if you think necessary.

Let me know what you think so far,

Best,



stefanprodan: @ernst01 thanks for the PR, can you please document the webhook here https://github.com/weaveworks/flagger/blob/master/docs/gitbook/usage/webhooks.md#integration-testing

Thanks

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 8674702a..ecdc397e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -72,20 +72,40 @@ jobs:
       - run: github-release-notes -org weaveworks -repo flagger -since-latest-release -include-author > /tmp/release.txt
       - run: test/goreleaser.sh
 
-  e2e-kubernetes-testing:
+  e2e-istio-testing:
+    machine: true
+    steps:
+      - checkout
+      - attach_workspace:
+          at: /tmp/bin
+      - run: test/container-build.sh
+      - run: test/e2e-kind.sh
+      - run: test/e2e-istio.sh
+      - run: test/e2e-tests.sh
+
+  e2e-kubernetes-daemonset-testing:
     machine: true
     steps:
       - checkout
       - attach_workspace:
           at: /tmp/bin
       - run: test/container-build.sh
-      - run: test/e2e-kind.sh v1.17.2
+      - run: test/e2e-kind.sh v1.17.0
       - run: test/e2e-kubernetes.sh
-      - run: test/e2e-kubernetes-tests-deployment.sh
-      - run: test/e2e-kubernetes-cleanup.sh
       - run: test/e2e-kubernetes-tests-daemonset.sh
 
-  e2e-istio-testing:
+  e2e-kubernetes-deployment-testing:
+    machine: true
+    steps:
+      - checkout
+      - attach_workspace:
+          at: /tmp/bin
+      - run: test/container-build.sh
+      - run: test/e2e-kind.sh v1.17.0
+      - run: test/e2e-kubernetes.sh
+      - run: test/e2e-kubernetes-tests-deployment.sh
+
+  e2e-kubernetes-svc-testing:
     machine: true
     steps:
       - checkout
@@ -93,8 +113,19 @@ jobs:
           at: /tmp/bin
       - run: test/container-build.sh
       - run: test/e2e-kind.sh
-      - run: test/e2e-istio.sh
-      - run: test/e2e-istio-tests.sh
+      - run: test/e2e-kubernetes.sh
+      - run: test/e2e-kubernetes-svc-tests.sh
+
+  e2e-smi-istio-testing:
+    machine: true
+    steps:
+      - checkout
+      - attach_workspace:
+          at: /tmp/bin
+      - run: test/container-build.sh
+      - run: test/e2e-kind.sh
+      - run: test/e2e-smi-istio.sh
+      - run: test/e2e-tests.sh canary
 
   e2e-gloo-testing:
     machine: true
@@ -194,10 +225,13 @@ workflows:
               ignore:
                 - gh-pages
                 - /^user-.*/
-      - e2e-kubernetes-testing:
+      - e2e-istio-testing:
           requires:
             - build-binary
-      - e2e-istio-testing:
+      - e2e-kubernetes-daemonset-testing:
+          requires:
+            - build-binary
+      - e2e-kubernetes-deployment-testing:
           requires:
             - build-binary
       - e2e-gloo-testing:
@@ -215,8 +249,9 @@ workflows:
       - push-container:
           requires:
             - build-binary
-            - e2e-kubernetes-testing
             - e2e-istio-testing
+            - e2e-kubernetes-daemonset-testing
+            - e2e-kubernetes-deployment-testing
             - e2e-gloo-testing
             - e2e-nginx-testing
             - e2e-linkerd-testing
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cdcebdfe..b79a9e49 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,7 @@ This is a release candidate for Flagger v1.0.0.
 
 The upgrade procedure from 0.x to 1.0 can be found [here](https://docs.flagger.app/dev/upgrade-guide).
 
-Two new resources were added to the API: `MetricTemplate` and `AlertProvider`.
+Two new resources where added to the API: `MetricTemplate` and `AlertProvider`.
 The analysis can reference [metric templates](https://docs.flagger.app//usage/metrics#custom-metrics)
 to query Prometheus, Datadog and AWS CloudWatch.
 [Alerting](https://docs.flagger.app/v/master/usage/alerting#canary-configuration) can be configured on a per
@@ -620,4 +620,4 @@ Initial semver release
 - Add OpenAPI v3 schema validation to Canary CRD
 - Use CRD status for canary state persistence
 - Add Helm charts for Flagger and Grafana
-- Add canary analysis Grafana dashboard 
+- Add canary analysis Grafana dashboard 
\ No newline at end of file
diff --git a/README.md b/README.md
index de6190d3..4f2e12d0 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ Flagger documentation can be found at [docs.flagger.app](https://docs.flagger.ap
 * Usage
   * [How it works](https://docs.flagger.app/usage/how-it-works)
   * [Deployment strategies](https://docs.flagger.app/usage/deployment-strategies)
-  * [Metrics analysis](https://docs.flagger.app/usage/metrics)
+  * [Metrics analysis](https://docs.flagger.app/usage/webhooks)
   * [Webhooks](https://docs.flagger.app/usage/webhooks)
   * [Alerting](https://docs.flagger.app/usage/alerting)
   * [Monitoring](https://docs.flagger.app/usage/monitoring)
@@ -200,7 +200,7 @@ For more details on how the canary analysis and promotion works please [read the
 ### Contributing
 
 Flagger is Apache 2.0 licensed and accepts contributions via GitHub pull requests.
-To start contributing please read the [development guide](https://docs.flagger.app/dev/dev-guide).
+To start contributing please read the [development guide](https://docs.flagger.app/dev-guide).
 
 When submitting bug reports please include as much details as possible:
 
diff --git a/artifacts/flagger/crd.yaml b/artifacts/flagger/crd.yaml
index 3ae25fa8..0c37ebc3 100644
--- a/artifacts/flagger/crd.yaml
+++ b/artifacts/flagger/crd.yaml
@@ -528,9 +528,6 @@ spec:
                 mirror:
                   description: Mirror traffic to canary
                   type: boolean
-                mirrorWeight:
-                  description: Percentage of traffic to be mirrored
-                  type: number
                 match:
                   description: A/B testing match conditions
                   type: array
diff --git a/charts/flagger/README.md b/charts/flagger/README.md
index dc8df74c..f17900d6 100644
--- a/charts/flagger/README.md
+++ b/charts/flagger/README.md
@@ -77,7 +77,6 @@ Parameter | Description | Default
 `image.repository` | Image repository | `weaveworks/flagger`
 `image.tag` | Image tag | `<VERSION>`
 `image.pullPolicy` | Image pull policy | `IfNotPresent`
-`logLevel` | Log level | `info`
 `prometheus.install` | If `true`, installs Prometheus configured to scrape all pods in the custer including the App Mesh sidecar | `false`
 `metricsServer` | Prometheus URL, used when `prometheus.install` is `false` | `http://prometheus.istio-system:9090`
 `selectorLabels` | List of labels that Flagger uses to create pod selectors | `app,name,app.kubernetes.io/name`
diff --git a/charts/flagger/crds/crd.yaml b/charts/flagger/crds/crd.yaml
index 3ae25fa8..0c37ebc3 100644
--- a/charts/flagger/crds/crd.yaml
+++ b/charts/flagger/crds/crd.yaml
@@ -528,9 +528,6 @@ spec:
                 mirror:
                   description: Mirror traffic to canary
                   type: boolean
-                mirrorWeight:
-                  description: Percentage of traffic to be mirrored
-                  type: number
                 match:
                   description: A/B testing match conditions
                   type: array
diff --git a/charts/flagger/templates/deployment.yaml b/charts/flagger/templates/deployment.yaml
index 2c2115ea..f5c264a4 100644
--- a/charts/flagger/templates/deployment.yaml
+++ b/charts/flagger/templates/deployment.yaml
@@ -65,7 +65,7 @@ spec:
             containerPort: 8080
           command:
           - ./flagger
-          - -log-level={{ .Values.logLevel }}
+          - -log-level=info
           {{- if .Values.meshProvider }}
           - -mesh-provider={{ .Values.meshProvider }}
           {{- end }}
@@ -85,11 +85,7 @@ spec:
           {{- end }}
           {{- if .Values.slack.url }}
           - -slack-url={{ .Values.slack.url }}
-          {{- end }}
-          {{- if .Values.slack.user }}
           - -slack-user={{ .Values.slack.user }}
-          {{- end }}
-          {{- if .Values.slack.channel }}
           - -slack-channel={{ .Values.slack.channel }}
           {{- end }}
           {{- if .Values.msteams.url }}
diff --git a/charts/flagger/values.yaml b/charts/flagger/values.yaml
index ca9b123a..24495ef0 100644
--- a/charts/flagger/values.yaml
+++ b/charts/flagger/values.yaml
@@ -6,9 +6,6 @@ image:
   pullPolicy: IfNotPresent
   pullSecret:
 
-# accepted values are debug, info, warning, error (defaults to info)
-logLevel: info
-
 podAnnotations:
   prometheus.io/scrape: "true"
   prometheus.io/port: "8080"
diff --git a/cmd/flagger/main.go b/cmd/flagger/main.go
index 1c31f677..f12fa83c 100644
--- a/cmd/flagger/main.go
+++ b/cmd/flagger/main.go
@@ -125,9 +125,6 @@ func main() {
 	}
 
 	// use a remote cluster for routing if a service mesh kubeconfig is specified
-	if kubeconfigServiceMesh == "" {
-		kubeconfigServiceMesh = kubeconfig
-	}
 	cfgHost, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfigServiceMesh)
 	if err != nil {
 		logger.Fatalf("Error building host kubeconfig: %v", err)
@@ -186,6 +183,7 @@ func main() {
 
 	c := controller.NewController(
 		kubeClient,
+		meshClient,
 		flaggerClient,
 		infos,
 		controlLoopInterval,
@@ -318,19 +316,21 @@ func initNotifier(logger *zap.SugaredLogger) (client notifier.Interface) {
 	}
 	notifierFactory := notifier.NewFactory(notifierURL, slackUser, slackChannel)
 
-	var err error
-	client, err = notifierFactory.Notifier(provider)
-	if err != nil {
-		logger.Errorf("Notifier %v", err)
-	} else if len(notifierURL) > 30 {
-		logger.Infof("Notifications enabled for %s", notifierURL[0:30])
+	if notifierURL != "" {
+		var err error
+		client, err = notifierFactory.Notifier(provider)
+		if err != nil {
+			logger.Errorf("Notifier %v", err)
+		} else {
+			logger.Infof("Notifications enabled for %s", notifierURL[0:30])
+		}
 	}
 	return
 }
 
 func fromEnv(envVar string, defaultVal string) string {
-	if v := os.Getenv(envVar); v != "" {
-		return v
+	if os.Getenv(envVar) != "" {
+		return os.Getenv(envVar)
 	}
 	return defaultVal
 }
diff --git a/docs/gitbook/dev/upgrade-guide.md b/docs/gitbook/dev/upgrade-guide.md
index 675e6472..c5f2b503 100644
--- a/docs/gitbook/dev/upgrade-guide.md
+++ b/docs/gitbook/dev/upgrade-guide.md
@@ -11,7 +11,6 @@ Canary CRD changes in `canaries.flagger.app/v1beta1`:
 * the `spec.analysis.alerts` array can reference `alertproviders.flagger.app/v1beta1` resources
 * the `spec.analysis.metrics[].templateRef` can reference a `metrictemplate.flagger.app/v1beta1` resource
 * the `metric.threshold` field has been deprecated and replaced with `metric.thresholdRange`
-* the `metric.query` field has been deprecated and replaced with `metric.templateRef`
 * the `spec.targetRef` can reference `DaemonSet` kind
 
 Upgrade procedure:
@@ -40,49 +39,3 @@ metrics:
     max: 500
   interval: 1m
 ```
-
-### Istio telemetry v2
-
-Istio 1.5 comes with a breaking change for Flagger uses. In Istio telemetry v2 the metric 
-`istio_request_duration_seconds_bucket` has been removed and replaced with `istio_request_duration_milliseconds_bucket`
-and this breaks the `request-duration` metric check.
-
-You can create a metric template using the new duration metric like this:
-
-```yaml
-apiVersion: flagger.app/v1beta1
-kind: MetricTemplate
-metadata:
-  name: latency
-  namespace: istio-system
-spec:
-  provider:
-    type: prometheus
-    address: http://prometheus.istio-system:9090
-  query: |
-    histogram_quantile(
-        0.99,
-        sum(
-            rate(
-                istio_request_duration_milliseconds_bucket{
-                    reporter="destination",
-                    destination_workload_namespace="{{ namespace }}",
-                    destination_workload=~"{{ target }}"
-                }[{{ interval }}]
-            )
-        ) by (le)
-    )
-```
-
-In the canary manifests, replace the `request-duration` metric with `latency`:
-
-```yaml
-metrics:
-- name: latency
-  templateRef:
-    name: latency
-    namespace: istio-system
-  thresholdRange:
-    max: 500
-  interval: 1m
-```
diff --git a/docs/gitbook/faq.md b/docs/gitbook/faq.md
index 5deb2cec..ecc807c4 100644
--- a/docs/gitbook/faq.md
+++ b/docs/gitbook/faq.md
@@ -26,26 +26,6 @@ Traffic mirroring can be used for Blue/Green deployment strategy or a pre-stage
 Traffic mirroring will copy each incoming request, sending one request to the primary and one to the canary service.
 Mirroring should be used for requests that are **idempotent** or capable of being processed twice (once by the primary and once by the canary).
 
-**How to retry a failed release?**
-
-A canary analysis is triggered by changes in any of the following objects:
-
-* Deployment/DaemonSet PodSpec (metadata, container image, command, ports, env, resources, etc)
-* ConfigMaps mounted as volumes or mapped to environment variables
-* Secrets mounted as volumes or mapped to environment variables
-
-To retry a release you can add or change an annotation on the pod template:
-
-```yaml
-apiVersion: apps/v1
-kind: Deployment
-spec:
-  template:
-    metadata:
-      annotations:
-        timestamp: "2020-03-10T14:24:48+0000"
-```
-
 ### Kubernetes services
 
 **How is an application exposed inside the cluster?**
diff --git a/docs/gitbook/tutorials/istio-progressive-delivery.md b/docs/gitbook/tutorials/istio-progressive-delivery.md
index 39b6df63..413f4b40 100644
--- a/docs/gitbook/tutorials/istio-progressive-delivery.md
+++ b/docs/gitbook/tutorials/istio-progressive-delivery.md
@@ -149,9 +149,6 @@ spec:
           cmd: "hey -z 1m -q 10 -c 2 http://podinfo-canary.test:9898/"
 ```
 
-**Note** that when using Istio 1.5 you have to replace the `request-duration`
-with a [metric template](https://docs.flagger.app/dev/upgrade-guide#istio-telemetry-v2).
-
 Save the above resource as podinfo-canary.yaml and then apply it:
 
 ```bash
@@ -324,8 +321,6 @@ spec:
     iterations: 10
     # enable traffic shadowing 
     mirror: true
-    # weight of the traffic mirrored to your canary (defaults to 100%)
-    mirrorWeight: 100
     metrics:
     - name: request-success-rate
       thresholdRange:
@@ -359,7 +354,7 @@ With the above configuration, Flagger will run a canary release with the followi
 * run the acceptance tests
 * abort the canary release if tests fail
 * start the load tests
-* mirror 100% of the traffic from primary to canary
+* mirror traffic from primary to canary
 * check request success rate and request duration every minute
 * abort the canary release if the metrics check failure threshold is reached
 * stop traffic mirroring after the number of iterations is reached
diff --git a/docs/gitbook/usage/deployment-strategies.md b/docs/gitbook/usage/deployment-strategies.md
index 05cc4fd5..633b9b65 100644
--- a/docs/gitbook/usage/deployment-strategies.md
+++ b/docs/gitbook/usage/deployment-strategies.md
@@ -214,7 +214,7 @@ with Kubernetes L4 networking. When using Istio you have the option to mirror tr
 
 ![Flagger Blue/Green Stages](https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-bluegreen-steps.png)
 
-You can use the blue/green deployment strategy by replacing `stepWeight/maxWeight` with `iterations` in the `analysis` spec:
+You can use the blue/green deployment strategy by replacing `stepWeight/maxWeight` with `iterations` in the `canaryAnalysis` spec:
 
 ```yaml
   analysis:
@@ -263,7 +263,7 @@ idempotent. Before using mirroring on requests that may be writes, you should
 consider what will happen if a write is duplicated and handled by the primary
 and canary.
 
-To use mirroring, set `spec.analysis.mirror` to `true`.
+To use mirroring, set `spec.canaryAnalysis.mirror` to `true`. 
 
 Istio example:
 
@@ -277,8 +277,6 @@ Istio example:
     threshold: 2
     # Traffic shadowing (compatible with Istio only)
     mirror: true
-    # Weight of the traffic mirrored to your canary (defaults to 100%)
-    mirrorWeight: 100
 ```
 
 Mirroring rollout steps for service mesh:
@@ -289,7 +287,7 @@ Mirroring rollout steps for service mesh:
 * run the acceptance tests
 * abort the canary release if tests fail
 * start the load tests
-* mirror 100% of the traffic from primary to canary
+* mirror traffic from primary to canary
 * check request success rate and request duration every minute
 * abort the canary release if the failure threshold is reached
 * stop traffic mirroring after the number of iterations is reached
diff --git a/docs/gitbook/usage/metrics.md b/docs/gitbook/usage/metrics.md
index 9c9e4bd2..7d0d8385 100644
--- a/docs/gitbook/usage/metrics.md
+++ b/docs/gitbook/usage/metrics.md
@@ -58,7 +58,7 @@ The following variables are available in query templates:
 - `target` (canary.spec.targetRef.name)
 - `service` (canary.spec.service.name)
 - `ingress` (canary.spec.ingresRef.name)
-- `interval` (canary.spec.analysis.metrics[].interval)
+- `interval` (canary.spec.canaryAnalysis.metrics[].interval)
 
 A canary analysis metric can reference a template with `templateRef`:
 
diff --git a/docs/gitbook/usage/webhooks.md b/docs/gitbook/usage/webhooks.md
index 288ed19b..0864d95a 100644
--- a/docs/gitbook/usage/webhooks.md
+++ b/docs/gitbook/usage/webhooks.md
@@ -231,7 +231,7 @@ for the status of the test, and prevent duplicate requests from being sent in su
 
 ### Integration Testing
 
-Flagger comes with a testing service that can run Helm tests or Bats tests when configured as a webhook.
+Flagger comes with a testing service that can run Helm tests, Bats tests or Concord tests when configured as a webhook.
 
 Deploy the Helm test runner in the `kube-system` namespace using the `tiller` service account:
 
@@ -292,6 +292,33 @@ As an alternative to Helm you can use the [Bash Automated Testing System](https:
 
 Note that you should create a ConfigMap with your Bats tests and mount it inside the tester container.
 
+You can also configure the test runner to start a [Concord](https://concord.walmartlabs.com/) process.
+
+```yaml
+  analysis:
+    webhooks:
+      - name: "concord integration test"
+        type: pre-rollout
+        url: http://flagger-concordtester.default/
+        timeout: 60s
+        metadata:
+          type: "concord"
+          org: "your-concord-org"
+          project: "your-concord-project"
+          repo: "your-concord-repo"
+          entrypoint: "your-concord-entrypoint"
+          apiKeyPath: "/tmp/concord-api-key"
+          endpoint: "https://canary-endpoint/"
+          pollInterval: "5"
+          pollTimeout: "60"
+```
+
+`org`, `project`, `repo` and `entrypoint` represents where your test process runs in Concord. 
+In order to authenticate to Concord, you need to set `apiKeyPath` to a path of a file containing a valid Concord API key
+ on the `flagger-helmtester` container. This can be done via mounting a Kubernetes secret in the tester's Deployment. 
+`pollInterval` represents the interval in seconds the web-hook will call Concord to see if the process has finished (Default is 5s). 
+`pollTimeout` represents the time in seconds the web-hook will try to call Concord before timing out (Default is 30s). 
+
 ### Manual Gating
 
 For manual approval of a canary deployment you can use the `confirm-rollout` and `confirm-promotion` webhooks. 
diff --git a/kustomize/base/flagger/crd.yaml b/kustomize/base/flagger/crd.yaml
index 3ae25fa8..0c37ebc3 100644
--- a/kustomize/base/flagger/crd.yaml
+++ b/kustomize/base/flagger/crd.yaml
@@ -528,9 +528,6 @@ spec:
                 mirror:
                   description: Mirror traffic to canary
                   type: boolean
-                mirrorWeight:
-                  description: Percentage of traffic to be mirrored
-                  type: number
                 match:
                   description: A/B testing match conditions
                   type: array
diff --git a/pkg/apis/flagger/v1beta1/canary.go b/pkg/apis/flagger/v1beta1/canary.go
index a68a0a43..f13bd908 100644
--- a/pkg/apis/flagger/v1beta1/canary.go
+++ b/pkg/apis/flagger/v1beta1/canary.go
@@ -175,14 +175,10 @@ type CanaryAnalysis struct {
 	// +optional
 	Iterations int `json:"iterations,omitempty"`
 
-	// Enable traffic mirroring for Blue/Green
+	//Enable traffic mirroring for Blue/Green
 	// +optional
 	Mirror bool `json:"mirror,omitempty"`
 
-	// Percentage of the traffic to be mirrored in the range of [0, 100].
-	// +optional
-	MirrorWeight int `json:"mirrorWeight,omitempty"`
-
 	// Max traffic percentage routed to canary
 	// +optional
 	MaxWeight int `json:"maxWeight,omitempty"`
@@ -218,14 +214,14 @@ type CanaryMetric struct {
 	// Interval represents the windows size
 	Interval string `json:"interval,omitempty"`
 
-	// Deprecated: Max value accepted for this metric (replaced by ThresholdRange)
+	// Max value accepted for this metric
 	Threshold float64 `json:"threshold"`
 
 	// Range value accepted for this metric
 	// +optional
 	ThresholdRange *CanaryThresholdRange `json:"thresholdRange,omitempty"`
 
-	// Deprecated: Prometheus query for this metric (replaced by TemplateRef)
+	// Prometheus query for this metric (deprecated in favor of TemplateRef)
 	// +optional
 	Query string `json:"query,omitempty"`
 
diff --git a/pkg/apis/istio/v1alpha3/virtual_service.go b/pkg/apis/istio/v1alpha3/virtual_service.go
index 7718ab07..d800142b 100644
--- a/pkg/apis/istio/v1alpha3/virtual_service.go
+++ b/pkg/apis/istio/v1alpha3/virtual_service.go
@@ -325,11 +325,6 @@ type HTTPRoute struct {
 	// destination.
 	Mirror *Destination `json:"mirror,omitempty"`
 
-	// Percentage of the traffic to be mirrored by the `mirror` field.
-	// If this field is absent, all the traffic (100%) will be mirrored.
-	// Max value is 100.
-	MirrorPercentage *Percent `json:"mirrorPercentage,omitempty"`
-
 	// Cross-Origin Resource Sharing policy (CORS). Refer to
 	// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
 	// for further details about cross origin resource sharing.
@@ -339,11 +334,6 @@ type HTTPRoute struct {
 	Headers *Headers `json:"headers,omitempty"`
 }
 
-// Percent specifies a percentage in the range of [0.0, 100.0].
-type Percent struct {
-	Value float64 `json:"value,omitempty"`
-}
-
 // Header manipulation rules
 type Headers struct {
 	// Header manipulation rules to apply before forwarding a request
diff --git a/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go b/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go
index a187f416..9447a78e 100644
--- a/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go
+++ b/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go
@@ -421,11 +421,6 @@ func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) {
 		*out = new(Destination)
 		(*in).DeepCopyInto(*out)
 	}
-	if in.MirrorPercentage != nil {
-		in, out := &in.MirrorPercentage, &out.MirrorPercentage
-		*out = new(Percent)
-		**out = **in
-	}
 	if in.CorsPolicy != nil {
 		in, out := &in.CorsPolicy, &out.CorsPolicy
 		*out = new(CorsPolicy)
@@ -633,22 +628,6 @@ func (in *OutlierDetection) DeepCopy() *OutlierDetection {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *Percent) DeepCopyInto(out *Percent) {
-	*out = *in
-	return
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Percent.
-func (in *Percent) DeepCopy() *Percent {
-	if in == nil {
-		return nil
-	}
-	out := new(Percent)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *PortSelector) DeepCopyInto(out *PortSelector) {
 	*out = *in
diff --git a/pkg/canary/config_tracker.go b/pkg/canary/config_tracker.go
index 75c6102b..e3922fcb 100644
--- a/pkg/canary/config_tracker.go
+++ b/pkg/canary/config_tracker.go
@@ -54,7 +54,7 @@ func checksum(data interface{}) string {
 func (ct *ConfigTracker) getRefFromConfigMap(name string, namespace string) (*ConfigRef, error) {
 	config, err := ct.KubeClient.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{})
 	if err != nil {
-		return nil, fmt.Errorf("configmap  %s.%s get query error: %w", name, namespace, err)
+		return nil, err
 	}
 
 	return &ConfigRef{
@@ -69,7 +69,7 @@ func (ct *ConfigTracker) getRefFromConfigMap(name string, namespace string) (*Co
 func (ct *ConfigTracker) getRefFromSecret(name string, namespace string) (*ConfigRef, error) {
 	secret, err := ct.KubeClient.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{})
 	if err != nil {
-		return nil, fmt.Errorf("secret %s.%s get query error: %w", name, namespace, err)
+		return nil, err
 	}
 
 	// ignore registry secrets (those should be set via service account)
@@ -100,14 +100,20 @@ func (ct *ConfigTracker) GetTargetConfigs(cd *flaggerv1.Canary) (map[string]Conf
 	case "Deployment":
 		targetDep, err := ct.KubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{})
 		if err != nil {
-			return res, fmt.Errorf("deployment %s.%s get query error: %w", targetName, cd.Namespace, err)
+			if errors.IsNotFound(err) {
+				return res, fmt.Errorf("deployment %s.%s not found", targetName, cd.Namespace)
+			}
+			return res, fmt.Errorf("deployment %s.%s query error %v", targetName, cd.Namespace, err)
 		}
 		vs = targetDep.Spec.Template.Spec.Volumes
 		cs = targetDep.Spec.Template.Spec.Containers
 	case "DaemonSet":
 		targetDae, err := ct.KubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
 		if err != nil {
-			return res, fmt.Errorf("daemonset %s.%s get query error: %w", targetName, cd.Namespace, err)
+			if errors.IsNotFound(err) {
+				return res, fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
+			}
+			return res, fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
 		}
 		vs = targetDae.Spec.Template.Spec.Volumes
 		cs = targetDae.Spec.Template.Spec.Containers
@@ -120,16 +126,18 @@ func (ct *ConfigTracker) GetTargetConfigs(cd *flaggerv1.Canary) (map[string]Conf
 		if cmv := volume.ConfigMap; cmv != nil {
 			config, err := ct.getRefFromConfigMap(cmv.Name, cd.Namespace)
 			if err != nil {
-				ct.Logger.Errorf("getRefFromConfigMap failed: %v", err)
+				ct.Logger.Errorf("configMap %s.%s query error %v", cmv.Name, cd.Namespace, err)
 				continue
 			}
-			res[config.GetName()] = *config
+			if config != nil {
+				res[config.GetName()] = *config
+			}
 		}
 
 		if sv := volume.Secret; sv != nil {
 			secret, err := ct.getRefFromSecret(sv.SecretName, cd.Namespace)
 			if err != nil {
-				ct.Logger.Errorf("getRefFromSecret failed: %v", err)
+				ct.Logger.Errorf("secret %s.%s query error %v", sv.SecretName, cd.Namespace, err)
 				continue
 			}
 			if secret != nil {
@@ -142,16 +150,18 @@ func (ct *ConfigTracker) GetTargetConfigs(cd *flaggerv1.Canary) (map[string]Conf
 				if cmv := source.ConfigMap; cmv != nil {
 					config, err := ct.getRefFromConfigMap(cmv.Name, cd.Namespace)
 					if err != nil {
-						ct.Logger.Errorf("getRefFromConfigMap failed: %v", err)
+						ct.Logger.Errorf("configMap %s.%s query error %v", cmv.Name, cd.Namespace, err)
 						continue
 					}
-					res[config.GetName()] = *config
+					if config != nil {
+						res[config.GetName()] = *config
+					}
 				}
 
 				if sv := source.Secret; sv != nil {
 					secret, err := ct.getRefFromSecret(sv.Name, cd.Namespace)
 					if err != nil {
-						ct.Logger.Errorf("getRefFromSecret failed: %v", err)
+						ct.Logger.Errorf("secret %s.%s query error %v", sv.Name, cd.Namespace, err)
 						continue
 					}
 					if secret != nil {
@@ -171,15 +181,17 @@ func (ct *ConfigTracker) GetTargetConfigs(cd *flaggerv1.Canary) (map[string]Conf
 					name := env.ValueFrom.ConfigMapKeyRef.LocalObjectReference.Name
 					config, err := ct.getRefFromConfigMap(name, cd.Namespace)
 					if err != nil {
-						ct.Logger.Errorf("getRefFromConfigMap failed: %v", err)
+						ct.Logger.Errorf("configMap %s.%s query error %v", name, cd.Namespace, err)
 						continue
 					}
-					res[config.GetName()] = *config
+					if config != nil {
+						res[config.GetName()] = *config
+					}
 				case env.ValueFrom.SecretKeyRef != nil:
 					name := env.ValueFrom.SecretKeyRef.LocalObjectReference.Name
 					secret, err := ct.getRefFromSecret(name, cd.Namespace)
 					if err != nil {
-						ct.Logger.Errorf("getRefFromSecret failed: %v", err)
+						ct.Logger.Errorf("secret %s.%s query error %v", name, cd.Namespace, err)
 						continue
 					}
 					if secret != nil {
@@ -195,15 +207,17 @@ func (ct *ConfigTracker) GetTargetConfigs(cd *flaggerv1.Canary) (map[string]Conf
 				name := envFrom.ConfigMapRef.LocalObjectReference.Name
 				config, err := ct.getRefFromConfigMap(name, cd.Namespace)
 				if err != nil {
-					ct.Logger.Errorf("getRefFromConfigMap failed %v", err)
+					ct.Logger.Errorf("configMap %s.%s query error %v", name, cd.Namespace, err)
 					continue
 				}
-				res[config.GetName()] = *config
+				if config != nil {
+					res[config.GetName()] = *config
+				}
 			case envFrom.SecretRef != nil:
 				name := envFrom.SecretRef.LocalObjectReference.Name
 				secret, err := ct.getRefFromSecret(name, cd.Namespace)
 				if err != nil {
-					ct.Logger.Errorf("getRefFromSecret failed %v", err)
+					ct.Logger.Errorf("secret %s.%s query error %v", name, cd.Namespace, err)
 					continue
 				}
 				if secret != nil {
@@ -221,7 +235,7 @@ func (ct *ConfigTracker) GetConfigRefs(cd *flaggerv1.Canary) (*map[string]string
 	res := make(map[string]string)
 	configs, err := ct.GetTargetConfigs(cd)
 	if err != nil {
-		return nil, fmt.Errorf("GetTargetConfigs failed: %w", err)
+		return nil, err
 	}
 
 	for _, cfg := range configs {
@@ -236,7 +250,7 @@ func (ct *ConfigTracker) GetConfigRefs(cd *flaggerv1.Canary) (*map[string]string
 func (ct *ConfigTracker) HasConfigChanged(cd *flaggerv1.Canary) (bool, error) {
 	configs, err := ct.GetTargetConfigs(cd)
 	if err != nil {
-		return false, fmt.Errorf("GetTargetConfigs failed: %w", err)
+		return false, err
 	}
 
 	if len(configs) == 0 && cd.Status.TrackedConfigs == nil {
@@ -272,7 +286,7 @@ func (ct *ConfigTracker) CreatePrimaryConfigs(cd *flaggerv1.Canary, refs map[str
 		case ConfigRefMap:
 			config, err := ct.KubeClient.CoreV1().ConfigMaps(cd.Namespace).Get(ref.Name, metav1.GetOptions{})
 			if err != nil {
-				return fmt.Errorf("configmap %s.%s get query failed : %w", ref.Name, cd.Name, err)
+				return err
 			}
 			primaryName := fmt.Sprintf("%s-primary", config.GetName())
 			primaryConfigMap := &corev1.ConfigMap{
@@ -297,10 +311,10 @@ func (ct *ConfigTracker) CreatePrimaryConfigs(cd *flaggerv1.Canary, refs map[str
 				if errors.IsNotFound(err) {
 					_, err = ct.KubeClient.CoreV1().ConfigMaps(cd.Namespace).Create(primaryConfigMap)
 					if err != nil {
-						return fmt.Errorf("creating configmap %s.%s failed: %w", primaryConfigMap.Name, cd.Namespace, err)
+						return err
 					}
 				} else {
-					return fmt.Errorf("updating configmap %s.%s failed: %w", primaryConfigMap.Name, cd.Namespace, err)
+					return err
 				}
 			}
 
@@ -309,7 +323,7 @@ func (ct *ConfigTracker) CreatePrimaryConfigs(cd *flaggerv1.Canary, refs map[str
 		case ConfigRefSecret:
 			secret, err := ct.KubeClient.CoreV1().Secrets(cd.Namespace).Get(ref.Name, metav1.GetOptions{})
 			if err != nil {
-				return fmt.Errorf("secret %s.%s get query failed : %w", ref.Name, cd.Name, err)
+				return err
 			}
 			primaryName := fmt.Sprintf("%s-primary", secret.GetName())
 			primarySecret := &corev1.Secret{
@@ -335,10 +349,10 @@ func (ct *ConfigTracker) CreatePrimaryConfigs(cd *flaggerv1.Canary, refs map[str
 				if errors.IsNotFound(err) {
 					_, err = ct.KubeClient.CoreV1().Secrets(cd.Namespace).Create(primarySecret)
 					if err != nil {
-						return fmt.Errorf("creating secret %s.%s failed: %w", primarySecret.Name, cd.Namespace, err)
+						return err
 					}
 				} else {
-					return fmt.Errorf("updating secret %s.%s failed: %w", primarySecret.Name, cd.Namespace, err)
+					return err
 				}
 			}
 
diff --git a/pkg/canary/controller.go b/pkg/canary/controller.go
index d54c1a20..c615cc5e 100644
--- a/pkg/canary/controller.go
+++ b/pkg/canary/controller.go
@@ -5,7 +5,7 @@ import (
 )
 
 type Controller interface {
-	IsPrimaryReady(canary *flaggerv1.Canary) error
+	IsPrimaryReady(canary *flaggerv1.Canary) (bool, error)
 	IsCanaryReady(canary *flaggerv1.Canary) (bool, error)
 	GetMetadata(canary *flaggerv1.Canary) (string, map[string]int32, error)
 	SyncStatus(canary *flaggerv1.Canary, status flaggerv1.CanaryStatus) error
@@ -17,6 +17,6 @@ type Controller interface {
 	Promote(canary *flaggerv1.Canary) error
 	HasTargetChanged(canary *flaggerv1.Canary) (bool, error)
 	HaveDependenciesChanged(canary *flaggerv1.Canary) (bool, error)
-	ScaleToZero(canary *flaggerv1.Canary) error
+	Scale(canary *flaggerv1.Canary, replicas int32) error
 	ScaleFromZero(canary *flaggerv1.Canary) error
 }
diff --git a/pkg/canary/daemonset_controller.go b/pkg/canary/daemonset_controller.go
index 0642f6b8..0041247b 100644
--- a/pkg/canary/daemonset_controller.go
+++ b/pkg/canary/daemonset_controller.go
@@ -28,27 +28,33 @@ type DaemonSetController struct {
 	labels        []string
 }
 
-func (c *DaemonSetController) ScaleToZero(cd *flaggerv1.Canary) error {
-	targetName := cd.Spec.TargetRef.Name
-	dae, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
-	if err != nil {
-		return fmt.Errorf("daemonset %s.%s get query error: %w", targetName, cd.Namespace, err)
-	}
+func (c *DaemonSetController) Scale(cd *flaggerv1.Canary, v int32) error {
+	// there's no concept `replicas` for DaemonSet
+	if v == 0 {
+		targetName := cd.Spec.TargetRef.Name
+		dae, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
+		if err != nil {
+			if errors.IsNotFound(err) {
+				return fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
+			}
+			return fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
+		}
 
-	daeCopy := dae.DeepCopy()
-	daeCopy.Spec.Template.Spec.NodeSelector = make(map[string]string,
-		len(dae.Spec.Template.Spec.NodeSelector)+len(daemonSetScaleDownNodeSelector))
-	for k, v := range dae.Spec.Template.Spec.NodeSelector {
-		daeCopy.Spec.Template.Spec.NodeSelector[k] = v
-	}
+		daeCopy := dae.DeepCopy()
+		daeCopy.Spec.Template.Spec.NodeSelector = make(map[string]string,
+			len(dae.Spec.Template.Spec.NodeSelector)+len(daemonSetScaleDownNodeSelector))
+		for k, v := range dae.Spec.Template.Spec.NodeSelector {
+			daeCopy.Spec.Template.Spec.NodeSelector[k] = v
+		}
 
-	for k, v := range daemonSetScaleDownNodeSelector {
-		daeCopy.Spec.Template.Spec.NodeSelector[k] = v
-	}
+		for k, v := range daemonSetScaleDownNodeSelector {
+			daeCopy.Spec.Template.Spec.NodeSelector[k] = v
+		}
 
-	_, err = c.kubeClient.AppsV1().DaemonSets(dae.Namespace).Update(daeCopy)
-	if err != nil {
-		return fmt.Errorf("updating daemonset %s.%s failed: %w", daeCopy.GetName(), daeCopy.Namespace, err)
+		_, err = c.kubeClient.AppsV1().DaemonSets(dae.Namespace).Update(daeCopy)
+		if err != nil {
+			return fmt.Errorf("scaling down daemonset %s.%s failed: %v", daeCopy.GetName(), daeCopy.Namespace, err)
+		}
 	}
 	return nil
 }
@@ -57,7 +63,10 @@ func (c *DaemonSetController) ScaleFromZero(cd *flaggerv1.Canary) error {
 	targetName := cd.Spec.TargetRef.Name
 	dep, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("daemonset %s.%s query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
+		}
+		return fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
 	}
 
 	depCopy := dep.DeepCopy()
@@ -67,30 +76,31 @@ func (c *DaemonSetController) ScaleFromZero(cd *flaggerv1.Canary) error {
 
 	_, err = c.kubeClient.AppsV1().DaemonSets(dep.Namespace).Update(depCopy)
 	if err != nil {
-		return fmt.Errorf("scaling up daemonset %s.%s failed: %w", depCopy.GetName(), depCopy.Namespace, err)
+		return fmt.Errorf("scaling up daemonset %s.%s failed: %v", depCopy.GetName(), depCopy.Namespace, err)
 	}
 	return nil
 }
 
-// Initialize creates the primary DaemonSet, scales down the canary DaemonSet,
-// and returns the pod selector label and container ports
+// Initialize creates the primary DaemonSet and
+// delete the canary DaemonSet and returns the pod selector label and container ports
 func (c *DaemonSetController) Initialize(cd *flaggerv1.Canary, skipLivenessChecks bool) (err error) {
+	primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
 	err = c.createPrimaryDaemonSet(cd)
 	if err != nil {
-		return fmt.Errorf("createPrimaryDaemonSet failed: %w", err)
+		return fmt.Errorf("creating daemonset %s.%s failed: %v", primaryName, cd.Namespace, err)
 	}
 
 	if cd.Status.Phase == "" || cd.Status.Phase == flaggerv1.CanaryPhaseInitializing {
 		if !skipLivenessChecks && !cd.SkipAnalysis() {
-			if err := c.IsPrimaryReady(cd); err != nil {
-				return fmt.Errorf("IsPrimaryReady failed: %w", err)
+			_, readyErr := c.IsPrimaryReady(cd)
+			if readyErr != nil {
+				return readyErr
 			}
 		}
 
-		c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).
-			Infof("Scaling down DaemonSet %s.%s", cd.Spec.TargetRef.Name, cd.Namespace)
-		if err := c.ScaleToZero(cd); err != nil {
-			return fmt.Errorf("ScaleToZero failed: %w", err)
+		c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof("Scaling down %s.%s", cd.Spec.TargetRef.Name, cd.Namespace)
+		if err := c.Scale(cd, 0); err != nil {
+			return err
 		}
 	}
 	return nil
@@ -103,26 +113,33 @@ func (c *DaemonSetController) Promote(cd *flaggerv1.Canary) error {
 
 	canary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("damonset %s.%s get query error: %v", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("damonset %s.%s not found", targetName, cd.Namespace)
+		}
+		return fmt.Errorf("damonset %s.%s query error %v", targetName, cd.Namespace, err)
 	}
 
 	label, err := c.getSelectorLabel(canary)
 	if err != nil {
-		return fmt.Errorf("getSelectorLabel failed: %w", err)
+		return fmt.Errorf("invalid label selector! DaemonSet %s.%s spec.selector.matchLabels must contain selector 'app: %s'",
+			targetName, cd.Namespace, targetName)
 	}
 
 	primary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(primaryName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("daemonset %s.%s get query error: %w", primaryName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("daemonset %s.%s not found", primaryName, cd.Namespace)
+		}
+		return fmt.Errorf("daemonset %s.%s query error %v", primaryName, cd.Namespace, err)
 	}
 
 	// promote secrets and config maps
 	configRefs, err := c.configTracker.GetTargetConfigs(cd)
 	if err != nil {
-		return fmt.Errorf("GetTargetConfigs failed: %w", err)
+		return err
 	}
 	if err := c.configTracker.CreatePrimaryConfigs(cd, configRefs); err != nil {
-		return fmt.Errorf("CreatePrimaryConfigs failed: %w", err)
+		return err
 	}
 
 	primaryCopy := primary.DeepCopy()
@@ -141,16 +158,16 @@ func (c *DaemonSetController) Promote(cd *flaggerv1.Canary) error {
 	// update pod annotations to ensure a rolling update
 	annotations, err := makeAnnotations(canary.Spec.Template.Annotations)
 	if err != nil {
-		return fmt.Errorf("makeAnnotations failed: %w", err)
+		return err
 	}
-
 	primaryCopy.Spec.Template.Annotations = annotations
+
 	primaryCopy.Spec.Template.Labels = makePrimaryLabels(canary.Spec.Template.Labels, primaryName, label)
 
 	// apply update
 	_, err = c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Update(primaryCopy)
 	if err != nil {
-		return fmt.Errorf("updating daemonset %s.%s template spec failed: %w",
+		return fmt.Errorf("updating deployment %s.%s template spec failed: %v",
 			primaryCopy.GetName(), primaryCopy.Namespace, err)
 	}
 	return nil
@@ -161,7 +178,10 @@ func (c *DaemonSetController) HasTargetChanged(cd *flaggerv1.Canary) (bool, erro
 	targetName := cd.Spec.TargetRef.Name
 	canary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return false, fmt.Errorf("daemonset %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return false, fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
+		}
+		return false, fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
 	}
 
 	// ignore `daemonSetScaleDownNodeSelector` node selector
@@ -183,18 +203,27 @@ func (c *DaemonSetController) GetMetadata(cd *flaggerv1.Canary) (string, map[str
 
 	canaryDae, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return "", nil, fmt.Errorf("daemonset %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return "", nil, fmt.Errorf("daemonset %s.%s not found, retrying", targetName, cd.Namespace)
+		}
+		return "", nil, err
 	}
 
 	label, err := c.getSelectorLabel(canaryDae)
 	if err != nil {
-		return "", nil, fmt.Errorf("getSelectorLabel failed: %w", err)
+		return "", nil, fmt.Errorf("invalid label selector! DaemonSet %s.%s spec.selector.matchLabels must contain selector 'app: %s'",
+			targetName, cd.Namespace, targetName)
 	}
 
 	var ports map[string]int32
 	if cd.Spec.Service.PortDiscovery {
-		ports = getPorts(cd, canaryDae.Spec.Template.Spec.Containers)
+		p, err := getPorts(cd, canaryDae.Spec.Template.Spec.Containers)
+		if err != nil {
+			return "", nil, fmt.Errorf("port discovery failed with error: %v", err)
+		}
+		ports = p
 	}
+
 	return label, ports, nil
 }
 
@@ -204,7 +233,10 @@ func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary) error
 
 	canaryDae, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("daemonset %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("daemonset %s.%s not found, retrying", targetName, cd.Namespace)
+		}
+		return err
 	}
 
 	if canaryDae.Spec.UpdateStrategy.Type != "" &&
@@ -215,26 +247,27 @@ func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary) error
 
 	label, err := c.getSelectorLabel(canaryDae)
 	if err != nil {
-		return fmt.Errorf("getSelectorLabel failed: %w", err)
+		return fmt.Errorf("invalid label selector! DaemonSet %s.%s spec.selector.matchLabels must contain selector 'app: %s'",
+			targetName, cd.Namespace, targetName)
 	}
 
-	primaryDae, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(primaryName, metav1.GetOptions{})
+	primaryDep, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(primaryName, metav1.GetOptions{})
 	if errors.IsNotFound(err) {
 		// create primary secrets and config maps
 		configRefs, err := c.configTracker.GetTargetConfigs(cd)
 		if err != nil {
-			return fmt.Errorf("GetTargetConfigs failed: %w", err)
+			return err
 		}
 		if err := c.configTracker.CreatePrimaryConfigs(cd, configRefs); err != nil {
-			return fmt.Errorf("CreatePrimaryConfigs failed: %w", err)
+			return err
 		}
 		annotations, err := makeAnnotations(canaryDae.Spec.Template.Annotations)
 		if err != nil {
-			return fmt.Errorf("makeAnnotations failed: %w", err)
+			return err
 		}
 
-		// create primary daemonset
-		primaryDae = &appsv1.DaemonSet{
+		// create primary deployment
+		primaryDep = &appsv1.DaemonSet{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      primaryName,
 				Namespace: cd.Namespace,
@@ -269,12 +302,12 @@ func (c *DaemonSetController) createPrimaryDaemonSet(cd *flaggerv1.Canary) error
 			},
 		}
 
-		_, err = c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Create(primaryDae)
+		_, err = c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Create(primaryDep)
 		if err != nil {
-			return fmt.Errorf("creating daemonset %s.%s failed: %w", primaryDae.Name, cd.Namespace, err)
+			return err
 		}
 
-		c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof("DaemonSet %s.%s created", primaryDae.GetName(), cd.Namespace)
+		c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof("DaemonSet %s.%s created", primaryDep.GetName(), cd.Namespace)
 	}
 	return nil
 }
@@ -287,10 +320,7 @@ func (c *DaemonSetController) getSelectorLabel(daemonSet *appsv1.DaemonSet) (str
 		}
 	}
 
-	return "", fmt.Errorf(
-		"daemonset %s.%s spec.selector.matchLabels must contain one of %v'",
-		daemonSet.Name, daemonSet.Namespace, c.labels,
-	)
+	return "", fmt.Errorf("selector not found")
 }
 
 func (c *DaemonSetController) HaveDependenciesChanged(cd *flaggerv1.Canary) (bool, error) {
diff --git a/pkg/canary/daemonset_ready.go b/pkg/canary/daemonset_ready.go
index 1c5a392d..4120082a 100644
--- a/pkg/canary/daemonset_ready.go
+++ b/pkg/canary/daemonset_ready.go
@@ -5,6 +5,7 @@ import (
 	"time"
 
 	appsv1 "k8s.io/api/apps/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -12,18 +13,21 @@ import (
 
 // IsPrimaryReady checks the primary daemonset status and returns an error if
 // the daemonset is in the middle of a rolling update
-func (c *DaemonSetController) IsPrimaryReady(cd *flaggerv1.Canary) error {
+func (c *DaemonSetController) IsPrimaryReady(cd *flaggerv1.Canary) (bool, error) {
 	primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
 	primary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(primaryName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("daemonset %s.%s get query error: %w", primaryName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return true, fmt.Errorf("deployment %s.%s not found", primaryName, cd.Namespace)
+		}
+		return true, fmt.Errorf("deployment %s.%s query error %v", primaryName, cd.Namespace, err)
 	}
 
-	_, err = c.isDaemonSetReady(cd, primary)
+	retriable, err := c.isDaemonSetReady(cd, primary)
 	if err != nil {
-		return fmt.Errorf("primary daemonset %s.%s not ready: %w", primaryName, cd.Namespace, err)
+		return retriable, fmt.Errorf("halt advancement %s.%s %s", primaryName, cd.Namespace, err.Error())
 	}
-	return nil
+	return true, nil
 }
 
 // IsCanaryReady checks the primary daemonset and returns an error if
@@ -32,13 +36,15 @@ func (c *DaemonSetController) IsCanaryReady(cd *flaggerv1.Canary) (bool, error)
 	targetName := cd.Spec.TargetRef.Name
 	canary, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return true, fmt.Errorf("daemonset %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return true, fmt.Errorf("daemonset %s.%s not found", targetName, cd.Namespace)
+		}
+		return true, fmt.Errorf("daemonset %s.%s query error %v", targetName, cd.Namespace, err)
 	}
 
-	retryable, err := c.isDaemonSetReady(cd, canary)
+	retriable, err := c.isDaemonSetReady(cd, canary)
 	if err != nil {
-		return retryable, fmt.Errorf("canary damonset %s.%s not ready with retryable %v: %w",
-			targetName, cd.Namespace, retryable, err)
+		return retriable, fmt.Errorf("halt advancement %s.%s %s", targetName, cd.Namespace, err.Error())
 	}
 	return true, nil
 }
@@ -50,7 +56,7 @@ func (c *DaemonSetController) isDaemonSetReady(cd *flaggerv1.Canary, daemonSet *
 		delta := time.Duration(cd.GetProgressDeadlineSeconds()) * time.Second
 		dl := from.Add(delta)
 		if dl.Before(time.Now()) {
-			return false, fmt.Errorf("exceeded its progressDeadlineSeconds: %d", cd.GetProgressDeadlineSeconds())
+			return false, fmt.Errorf("daemonset %s exceeded its progress deadline", cd.GetName())
 		} else {
 			return true, fmt.Errorf(
 				"waiting for rollout to finish: desiredNumberScheduled=%d, updatedNumberScheduled=%d, numberUnavailable=%d",
diff --git a/pkg/canary/daemonset_status.go b/pkg/canary/daemonset_status.go
index dd558f96..49329866 100644
--- a/pkg/canary/daemonset_status.go
+++ b/pkg/canary/daemonset_status.go
@@ -3,6 +3,8 @@ package canary
 import (
 	"fmt"
 
+	ex "github.com/pkg/errors"
+	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -12,7 +14,10 @@ import (
 func (c *DaemonSetController) SyncStatus(cd *flaggerv1.Canary, status flaggerv1.CanaryStatus) error {
 	dae, err := c.kubeClient.AppsV1().DaemonSets(cd.Namespace).Get(cd.Spec.TargetRef.Name, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("daemonset %s.%s get query error: %w", cd.Spec.TargetRef.Name, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("daemonset %s.%s not found", cd.Spec.TargetRef.Name, cd.Namespace)
+		}
+		return ex.Wrap(err, "SyncStatus daemonset query error")
 	}
 
 	// ignore `daemonSetScaleDownNodeSelector` node selector
@@ -27,7 +32,7 @@ func (c *DaemonSetController) SyncStatus(cd *flaggerv1.Canary, status flaggerv1.
 
 	configs, err := c.configTracker.GetConfigRefs(cd)
 	if err != nil {
-		return fmt.Errorf("GetConfigRefs failed: %w", err)
+		return ex.Wrap(err, "SyncStatus configs query error")
 	}
 
 	return syncCanaryStatus(c.flaggerClient, cd, status, dae.Spec.Template, func(cdCopy *flaggerv1.Canary) {
diff --git a/pkg/canary/deployment_controller.go b/pkg/canary/deployment_controller.go
index 3a89526c..96b6b77f 100644
--- a/pkg/canary/deployment_controller.go
+++ b/pkg/canary/deployment_controller.go
@@ -30,32 +30,29 @@ type DeploymentController struct {
 // scales to zero the canary deployment and returns the pod selector label and container ports
 func (c *DeploymentController) Initialize(cd *flaggerv1.Canary, skipLivenessChecks bool) (err error) {
 	primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
-	if err := c.createPrimaryDeployment(cd); err != nil {
-		return fmt.Errorf("createPrimaryDeployment failed: %w", err)
+
+	err = c.createPrimaryDeployment(cd)
+	if err != nil {
+		return fmt.Errorf("creating deployment %s.%s failed: %v", primaryName, cd.Namespace, err)
 	}
 
 	if cd.Status.Phase == "" || cd.Status.Phase == flaggerv1.CanaryPhaseInitializing {
 		if !skipLivenessChecks && !cd.SkipAnalysis() {
-			if err := c.IsPrimaryReady(cd); err != nil {
-				return fmt.Errorf("IsPrimaryReady failed: %w", err)
+			_, readyErr := c.IsPrimaryReady(cd)
+			if readyErr != nil {
+				return readyErr
 			}
 		}
 
-		c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).
-			Infof("Scaling down Deployment %s.%s", cd.Spec.TargetRef.Name, cd.Namespace)
-		if err := c.ScaleToZero(cd); err != nil {
-			return fmt.Errorf("scaling down canary daemon set %s.%s failed: %w", cd.Spec.TargetRef.Name, cd.Namespace, err)
+		c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof("Scaling down %s.%s", cd.Spec.TargetRef.Name, cd.Namespace)
+		if err := c.Scale(cd, 0); err != nil {
+			return err
 		}
 	}
 
-	if cd.Spec.AutoscalerRef != nil {
-		if cd.Spec.AutoscalerRef.Kind == "HorizontalPodAutoscaler" {
-			if err := c.reconcilePrimaryHpa(cd, true); err != nil {
-				return fmt.Errorf(
-					"initial reconcilePrimaryHpa for %s.%s failed: %w", primaryName, cd.Namespace, err)
-			}
-		} else {
-			return fmt.Errorf("cd.Spec.AutoscalerRef.Kind is invalid: %s", cd.Spec.AutoscalerRef.Kind)
+	if cd.Spec.AutoscalerRef != nil && cd.Spec.AutoscalerRef.Kind == "HorizontalPodAutoscaler" {
+		if err := c.reconcilePrimaryHpa(cd, true); err != nil {
+			return fmt.Errorf("creating HorizontalPodAutoscaler %s.%s failed: %v", primaryName, cd.Namespace, err)
 		}
 	}
 	return nil
@@ -68,26 +65,33 @@ func (c *DeploymentController) Promote(cd *flaggerv1.Canary) error {
 
 	canary, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("deployment %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("deployment %s.%s not found", targetName, cd.Namespace)
+		}
+		return fmt.Errorf("deployment %s.%s query error %v", targetName, cd.Namespace, err)
 	}
 
 	label, err := c.getSelectorLabel(canary)
 	if err != nil {
-		return fmt.Errorf("getSelectorLabel failed: %w", err)
+		return fmt.Errorf("invalid label selector! Deployment %s.%s spec.selector.matchLabels must contain selector 'app: %s'",
+			targetName, cd.Namespace, targetName)
 	}
 
 	primary, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(primaryName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("deployment %s.%s get query error: %w", primaryName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("deployment %s.%s not found", primaryName, cd.Namespace)
+		}
+		return fmt.Errorf("deployment %s.%s query error %v", primaryName, cd.Namespace, err)
 	}
 
 	// promote secrets and config maps
 	configRefs, err := c.configTracker.GetTargetConfigs(cd)
 	if err != nil {
-		return fmt.Errorf("GetTargetConfigs failed: %w", err)
+		return err
 	}
 	if err := c.configTracker.CreatePrimaryConfigs(cd, configRefs); err != nil {
-		return fmt.Errorf("CreatePrimaryConfigs failed: %w", err)
+		return err
 	}
 
 	primaryCopy := primary.DeepCopy()
@@ -102,30 +106,26 @@ func (c *DeploymentController) Promote(cd *flaggerv1.Canary) error {
 	// update pod annotations to ensure a rolling update
 	annotations, err := makeAnnotations(canary.Spec.Template.Annotations)
 	if err != nil {
-		return fmt.Errorf("makeAnnotations failed: %w", err)
+		return err
 	}
-
 	primaryCopy.Spec.Template.Annotations = annotations
+
 	primaryCopy.Spec.Template.Labels = makePrimaryLabels(canary.Spec.Template.Labels, primaryName, label)
 
 	// apply update
 	_, err = c.kubeClient.AppsV1().Deployments(cd.Namespace).Update(primaryCopy)
 	if err != nil {
-		return fmt.Errorf("updating deployment %s.%s template spec failed: %w",
+		return fmt.Errorf("updating deployment %s.%s template spec failed: %v",
 			primaryCopy.GetName(), primaryCopy.Namespace, err)
 	}
 
 	// update HPA
-	if cd.Spec.AutoscalerRef != nil {
-		if cd.Spec.AutoscalerRef.Kind == "HorizontalPodAutoscaler" {
-			if err := c.reconcilePrimaryHpa(cd, false); err != nil {
-				return fmt.Errorf(
-					"reconcilePrimaryHpa for %s.%s failed: %w", primaryName, cd.Namespace, err)
-			}
-		} else {
-			return fmt.Errorf("cd.Spec.AutoscalerRef.Kind is invalid: %s", cd.Spec.AutoscalerRef.Kind)
+	if cd.Spec.AutoscalerRef != nil && cd.Spec.AutoscalerRef.Kind == "HorizontalPodAutoscaler" {
+		if err := c.reconcilePrimaryHpa(cd, false); err != nil {
+			return fmt.Errorf("updating HorizontalPodAutoscaler %s.%s failed: %v", primaryName, cd.Namespace, err)
 		}
 	}
+
 	return nil
 }
 
@@ -134,26 +134,32 @@ func (c *DeploymentController) HasTargetChanged(cd *flaggerv1.Canary) (bool, err
 	targetName := cd.Spec.TargetRef.Name
 	canary, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return false, fmt.Errorf("deployment %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return false, fmt.Errorf("deployment %s.%s not found", targetName, cd.Namespace)
+		}
+		return false, fmt.Errorf("deployment %s.%s query error %v", targetName, cd.Namespace, err)
 	}
 
 	return hasSpecChanged(cd, canary.Spec.Template)
 }
 
 // Scale sets the canary deployment replicas
-func (c *DeploymentController) ScaleToZero(cd *flaggerv1.Canary) error {
+func (c *DeploymentController) Scale(cd *flaggerv1.Canary, replicas int32) error {
 	targetName := cd.Spec.TargetRef.Name
 	dep, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("deployment %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("deployment %s.%s not found", targetName, cd.Namespace)
+		}
+		return fmt.Errorf("deployment %s.%s query error %v", targetName, cd.Namespace, err)
 	}
 
 	depCopy := dep.DeepCopy()
-	depCopy.Spec.Replicas = int32p(0)
+	depCopy.Spec.Replicas = int32p(replicas)
 
 	_, err = c.kubeClient.AppsV1().Deployments(dep.Namespace).Update(depCopy)
 	if err != nil {
-		return fmt.Errorf("deployment %s.%s update query error: %w", targetName, cd.Namespace, err)
+		return fmt.Errorf("scaling %s.%s to %v failed: %v", depCopy.GetName(), depCopy.Namespace, replicas, err)
 	}
 	return nil
 }
@@ -162,7 +168,10 @@ func (c *DeploymentController) ScaleFromZero(cd *flaggerv1.Canary) error {
 	targetName := cd.Spec.TargetRef.Name
 	dep, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("deployment %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("deployment %s.%s not found", targetName, cd.Namespace)
+		}
+		return fmt.Errorf("deployment %s.%s query error %v", targetName, cd.Namespace, err)
 	}
 
 	replicas := int32p(1)
@@ -174,7 +183,7 @@ func (c *DeploymentController) ScaleFromZero(cd *flaggerv1.Canary) error {
 
 	_, err = c.kubeClient.AppsV1().Deployments(dep.Namespace).Update(depCopy)
 	if err != nil {
-		return fmt.Errorf("scaling up %s.%s to %v failed: %v", depCopy.GetName(), depCopy.Namespace, replicas, err)
+		return fmt.Errorf("scaling %s.%s to %v failed: %v", depCopy.GetName(), depCopy.Namespace, replicas, err)
 	}
 	return nil
 }
@@ -185,17 +194,25 @@ func (c *DeploymentController) GetMetadata(cd *flaggerv1.Canary) (string, map[st
 
 	canaryDep, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return "", nil, fmt.Errorf("deployment %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return "", nil, fmt.Errorf("deployment %s.%s not found, retrying", targetName, cd.Namespace)
+		}
+		return "", nil, err
 	}
 
 	label, err := c.getSelectorLabel(canaryDep)
 	if err != nil {
-		return "", nil, fmt.Errorf("getSelectorLabel failed: %w", err)
+		return "", nil, fmt.Errorf("invalid label selector! Deployment %s.%s spec.selector.matchLabels must contain selector 'app: %s'",
+			targetName, cd.Namespace, targetName)
 	}
 
 	var ports map[string]int32
 	if cd.Spec.Service.PortDiscovery {
-		ports = getPorts(cd, canaryDep.Spec.Template.Spec.Containers)
+		p, err := getPorts(cd, canaryDep.Spec.Template.Spec.Containers)
+		if err != nil {
+			return "", nil, fmt.Errorf("port discovery failed with error: %v", err)
+		}
+		ports = p
 	}
 
 	return label, ports, nil
@@ -206,12 +223,16 @@ func (c *DeploymentController) createPrimaryDeployment(cd *flaggerv1.Canary) err
 
 	canaryDep, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("deplyoment %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("deployment %s.%s not found, retrying", targetName, cd.Namespace)
+		}
+		return err
 	}
 
 	label, err := c.getSelectorLabel(canaryDep)
 	if err != nil {
-		return fmt.Errorf("getSelectorLabel failed: %w", err)
+		return fmt.Errorf("invalid label selector! Deployment %s.%s spec.selector.matchLabels must contain selector 'app: %s'",
+			targetName, cd.Namespace, targetName)
 	}
 
 	primaryDep, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(primaryName, metav1.GetOptions{})
@@ -219,14 +240,14 @@ func (c *DeploymentController) createPrimaryDeployment(cd *flaggerv1.Canary) err
 		// create primary secrets and config maps
 		configRefs, err := c.configTracker.GetTargetConfigs(cd)
 		if err != nil {
-			return fmt.Errorf("GetTargetConfigs failed: %w", err)
+			return err
 		}
 		if err := c.configTracker.CreatePrimaryConfigs(cd, configRefs); err != nil {
-			return fmt.Errorf("CreatePrimaryConfigs failed: %w", err)
+			return err
 		}
 		annotations, err := makeAnnotations(canaryDep.Spec.Template.Annotations)
 		if err != nil {
-			return fmt.Errorf("makeAnnotations failed: %w", err)
+			return err
 		}
 
 		replicas := int32(1)
@@ -274,11 +295,10 @@ func (c *DeploymentController) createPrimaryDeployment(cd *flaggerv1.Canary) err
 
 		_, err = c.kubeClient.AppsV1().Deployments(cd.Namespace).Create(primaryDep)
 		if err != nil {
-			return fmt.Errorf("creating deployment %s.%s failed: %w", primaryDep.Name, cd.Namespace, err)
+			return err
 		}
 
-		c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).
-			Infof("Deployment %s.%s created", primaryDep.GetName(), cd.Namespace)
+		c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof("Deployment %s.%s created", primaryDep.GetName(), cd.Namespace)
 	}
 
 	return nil
@@ -288,8 +308,11 @@ func (c *DeploymentController) reconcilePrimaryHpa(cd *flaggerv1.Canary, init bo
 	primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
 	hpa, err := c.kubeClient.AutoscalingV2beta1().HorizontalPodAutoscalers(cd.Namespace).Get(cd.Spec.AutoscalerRef.Name, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("HorizontalPodAutoscaler %s.%s get query error: %w",
-			cd.Spec.AutoscalerRef.Name, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("HorizontalPodAutoscaler %s.%s not found, retrying",
+				cd.Spec.AutoscalerRef.Name, cd.Namespace)
+		}
+		return err
 	}
 
 	hpaSpec := hpav1.HorizontalPodAutoscalerSpec{
@@ -326,15 +349,14 @@ func (c *DeploymentController) reconcilePrimaryHpa(cd *flaggerv1.Canary, init bo
 
 		_, err = c.kubeClient.AutoscalingV2beta1().HorizontalPodAutoscalers(cd.Namespace).Create(primaryHpa)
 		if err != nil {
-			return fmt.Errorf("creating HorizontalPodAutoscaler %s.%s failed: %w",
-				primaryHpa.Name, primaryHpa.Namespace, err)
+			return err
 		}
-		c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof(
-			"HorizontalPodAutoscaler %s.%s created", primaryHpa.GetName(), cd.Namespace)
+		c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof("HorizontalPodAutoscaler %s.%s created", primaryHpa.GetName(), cd.Namespace)
 		return nil
-	} else if err != nil {
-		return fmt.Errorf("HorizontalPodAutoscaler %s.%s get query failed: %w",
-			primaryHpa.Name, primaryHpa.Namespace, err)
+	}
+
+	if err != nil {
+		return err
 	}
 
 	// update HPA
@@ -347,15 +369,14 @@ func (c *DeploymentController) reconcilePrimaryHpa(cd *flaggerv1.Canary, init bo
 			hpaClone.Spec.MinReplicas = hpaSpec.MinReplicas
 			hpaClone.Spec.Metrics = hpaSpec.Metrics
 
-			_, err := c.kubeClient.AutoscalingV2beta1().HorizontalPodAutoscalers(cd.Namespace).Update(hpaClone)
-			if err != nil {
-				return fmt.Errorf("updating HorizontalPodAutoscaler %s.%s failed: %w",
-					hpaClone.Name, hpaClone.Namespace, err)
+			_, upErr := c.kubeClient.AutoscalingV2beta1().HorizontalPodAutoscalers(cd.Namespace).Update(hpaClone)
+			if upErr != nil {
+				return upErr
 			}
-			c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).
-				Infof("HorizontalPodAutoscaler %s.%s updated", primaryHpa.GetName(), cd.Namespace)
+			c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).Infof("HorizontalPodAutoscaler %s.%s updated", primaryHpa.GetName(), cd.Namespace)
 		}
 	}
+
 	return nil
 }
 
@@ -367,10 +388,7 @@ func (c *DeploymentController) getSelectorLabel(deployment *appsv1.Deployment) (
 		}
 	}
 
-	return "", fmt.Errorf(
-		"deployment %s.%s spec.selector.matchLabels must contain one of %v",
-		deployment.Name, deployment.Namespace, c.labels,
-	)
+	return "", fmt.Errorf("selector not found")
 }
 
 func (c *DeploymentController) HaveDependenciesChanged(cd *flaggerv1.Canary) (bool, error) {
diff --git a/pkg/canary/deployment_ready.go b/pkg/canary/deployment_ready.go
index 86b2a776..083633f9 100644
--- a/pkg/canary/deployment_ready.go
+++ b/pkg/canary/deployment_ready.go
@@ -5,6 +5,7 @@ import (
 	"time"
 
 	appsv1 "k8s.io/api/apps/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -13,23 +14,26 @@ import (
 // IsPrimaryReady checks the primary deployment status and returns an error if
 // the deployment is in the middle of a rolling update or if the pods are unhealthy
 // it will return a non retriable error if the rolling update is stuck
-func (c *DeploymentController) IsPrimaryReady(cd *flaggerv1.Canary) error {
+func (c *DeploymentController) IsPrimaryReady(cd *flaggerv1.Canary) (bool, error) {
 	primaryName := fmt.Sprintf("%s-primary", cd.Spec.TargetRef.Name)
 	primary, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(primaryName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("deployment %s.%s get query error: %w", primaryName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return true, fmt.Errorf("deployment %s.%s not found", primaryName, cd.Namespace)
+		}
+		return true, fmt.Errorf("deployment %s.%s query error %v", primaryName, cd.Namespace, err)
 	}
 
-	_, err = c.isDeploymentReady(primary, cd.GetProgressDeadlineSeconds())
+	retriable, err := c.isDeploymentReady(primary, cd.GetProgressDeadlineSeconds())
 	if err != nil {
-		return fmt.Errorf("primary daemonset %s.%s not ready: %w", primaryName, cd.Namespace, err)
+		return retriable, fmt.Errorf("Halt advancement %s.%s %s", primaryName, cd.Namespace, err.Error())
 	}
 
 	if primary.Spec.Replicas == int32p(0) {
-		return fmt.Errorf("halt %s.%s advancement: primary deployment is scaled to zero",
+		return true, fmt.Errorf("Halt %s.%s advancement primary deployment is scaled to zero",
 			cd.Name, cd.Namespace)
 	}
-	return nil
+	return true, nil
 }
 
 // IsCanaryReady checks the canary deployment status and returns an error if
@@ -39,16 +43,22 @@ func (c *DeploymentController) IsCanaryReady(cd *flaggerv1.Canary) (bool, error)
 	targetName := cd.Spec.TargetRef.Name
 	canary, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return true, fmt.Errorf("deployment %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return true, fmt.Errorf("deployment %s.%s not found", targetName, cd.Namespace)
+		}
+		return true, fmt.Errorf("deployment %s.%s query error %v", targetName, cd.Namespace, err)
 	}
 
-	retryable, err := c.isDeploymentReady(canary, cd.GetProgressDeadlineSeconds())
+	retriable, err := c.isDeploymentReady(canary, cd.GetProgressDeadlineSeconds())
 	if err != nil {
-		return retryable, fmt.Errorf(
-			"canary deployment %s.%s not ready with retryable %v: %w",
-			targetName, cd.Namespace, retryable, err,
-		)
+		if retriable {
+			return retriable, fmt.Errorf("Halt advancement %s.%s %s", targetName, cd.Namespace, err.Error())
+		} else {
+			return retriable, fmt.Errorf("deployment does not have minimum availability for more than %vs",
+				cd.GetProgressDeadlineSeconds())
+		}
 	}
+
 	return true, nil
 }
 
@@ -83,9 +93,9 @@ func (c *DeploymentController) isDeploymentReady(deployment *appsv1.Deployment,
 		}
 
 	} else {
-		return true, fmt.Errorf(
-			"waiting for rollout to finish: observed deployment generation less then desired generation")
+		return true, fmt.Errorf("waiting for rollout to finish: observed deployment generation less then desired generation")
 	}
+
 	return true, nil
 }
 
diff --git a/pkg/canary/deployment_status.go b/pkg/canary/deployment_status.go
index 6c2babe6..e5577b51 100644
--- a/pkg/canary/deployment_status.go
+++ b/pkg/canary/deployment_status.go
@@ -3,6 +3,8 @@ package canary
 import (
 	"fmt"
 
+	ex "github.com/pkg/errors"
+	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -12,12 +14,15 @@ import (
 func (c *DeploymentController) SyncStatus(cd *flaggerv1.Canary, status flaggerv1.CanaryStatus) error {
 	dep, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(cd.Spec.TargetRef.Name, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("deployment %s.%s get query error: %w", cd.Spec.TargetRef.Name, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("deployment %s.%s not found", cd.Spec.TargetRef.Name, cd.Namespace)
+		}
+		return ex.Wrap(err, "SyncStatus deployment query error")
 	}
 
 	configs, err := c.configTracker.GetConfigRefs(cd)
 	if err != nil {
-		return fmt.Errorf("GetConfigRefs failed: %w", err)
+		return ex.Wrap(err, "SyncStatus configs query error")
 	}
 
 	return syncCanaryStatus(c.flaggerClient, cd, status, dep.Spec.Template, func(cdCopy *flaggerv1.Canary) {
diff --git a/pkg/canary/factory.go b/pkg/canary/factory.go
index 95f243b8..416a0c91 100644
--- a/pkg/canary/factory.go
+++ b/pkg/canary/factory.go
@@ -50,12 +50,12 @@ func (factory *Factory) Controller(kind string) Controller {
 		flaggerClient: factory.flaggerClient,
 	}
 
-	switch kind {
-	case "DaemonSet":
+	switch {
+	case kind == "DaemonSet":
 		return daemonSetCtrl
-	case "Deployment":
+	case kind == "Deployment":
 		return deploymentCtrl
-	case "Service":
+	case kind == "Service":
 		return serviceCtrl
 	default:
 		return deploymentCtrl
diff --git a/pkg/canary/nop_tracker.go b/pkg/canary/nop_tracker.go
index 866c08c4..d2234a0b 100644
--- a/pkg/canary/nop_tracker.go
+++ b/pkg/canary/nop_tracker.go
@@ -6,7 +6,8 @@ import (
 )
 
 // NopTracker no-operation tracker
-type NopTracker struct{}
+type NopTracker struct {
+}
 
 func (nt *NopTracker) GetTargetConfigs(*flaggerv1.Canary) (map[string]ConfigRef, error) {
 	res := make(map[string]ConfigRef)
@@ -25,6 +26,6 @@ func (nt *NopTracker) CreatePrimaryConfigs(*flaggerv1.Canary, map[string]ConfigR
 	return nil
 }
 
-func (nt *NopTracker) ApplyPrimaryConfigs(spec corev1.PodSpec, _ map[string]ConfigRef) corev1.PodSpec {
+func (nt *NopTracker) ApplyPrimaryConfigs(spec corev1.PodSpec, refs map[string]ConfigRef) corev1.PodSpec {
 	return spec
 }
diff --git a/pkg/canary/service_controller.go b/pkg/canary/service_controller.go
index c62949a2..3e3724d2 100644
--- a/pkg/canary/service_controller.go
+++ b/pkg/canary/service_controller.go
@@ -3,6 +3,7 @@ package canary
 import (
 	"fmt"
 
+	ex "github.com/pkg/errors"
 	"go.uber.org/zap"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/errors"
@@ -42,27 +43,31 @@ func (c *ServiceController) SetStatusPhase(cd *flaggerv1.Canary, phase flaggerv1
 }
 
 // GetMetadata returns the pod label selector and svc ports
-func (c *ServiceController) GetMetadata(_ *flaggerv1.Canary) (string, map[string]int32, error) {
+func (c *ServiceController) GetMetadata(cd *flaggerv1.Canary) (string, map[string]int32, error) {
 	return "", nil, nil
 }
 
 // Initialize creates or updates the primary and canary services to prepare for the canary release process targeted on the K8s service
-func (c *ServiceController) Initialize(cd *flaggerv1.Canary, _ bool) (err error) {
+func (c *ServiceController) Initialize(cd *flaggerv1.Canary, skipLivenessChecks bool) (err error) {
 	targetName := cd.Spec.TargetRef.Name
 	primaryName := fmt.Sprintf("%s-primary", targetName)
 	canaryName := fmt.Sprintf("%s-canary", targetName)
 
 	svc, err := c.kubeClient.CoreV1().Services(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("service %s.%s get query error: %w", primaryName, cd.Namespace, err)
+		return err
 	}
 
-	if err = c.reconcileCanaryService(cd, canaryName, svc); err != nil {
-		return fmt.Errorf("reconcileCanaryService failed: %w", err)
+	// canary svc
+	err = c.reconcileCanaryService(cd, canaryName, svc)
+	if err != nil {
+		return err
 	}
 
-	if err = c.reconcilePrimaryService(cd, primaryName, svc); err != nil {
-		return fmt.Errorf("reconcilePrimaryService failed: %w", err)
+	// primary svc
+	err = c.reconcilePrimaryService(cd, primaryName, svc)
+	if err != nil {
+		return err
 	}
 
 	return nil
@@ -72,29 +77,31 @@ func (c *ServiceController) reconcileCanaryService(canary *flaggerv1.Canary, nam
 	current, err := c.kubeClient.CoreV1().Services(canary.Namespace).Get(name, metav1.GetOptions{})
 	if errors.IsNotFound(err) {
 		return c.createService(canary, name, src)
-	} else if err != nil {
-		return fmt.Errorf("service %s.%s get query error: %w", name, canary.Namespace, err)
 	}
 
-	ns := buildService(canary, name, src)
+	if err != nil {
+		return fmt.Errorf("service %s query error %v", name, err)
+	}
 
-	if ns.Spec.Type == "ClusterIP" {
+	new := buildService(canary, name, src)
+
+	if new.Spec.Type == "ClusterIP" {
 		// We can't change this immutable field
-		ns.Spec.ClusterIP = current.Spec.ClusterIP
+		new.Spec.ClusterIP = current.Spec.ClusterIP
 	}
 
 	// We can't change this immutable field
-	ns.ObjectMeta.UID = current.ObjectMeta.UID
+	new.ObjectMeta.UID = current.ObjectMeta.UID
 
-	ns.ObjectMeta.ResourceVersion = current.ObjectMeta.ResourceVersion
+	new.ObjectMeta.ResourceVersion = current.ObjectMeta.ResourceVersion
 
-	_, err = c.kubeClient.CoreV1().Services(canary.Namespace).Update(ns)
+	_, err = c.kubeClient.CoreV1().Services(canary.Namespace).Update(new)
 	if err != nil {
-		return fmt.Errorf("updating service %s.%s failed: %w", name, canary.Namespace, err)
+		return err
 	}
 
 	c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
-		Infof("Service %s.%s updated", ns.GetName(), canary.Namespace)
+		Infof("Service %s.%s updated", new.GetName(), canary.Namespace)
 	return nil
 }
 
@@ -102,9 +109,12 @@ func (c *ServiceController) reconcilePrimaryService(canary *flaggerv1.Canary, na
 	_, err := c.kubeClient.CoreV1().Services(canary.Namespace).Get(name, metav1.GetOptions{})
 	if errors.IsNotFound(err) {
 		return c.createService(canary, name, src)
-	} else if err != nil {
-		return fmt.Errorf("service %s.%s get query error: %w", name, canary.Namespace, err)
 	}
+
+	if err != nil {
+		return fmt.Errorf("service %s query error %v", name, err)
+	}
+
 	return nil
 }
 
@@ -121,7 +131,7 @@ func (c *ServiceController) createService(canary *flaggerv1.Canary, name string,
 
 	_, err := c.kubeClient.CoreV1().Services(canary.Namespace).Create(svc)
 	if err != nil {
-		return fmt.Errorf("creating service %s.%s query error: %w", canary.Name, canary.Namespace, err)
+		return err
 	}
 
 	c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
@@ -146,6 +156,7 @@ func buildService(canary *flaggerv1.Canary, name string, src *corev1.Service) *c
 		//   Operation cannot be fulfilled on services "mysvc-canary": the object has been modified; please apply your changes to the latest version and try again
 		delete(svc.ObjectMeta.Annotations, "kubectl.kubernetes.io/last-applied-configuration")
 	}
+
 	return svc
 }
 
@@ -156,12 +167,18 @@ func (c *ServiceController) Promote(cd *flaggerv1.Canary) error {
 
 	canary, err := c.kubeClient.CoreV1().Services(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("service %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("service %s.%s not found", targetName, cd.Namespace)
+		}
+		return fmt.Errorf("service %s.%s query error %v", targetName, cd.Namespace, err)
 	}
 
 	primary, err := c.kubeClient.CoreV1().Services(cd.Namespace).Get(primaryName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("service %s.%s get query error: %w", primaryName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("service %s.%s not found", primaryName, cd.Namespace)
+		}
+		return fmt.Errorf("service %s.%s query error %v", primaryName, cd.Namespace, err)
 	}
 
 	primaryCopy := canary.DeepCopy()
@@ -175,7 +192,7 @@ func (c *ServiceController) Promote(cd *flaggerv1.Canary) error {
 	// apply update
 	_, err = c.kubeClient.CoreV1().Services(cd.Namespace).Update(primaryCopy)
 	if err != nil {
-		return fmt.Errorf("updating service %s.%s spec failed: %w",
+		return fmt.Errorf("updating service %s.%s spec failed: %v",
 			primaryCopy.GetName(), primaryCopy.Namespace, err)
 	}
 
@@ -187,37 +204,44 @@ func (c *ServiceController) HasTargetChanged(cd *flaggerv1.Canary) (bool, error)
 	targetName := cd.Spec.TargetRef.Name
 	canary, err := c.kubeClient.CoreV1().Services(cd.Namespace).Get(targetName, metav1.GetOptions{})
 	if err != nil {
-		return false, fmt.Errorf("service %s.%s get query error: %w", targetName, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return false, fmt.Errorf("service %s.%s not found", targetName, cd.Namespace)
+		}
+		return false, fmt.Errorf("service %s.%s query error %v", targetName, cd.Namespace, err)
 	}
+
 	return hasSpecChanged(cd, canary.Spec)
 }
 
 // Scale sets the canary deployment replicas
-func (c *ServiceController) ScaleToZero(_ *flaggerv1.Canary) error {
+func (c *ServiceController) Scale(cd *flaggerv1.Canary, replicas int32) error {
 	return nil
 }
 
-func (c *ServiceController) ScaleFromZero(_ *flaggerv1.Canary) error {
+func (c *ServiceController) ScaleFromZero(cd *flaggerv1.Canary) error {
 	return nil
 }
 
 func (c *ServiceController) SyncStatus(cd *flaggerv1.Canary, status flaggerv1.CanaryStatus) error {
 	dep, err := c.kubeClient.CoreV1().Services(cd.Namespace).Get(cd.Spec.TargetRef.Name, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("service %s.%s get query error: %w", cd.Spec.TargetRef.Name, cd.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("service %s.%s not found", cd.Spec.TargetRef.Name, cd.Namespace)
+		}
+		return ex.Wrap(err, "SyncStatus service query error")
 	}
 
 	return syncCanaryStatus(c.flaggerClient, cd, status, dep.Spec, func(cdCopy *flaggerv1.Canary) {})
 }
 
-func (c *ServiceController) HaveDependenciesChanged(_ *flaggerv1.Canary) (bool, error) {
+func (c *ServiceController) HaveDependenciesChanged(cd *flaggerv1.Canary) (bool, error) {
 	return false, nil
 }
 
-func (c *ServiceController) IsPrimaryReady(_ *flaggerv1.Canary) error {
-	return nil
+func (c *ServiceController) IsPrimaryReady(cd *flaggerv1.Canary) (bool, error) {
+	return true, nil
 }
 
-func (c *ServiceController) IsCanaryReady(_ *flaggerv1.Canary) (bool, error) {
+func (c *ServiceController) IsCanaryReady(cd *flaggerv1.Canary) (bool, error) {
 	return true, nil
 }
diff --git a/pkg/canary/status.go b/pkg/canary/status.go
index cfe1d657..d65fd979 100644
--- a/pkg/canary/status.go
+++ b/pkg/canary/status.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"strings"
 
+	ex "github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/util/retry"
@@ -16,12 +17,12 @@ func syncCanaryStatus(flaggerClient clientset.Interface, cd *flaggerv1.Canary, s
 	hash := computeHash(canaryResource)
 
 	firstTry := true
-	name, ns := cd.GetName(), cd.GetNamespace()
 	err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
+		var selErr error
 		if !firstTry {
-			cd, err = flaggerClient.FlaggerV1beta1().Canaries(ns).Get(name, metav1.GetOptions{})
-			if err != nil {
-				return fmt.Errorf("canary %s.%s get query failed: %w", name, ns, err)
+			cd, selErr = flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).Get(cd.GetName(), metav1.GetOptions{})
+			if selErr != nil {
+				return selErr
 			}
 		}
 
@@ -38,79 +39,72 @@ func syncCanaryStatus(flaggerClient clientset.Interface, cd *flaggerv1.Canary, s
 			cdCopy.Status.Conditions = conditions
 		}
 
-		if err = updateStatusWithUpgrade(flaggerClient, cdCopy); err != nil {
-			return fmt.Errorf("updateStatusWithUpgrade failed: %w", err)
-		}
+		err = updateStatusWithUpgrade(flaggerClient, cdCopy)
 		firstTry = false
 		return
 	})
-
 	if err != nil {
-		return fmt.Errorf("failed after retries: %w", err)
+		return ex.Wrap(err, "SyncStatus")
 	}
 	return nil
 }
 
 func setStatusFailedChecks(flaggerClient clientset.Interface, cd *flaggerv1.Canary, val int) error {
 	firstTry := true
-	name, ns := cd.GetName(), cd.GetNamespace()
 	err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
+		var selErr error
 		if !firstTry {
-			cd, err = flaggerClient.FlaggerV1beta1().Canaries(name).Get(ns, metav1.GetOptions{})
-			if err != nil {
-				return fmt.Errorf("canary %s.%s get query failed: %w", name, ns, err)
+			cd, selErr = flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).Get(cd.GetName(), metav1.GetOptions{})
+			if selErr != nil {
+				return selErr
 			}
 		}
 		cdCopy := cd.DeepCopy()
 		cdCopy.Status.FailedChecks = val
 		cdCopy.Status.LastTransitionTime = metav1.Now()
 
-		if err = updateStatusWithUpgrade(flaggerClient, cdCopy); err != nil {
-			return fmt.Errorf("updateStatusWithUpgrade failed: %w", err)
-		}
+		err = updateStatusWithUpgrade(flaggerClient, cdCopy)
 		firstTry = false
 		return
 	})
 	if err != nil {
-		return fmt.Errorf("failed after retries: %w", err)
+		return ex.Wrap(err, "SetStatusFailedChecks")
 	}
 	return nil
 }
 
 func setStatusWeight(flaggerClient clientset.Interface, cd *flaggerv1.Canary, val int) error {
 	firstTry := true
-	name, ns := cd.GetName(), cd.GetNamespace()
 	err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
+		var selErr error
 		if !firstTry {
-			cd, err = flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).Get(cd.GetName(), metav1.GetOptions{})
-			if err != nil {
-				return fmt.Errorf("canary %s.%s get query failed: %w", name, ns, err)
+			cd, selErr = flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).Get(cd.GetName(), metav1.GetOptions{})
+			if selErr != nil {
+				return selErr
 			}
 		}
 		cdCopy := cd.DeepCopy()
 		cdCopy.Status.CanaryWeight = val
 		cdCopy.Status.LastTransitionTime = metav1.Now()
 
-		if err = updateStatusWithUpgrade(flaggerClient, cdCopy); err != nil {
-			return fmt.Errorf("updateStatusWithUpgrade failed: %w", err)
-		}
+		err = updateStatusWithUpgrade(flaggerClient, cdCopy)
 		firstTry = false
 		return
 	})
 	if err != nil {
-		return fmt.Errorf("failed after retries: %w", err)
+		return ex.Wrap(err, "SetStatusWeight")
 	}
 	return nil
 }
 
 func setStatusIterations(flaggerClient clientset.Interface, cd *flaggerv1.Canary, val int) error {
 	firstTry := true
-	name, ns := cd.GetName(), cd.GetNamespace()
 	err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
+		var selErr error
 		if !firstTry {
-			cd, err = flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).Get(cd.GetName(), metav1.GetOptions{})
-			if err != nil {
-				return fmt.Errorf("canary %s.%s get query failed: %w", name, ns, err)
+			cd, selErr = flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).Get(cd.GetName(), metav1.GetOptions{})
+			if selErr != nil {
+				return selErr
 			}
 		}
 
@@ -118,27 +112,25 @@ func setStatusIterations(flaggerClient clientset.Interface, cd *flaggerv1.Canary
 		cdCopy.Status.Iterations = val
 		cdCopy.Status.LastTransitionTime = metav1.Now()
 
-		if err = updateStatusWithUpgrade(flaggerClient, cdCopy); err != nil {
-			return fmt.Errorf("updateStatusWithUpgrade failed: %w", err)
-		}
+		err = updateStatusWithUpgrade(flaggerClient, cdCopy)
 		firstTry = false
 		return
 	})
 
 	if err != nil {
-		return fmt.Errorf("failed after retries: %w", err)
+		return ex.Wrap(err, "SetStatusIterations")
 	}
 	return nil
 }
 
 func setStatusPhase(flaggerClient clientset.Interface, cd *flaggerv1.Canary, phase flaggerv1.CanaryPhase) error {
 	firstTry := true
-	name, ns := cd.GetName(), cd.GetNamespace()
 	err := retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
+		var selErr error
 		if !firstTry {
-			cd, err = flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).Get(cd.GetName(), metav1.GetOptions{})
-			if err != nil {
-				return fmt.Errorf("canary %s.%s get query failed: %w", name, ns, err)
+			cd, selErr = flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).Get(cd.GetName(), metav1.GetOptions{})
+			if selErr != nil {
+				return selErr
 			}
 		}
 
@@ -160,14 +152,12 @@ func setStatusPhase(flaggerClient clientset.Interface, cd *flaggerv1.Canary, pha
 			cdCopy.Status.Conditions = conditions
 		}
 
-		if err = updateStatusWithUpgrade(flaggerClient, cdCopy); err != nil {
-			return fmt.Errorf("updateStatusWithUpgrade failed: %w", err)
-		}
+		err = updateStatusWithUpgrade(flaggerClient, cdCopy)
 		firstTry = false
 		return
 	})
 	if err != nil {
-		return fmt.Errorf("failed after retries: %w", err)
+		return ex.Wrap(err, "SetStatusPhase")
 	}
 	return nil
 }
@@ -247,15 +237,13 @@ func updateStatusWithUpgrade(flaggerClient clientset.Interface, cd *flaggerv1.Ca
 	_, err := flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).UpdateStatus(cd)
 	if err != nil && strings.Contains(err.Error(), "flagger.app/v1alpha") {
 		// upgrade alpha resource
-		if _, updateErr := flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).Update(cd); updateErr != nil {
-			return fmt.Errorf("updating canary %s.%s from v1alpha to v1beta failed: %w", cd.Name, cd.Namespace, updateErr)
+		_, updateErr := flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).Update(cd)
+		if updateErr != nil {
+			return updateErr
 		}
+
 		// retry status update
 		_, err = flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).UpdateStatus(cd)
 	}
-
-	if err != nil {
-		return fmt.Errorf("updating canary %s.%s status failed: %w", cd.Name, cd.Namespace, err)
-	}
 	return err
 }
diff --git a/pkg/canary/util.go b/pkg/canary/util.go
index f4f58304..cbe9c0e8 100644
--- a/pkg/canary/util.go
+++ b/pkg/canary/util.go
@@ -16,7 +16,7 @@ var sidecars = map[string]bool{
 	"envoy":       true,
 }
 
-func getPorts(cd *flaggerv1.Canary, cs []corev1.Container) map[string]int32 {
+func getPorts(cd *flaggerv1.Canary, cs []corev1.Container) (map[string]int32, error) {
 	ports := make(map[string]int32, len(cs))
 	for _, container := range cs {
 		// exclude service mesh proxies based on container name
@@ -49,7 +49,7 @@ func getPorts(cd *flaggerv1.Canary, cs []corev1.Container) map[string]int32 {
 			ports[name] = p.ContainerPort
 		}
 	}
-	return ports
+	return ports, nil
 }
 
 // makeAnnotations appends an unique ID to annotations map
@@ -59,7 +59,7 @@ func makeAnnotations(annotations map[string]string) (map[string]string, error) {
 	uuid := make([]byte, 16)
 	n, err := io.ReadFull(rand.Reader, uuid)
 	if n != len(uuid) || err != nil {
-		return res, fmt.Errorf("%w", err)
+		return res, err
 	}
 	uuid[8] = uuid[8]&^0xc0 | 0x80
 	uuid[6] = uuid[6]&^0xf0 | 0x40
diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go
index 0b92c458..5c0d1caa 100644
--- a/pkg/controller/controller.go
+++ b/pkg/controller/controller.go
@@ -35,6 +35,7 @@ const controllerAgentName = "flagger"
 // Controller is managing the canary objects and schedules canary deployments
 type Controller struct {
 	kubeClient       kubernetes.Interface
+	istioClient      clientset.Interface
 	flaggerClient    clientset.Interface
 	flaggerInformers Informers
 	flaggerSynced    cache.InformerSynced
@@ -61,6 +62,7 @@ type Informers struct {
 
 func NewController(
 	kubeClient kubernetes.Interface,
+	istioClient clientset.Interface,
 	flaggerClient clientset.Interface,
 	flaggerInformers Informers,
 	flaggerWindow time.Duration,
@@ -87,6 +89,7 @@ func NewController(
 
 	ctrl := &Controller{
 		kubeClient:       kubeClient,
+		istioClient:      istioClient,
 		flaggerClient:    flaggerClient,
 		flaggerInformers: flaggerInformers,
 		flaggerSynced:    flaggerInformers.CanaryInformer.Informer().HasSynced,
@@ -189,7 +192,7 @@ func (c *Controller) processNextWorkItem() bool {
 		// Run the syncHandler, passing it the namespace/name string of the
 		// Foo resource to be synced.
 		if err := c.syncHandler(key); err != nil {
-			return fmt.Errorf("error syncing '%s': %w", key, err)
+			return fmt.Errorf("error syncing '%s': %s", key, err.Error())
 		}
 		// Finally, if no error occurs we Forget this item so it does not
 		// get queued again until another change happens.
@@ -227,7 +230,7 @@ func (c *Controller) syncHandler(key string) error {
 			_, err := c.flaggerClient.FlaggerV1beta1().Canaries(cd.Namespace).UpdateStatus(cdCopy)
 			if err != nil {
 				c.logger.Errorf("%s status condition update error: %v", key, err)
-				return fmt.Errorf("%s status condition update error: %w", key, err)
+				return fmt.Errorf("%s status condition update error: %v", key, err)
 			}
 		}
 	}
diff --git a/pkg/controller/events.go b/pkg/controller/events.go
index cc7bd22b..fb29915a 100644
--- a/pkg/controller/events.go
+++ b/pkg/controller/events.go
@@ -31,12 +31,14 @@ func (c *Controller) recordEventWarningf(r *flaggerv1.Canary, template string, a
 
 func (c *Controller) sendEventToWebhook(r *flaggerv1.Canary, eventType, template string, args []interface{}) {
 	webhookOverride := false
-	for _, canaryWebhook := range r.GetAnalysis().Webhooks {
-		if canaryWebhook.Type == flaggerv1.EventHook {
-			webhookOverride = true
-			err := CallEventWebhook(r, canaryWebhook.URL, fmt.Sprintf(template, args...), eventType)
-			if err != nil {
-				c.logger.With("canary", fmt.Sprintf("%s.%s", r.Name, r.Namespace)).Errorf("error sending event to webhook: %s", err)
+	if len(r.GetAnalysis().Webhooks) > 0 {
+		for _, canaryWebhook := range r.GetAnalysis().Webhooks {
+			if canaryWebhook.Type == flaggerv1.EventHook {
+				webhookOverride = true
+				err := CallEventWebhook(r, canaryWebhook.URL, fmt.Sprintf(template, args...), eventType)
+				if err != nil {
+					c.logger.With("canary", fmt.Sprintf("%s.%s", r.Name, r.Namespace)).Errorf("error sending event to webhook: %s", err)
+				}
 			}
 		}
 	}
@@ -50,6 +52,10 @@ func (c *Controller) sendEventToWebhook(r *flaggerv1.Canary, eventType, template
 }
 
 func (c *Controller) alert(canary *flaggerv1.Canary, message string, metadata bool, severity flaggerv1.AlertSeverity) {
+	if c.notifier == nil && len(canary.GetAnalysis().Alerts) == 0 {
+		return
+	}
+
 	var fields []notifier.Field
 	if metadata {
 		fields = alertMetadata(canary)
diff --git a/pkg/controller/scheduler.go b/pkg/controller/scheduler.go
index bba6b869..6d393b06 100644
--- a/pkg/controller/scheduler.go
+++ b/pkg/controller/scheduler.go
@@ -2,12 +2,15 @@ package controller
 
 import (
 	"fmt"
+	"strings"
 	"time"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
 	"github.com/weaveworks/flagger/pkg/canary"
+	"github.com/weaveworks/flagger/pkg/metrics/observers"
+	"github.com/weaveworks/flagger/pkg/metrics/providers"
 	"github.com/weaveworks/flagger/pkg/router"
 )
 
@@ -23,26 +26,26 @@ func (c *Controller) scheduleCanaries() {
 	stats := make(map[string]int)
 
 	c.canaries.Range(func(key interface{}, value interface{}) bool {
-		cn := value.(*flaggerv1.Canary)
+		canary := value.(*flaggerv1.Canary)
 
 		// format: <name>.<namespace>
 		name := key.(string)
-		current[name] = fmt.Sprintf("%s.%s", cn.Spec.TargetRef.Name, cn.Namespace)
+		current[name] = fmt.Sprintf("%s.%s", canary.Spec.TargetRef.Name, canary.Namespace)
 
 		job, exists := c.jobs[name]
 		// schedule new job for existing job with different analysis interval or non-existing job
-		if (exists && job.GetCanaryAnalysisInterval() != cn.GetAnalysisInterval()) || !exists {
+		if (exists && job.GetCanaryAnalysisInterval() != canary.GetAnalysisInterval()) || !exists {
 			if exists {
 				job.Stop()
 			}
 
 			newJob := CanaryJob{
-				Name:             cn.Name,
-				Namespace:        cn.Namespace,
+				Name:             canary.Name,
+				Namespace:        canary.Namespace,
 				function:         c.advanceCanary,
 				done:             make(chan bool),
-				ticker:           time.NewTicker(cn.GetAnalysisInterval()),
-				analysisInterval: cn.GetAnalysisInterval(),
+				ticker:           time.NewTicker(canary.GetAnalysisInterval()),
+				analysisInterval: canary.GetAnalysisInterval(),
 			}
 
 			c.jobs[name] = newJob
@@ -50,11 +53,11 @@ func (c *Controller) scheduleCanaries() {
 		}
 
 		// compute canaries per namespace total
-		t, ok := stats[cn.Namespace]
+		t, ok := stats[canary.Namespace]
 		if !ok {
-			stats[cn.Namespace] = 1
+			stats[canary.Namespace] = 1
 		} else {
-			stats[cn.Namespace] = t + 1
+			stats[canary.Namespace] = t + 1
 		}
 		return true
 	})
@@ -71,8 +74,7 @@ func (c *Controller) scheduleCanaries() {
 	for canaryName, targetName := range current {
 		for name, target := range current {
 			if name != canaryName && target == targetName {
-				c.logger.With("canary", canaryName).
-					Errorf("Bad things will happen! Found more than one canary with the same target %s", targetName)
+				c.logger.With("canary", canaryName).Errorf("Bad things will happen! Found more than one canary with the same target %s", targetName)
 			}
 		}
 	}
@@ -108,8 +110,8 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
 	}
 
 	// init Kubernetes router
-	kubeRouter := c.routerFactory.KubernetesRouter(cd.Spec.TargetRef.Kind, labelSelector, map[string]string{}, ports)
-	if err := kubeRouter.Initialize(cd); err != nil {
+	router := c.routerFactory.KubernetesRouter(cd.Spec.TargetRef.Kind, labelSelector, map[string]string{}, ports)
+	if err := router.Initialize(cd); err != nil {
 		c.recordEventWarningf(cd, "%v", err)
 		return
 	}
@@ -125,7 +127,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
 	meshRouter := c.routerFactory.MeshRouter(provider)
 
 	// create or update svc
-	if err := kubeRouter.Reconcile(cd); err != nil {
+	if err := router.Reconcile(cd); err != nil {
 		c.recordEventWarningf(cd, "%v", err)
 		return
 	}
@@ -161,7 +163,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
 
 	// check primary status
 	if !skipLivenessChecks && !cd.SkipAnalysis() {
-		if err := canaryController.IsPrimaryReady(cd); err != nil {
+		if _, err := canaryController.IsPrimaryReady(cd); err != nil {
 			c.recordEventWarningf(cd, "%v", err)
 			return
 		}
@@ -203,6 +205,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
 		}
 		if err := canaryController.SyncStatus(cd, status); err != nil {
 			c.recordEventWarningf(cd, "%v", err)
+			return
 		}
 		return
 	}
@@ -218,7 +221,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
 	}
 
 	// check if analysis should be skipped
-	if skip := c.shouldSkipAnalysis(cd, canaryController, meshRouter); skip {
+	if skip := c.shouldSkipAnalysis(cd, canaryController, meshRouter, primaryWeight, canaryWeight); skip {
 		return
 	}
 
@@ -255,7 +258,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
 
 	// scale canary to zero if promotion has finished
 	if cd.Status.Phase == flaggerv1.CanaryPhaseFinalising {
-		if err := canaryController.ScaleToZero(cd); err != nil {
+		if err := canaryController.Scale(cd, 0); err != nil {
 			c.recordEventWarningf(cd, "%v", err)
 			return
 		}
@@ -301,6 +304,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
 		if ok := c.runPreRolloutHooks(cd); !ok {
 			if err := canaryController.SetStatusFailedChecks(cd, cd.Status.FailedChecks+1); err != nil {
 				c.recordEventWarningf(cd, "%v", err)
+				return
 			}
 			return
 		}
@@ -308,6 +312,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
 		if ok := c.runAnalysis(cd); !ok {
 			if err := canaryController.SetStatusFailedChecks(cd, cd.Status.FailedChecks+1); err != nil {
 				c.recordEventWarningf(cd, "%v", err)
+				return
 			}
 			return
 		}
@@ -328,7 +333,7 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
 
 	// strategy: A/B testing
 	if len(cd.GetAnalysis().Match) > 0 && cd.GetAnalysis().Iterations > 0 {
-		c.runAB(cd, canaryController, meshRouter)
+		c.runAB(cd, canaryController, meshRouter, provider)
 		return
 	}
 
@@ -340,13 +345,12 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
 
 	// strategy: Canary progressive traffic increase
 	if cd.GetAnalysis().StepWeight > 0 {
-		c.runCanary(cd, canaryController, meshRouter, mirrored, canaryWeight, primaryWeight, maxWeight)
+		c.runCanary(cd, canaryController, meshRouter, provider, mirrored, canaryWeight, primaryWeight, maxWeight)
 	}
 
 }
 
-func (c *Controller) runCanary(canary *flaggerv1.Canary, canaryController canary.Controller,
-	meshRouter router.Interface, mirrored bool, canaryWeight int, primaryWeight int, maxWeight int) {
+func (c *Controller) runCanary(canary *flaggerv1.Canary, canaryController canary.Controller, meshRouter router.Interface, provider string, mirrored bool, canaryWeight int, primaryWeight int, maxWeight int) {
 	primaryName := fmt.Sprintf("%s-primary", canary.Spec.TargetRef.Name)
 
 	// increase traffic weight
@@ -416,8 +420,7 @@ func (c *Controller) runCanary(canary *flaggerv1.Canary, canaryController canary
 	}
 }
 
-func (c *Controller) runAB(canary *flaggerv1.Canary, canaryController canary.Controller,
-	meshRouter router.Interface) {
+func (c *Controller) runAB(canary *flaggerv1.Canary, canaryController canary.Controller, meshRouter router.Interface, provider string) {
 	primaryName := fmt.Sprintf("%s-primary", canary.Spec.TargetRef.Name)
 
 	// route traffic to canary and increment iterations
@@ -459,8 +462,7 @@ func (c *Controller) runAB(canary *flaggerv1.Canary, canaryController canary.Con
 	}
 }
 
-func (c *Controller) runBlueGreen(canary *flaggerv1.Canary, canaryController canary.Controller,
-	meshRouter router.Interface, provider string, mirrored bool) {
+func (c *Controller) runBlueGreen(canary *flaggerv1.Canary, canaryController canary.Controller, meshRouter router.Interface, provider string, mirrored bool) {
 	primaryName := fmt.Sprintf("%s-primary", canary.Spec.TargetRef.Name)
 
 	// increment iterations
@@ -529,40 +531,14 @@ func (c *Controller) runBlueGreen(canary *flaggerv1.Canary, canaryController can
 
 }
 
-func (c *Controller) runAnalysis(canary *flaggerv1.Canary) bool {
-	// run external checks
-	for _, webhook := range canary.GetAnalysis().Webhooks {
-		if webhook.Type == "" || webhook.Type == flaggerv1.RolloutHook {
-			err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
-			if err != nil {
-				c.recordEventWarningf(canary, "Halt %s.%s advancement external check %s failed %v",
-					canary.Name, canary.Namespace, webhook.Name, err)
-				return false
-			}
-		}
-	}
-
-	ok := c.runBuiltinMetricChecks(canary)
-	if !ok {
-		return ok
-	}
-
-	ok = c.runMetricChecks(canary)
-	if !ok {
-		return ok
-	}
-
-	return true
-}
-
-func (c *Controller) shouldSkipAnalysis(canary *flaggerv1.Canary, canaryController canary.Controller, meshRouter router.Interface) bool {
+func (c *Controller) shouldSkipAnalysis(canary *flaggerv1.Canary, canaryController canary.Controller, meshRouter router.Interface, primaryWeight int, canaryWeight int) bool {
 	if !canary.SkipAnalysis() {
 		return false
 	}
 
 	// route all traffic to primary
-	primaryWeight := 100
-	canaryWeight := 0
+	primaryWeight = 100
+	canaryWeight = 0
 	if err := meshRouter.SetRoutes(canary, primaryWeight, canaryWeight, false); err != nil {
 		c.recordEventWarningf(canary, "%v", err)
 		return false
@@ -578,7 +554,7 @@ func (c *Controller) shouldSkipAnalysis(canary *flaggerv1.Canary, canaryControll
 	}
 
 	// shutdown canary
-	if err := canaryController.ScaleToZero(canary); err != nil {
+	if err := canaryController.Scale(canary, 0); err != nil {
 		c.recordEventWarningf(canary, "%v", err)
 		return false
 	}
@@ -679,6 +655,357 @@ func (c *Controller) hasCanaryRevisionChanged(canary *flaggerv1.Canary, canaryCo
 	return false
 }
 
+func (c *Controller) runConfirmRolloutHooks(canary *flaggerv1.Canary, canaryController canary.Controller) bool {
+	for _, webhook := range canary.GetAnalysis().Webhooks {
+		if webhook.Type == flaggerv1.ConfirmRolloutHook {
+			err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
+			if err != nil {
+				if canary.Status.Phase != flaggerv1.CanaryPhaseWaiting {
+					if err := canaryController.SetStatusPhase(canary, flaggerv1.CanaryPhaseWaiting); err != nil {
+						c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).Errorf("%v", err)
+					}
+					c.recordEventWarningf(canary, "Halt %s.%s advancement waiting for approval %s",
+						canary.Name, canary.Namespace, webhook.Name)
+					c.alert(canary, "Canary is waiting for approval.", false, flaggerv1.SeverityWarn)
+				}
+				return false
+			} else {
+				if canary.Status.Phase == flaggerv1.CanaryPhaseWaiting {
+					if err := canaryController.SetStatusPhase(canary, flaggerv1.CanaryPhaseProgressing); err != nil {
+						c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).Errorf("%v", err)
+						return false
+					}
+					c.recordEventInfof(canary, "Confirm-rollout check %s passed", webhook.Name)
+					return false
+				}
+			}
+		}
+	}
+	return true
+}
+
+func (c *Controller) runConfirmPromotionHooks(canary *flaggerv1.Canary) bool {
+	for _, webhook := range canary.GetAnalysis().Webhooks {
+		if webhook.Type == flaggerv1.ConfirmPromotionHook {
+			err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
+			if err != nil {
+				c.recordEventWarningf(canary, "Halt %s.%s advancement waiting for promotion approval %s",
+					canary.Name, canary.Namespace, webhook.Name)
+				c.alert(canary, "Canary promotion is waiting for approval.", false, flaggerv1.SeverityWarn)
+				return false
+			} else {
+				c.recordEventInfof(canary, "Confirm-promotion check %s passed", webhook.Name)
+			}
+		}
+	}
+	return true
+}
+
+func (c *Controller) runPreRolloutHooks(canary *flaggerv1.Canary) bool {
+	for _, webhook := range canary.GetAnalysis().Webhooks {
+		if webhook.Type == flaggerv1.PreRolloutHook {
+			err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
+			if err != nil {
+				c.recordEventWarningf(canary, "Halt %s.%s advancement pre-rollout check %s failed %v",
+					canary.Name, canary.Namespace, webhook.Name, err)
+				return false
+			} else {
+				c.recordEventInfof(canary, "Pre-rollout check %s passed", webhook.Name)
+			}
+		}
+	}
+	return true
+}
+
+func (c *Controller) runPostRolloutHooks(canary *flaggerv1.Canary, phase flaggerv1.CanaryPhase) bool {
+	for _, webhook := range canary.GetAnalysis().Webhooks {
+		if webhook.Type == flaggerv1.PostRolloutHook {
+			err := CallWebhook(canary.Name, canary.Namespace, phase, webhook)
+			if err != nil {
+				c.recordEventWarningf(canary, "Post-rollout hook %s failed %v", webhook.Name, err)
+				return false
+			} else {
+				c.recordEventInfof(canary, "Post-rollout check %s passed", webhook.Name)
+			}
+		}
+	}
+	return true
+}
+
+func (c *Controller) runRollbackHooks(canary *flaggerv1.Canary, phase flaggerv1.CanaryPhase) bool {
+	for _, webhook := range canary.GetAnalysis().Webhooks {
+		if webhook.Type == flaggerv1.RollbackHook {
+			err := CallWebhook(canary.Name, canary.Namespace, phase, webhook)
+			if err != nil {
+				c.recordEventInfof(canary, "Rollback hook %s not signaling a rollback", webhook.Name)
+			} else {
+				c.recordEventWarningf(canary, "Rollback check %s passed", webhook.Name)
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func (c *Controller) runAnalysis(canary *flaggerv1.Canary) bool {
+	// run external checks
+	for _, webhook := range canary.GetAnalysis().Webhooks {
+		if webhook.Type == "" || webhook.Type == flaggerv1.RolloutHook {
+			err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
+			if err != nil {
+				c.recordEventWarningf(canary, "Halt %s.%s advancement external check %s failed %v",
+					canary.Name, canary.Namespace, webhook.Name, err)
+				return false
+			}
+		}
+	}
+
+	ok := c.runBuiltinMetricChecks(canary)
+	if !ok {
+		return ok
+	}
+
+	ok = c.runMetricChecks(canary)
+	if !ok {
+		return ok
+	}
+
+	return true
+}
+
+func (c *Controller) runBuiltinMetricChecks(canary *flaggerv1.Canary) bool {
+	// override the global provider if one is specified in the canary spec
+	var metricsProvider string
+	// set the metrics provider to Crossover Prometheus when Crossover is the mesh provider
+	// For example, `crossover` metrics provider should be used for `smi:crossover` mesh provider
+	if strings.Contains(c.meshProvider, "crossover") {
+		metricsProvider = "crossover"
+	} else {
+		metricsProvider = c.meshProvider
+	}
+
+	if canary.Spec.Provider != "" {
+		metricsProvider = canary.Spec.Provider
+
+		// set the metrics provider to Linkerd Prometheus when Linkerd is the default mesh provider
+		if strings.Contains(c.meshProvider, "linkerd") {
+			metricsProvider = "linkerd"
+		}
+	}
+	// set the metrics provider to query Prometheus for the canary Kubernetes service if the canary target is Service
+	if canary.Spec.TargetRef.Kind == "Service" {
+		metricsProvider = metricsProvider + MetricsProviderServiceSuffix
+	}
+
+	// create observer based on the mesh provider
+	observerFactory := c.observerFactory
+
+	// override the global metrics server if one is specified in the canary spec
+	if canary.Spec.MetricsServer != "" {
+		var err error
+		observerFactory, err = observers.NewFactory(canary.Spec.MetricsServer)
+		if err != nil {
+			c.recordEventErrorf(canary, "Error building Prometheus client for %s %v", canary.Spec.MetricsServer, err)
+			return false
+		}
+	}
+	observer := observerFactory.Observer(metricsProvider)
+
+	// run metrics checks
+	for _, metric := range canary.GetAnalysis().Metrics {
+		if metric.Interval == "" {
+			metric.Interval = canary.GetMetricInterval()
+		}
+
+		if metric.Name == "request-success-rate" {
+			val, err := observer.GetRequestSuccessRate(toMetricModel(canary, metric.Interval))
+			if err != nil {
+				if strings.Contains(err.Error(), "no values found") {
+					c.recordEventWarningf(canary, "Halt advancement no values found for %s metric %s probably %s.%s is not receiving traffic",
+						metricsProvider, metric.Name, canary.Spec.TargetRef.Name, canary.Namespace)
+				} else {
+					c.recordEventErrorf(canary, "Prometheus query failed: %v", err)
+				}
+				return false
+			}
+
+			if metric.ThresholdRange != nil {
+				tr := *metric.ThresholdRange
+				if tr.Min != nil && val < *tr.Min {
+					c.recordEventWarningf(canary, "Halt %s.%s advancement success rate %.2f%% < %v%%",
+						canary.Name, canary.Namespace, val, *tr.Min)
+					return false
+				}
+				if tr.Max != nil && val > *tr.Max {
+					c.recordEventWarningf(canary, "Halt %s.%s advancement success rate %.2f%% > %v%%",
+						canary.Name, canary.Namespace, val, *tr.Max)
+					return false
+				}
+			} else if metric.Threshold > val {
+				c.recordEventWarningf(canary, "Halt %s.%s advancement success rate %.2f%% < %v%%",
+					canary.Name, canary.Namespace, val, metric.Threshold)
+				return false
+			}
+		}
+
+		if metric.Name == "request-duration" {
+			val, err := observer.GetRequestDuration(toMetricModel(canary, metric.Interval))
+			if err != nil {
+				if strings.Contains(err.Error(), "no values found") {
+					c.recordEventWarningf(canary, "Halt advancement no values found for %s metric %s probably %s.%s is not receiving traffic",
+						metricsProvider, metric.Name, canary.Spec.TargetRef.Name, canary.Namespace)
+				} else {
+					c.recordEventErrorf(canary, "Prometheus query failed: %v", err)
+				}
+				return false
+			}
+			if metric.ThresholdRange != nil {
+				tr := *metric.ThresholdRange
+				if tr.Min != nil && val < time.Duration(*tr.Min)*time.Millisecond {
+					c.recordEventWarningf(canary, "Halt %s.%s advancement request duration %v < %v",
+						canary.Name, canary.Namespace, val, time.Duration(*tr.Min)*time.Millisecond)
+					return false
+				}
+				if tr.Max != nil && val > time.Duration(*tr.Max)*time.Millisecond {
+					c.recordEventWarningf(canary, "Halt %s.%s advancement request duration %v > %v",
+						canary.Name, canary.Namespace, val, time.Duration(*tr.Max)*time.Millisecond)
+					return false
+				}
+			} else if val > time.Duration(metric.Threshold)*time.Millisecond {
+				c.recordEventWarningf(canary, "Halt %s.%s advancement request duration %v > %v",
+					canary.Name, canary.Namespace, val, time.Duration(metric.Threshold)*time.Millisecond)
+				return false
+			}
+		}
+
+		// in-line PromQL
+		if metric.Query != "" {
+			val, err := observerFactory.Client.RunQuery(metric.Query)
+			if err != nil {
+				if strings.Contains(err.Error(), "no values found") {
+					c.recordEventWarningf(canary, "Halt advancement no values found for metric: %s",
+						metric.Name)
+				} else {
+					c.recordEventErrorf(canary, "Prometheus query failed for %s: %v", metric.Name, err)
+				}
+				return false
+			}
+			if metric.ThresholdRange != nil {
+				tr := *metric.ThresholdRange
+				if tr.Min != nil && val < *tr.Min {
+					c.recordEventWarningf(canary, "Halt %s.%s advancement %s %.2f < %v",
+						canary.Name, canary.Namespace, metric.Name, val, *tr.Min)
+					return false
+				}
+				if tr.Max != nil && val > *tr.Max {
+					c.recordEventWarningf(canary, "Halt %s.%s advancement %s %.2f > %v",
+						canary.Name, canary.Namespace, metric.Name, val, *tr.Max)
+					return false
+				}
+			} else if val > metric.Threshold {
+				c.recordEventWarningf(canary, "Halt %s.%s advancement %s %.2f > %v",
+					canary.Name, canary.Namespace, metric.Name, val, metric.Threshold)
+				return false
+			}
+		}
+	}
+
+	return true
+}
+
+func (c *Controller) runMetricChecks(canary *flaggerv1.Canary) bool {
+	for _, metric := range canary.GetAnalysis().Metrics {
+		if metric.TemplateRef != nil {
+			namespace := canary.Namespace
+			if metric.TemplateRef.Namespace != "" {
+				namespace = metric.TemplateRef.Namespace
+			}
+
+			template, err := c.flaggerInformers.MetricInformer.Lister().MetricTemplates(namespace).Get(metric.TemplateRef.Name)
+			if err != nil {
+				c.recordEventErrorf(canary, "Metric template %s.%s error: %v", metric.TemplateRef.Name, namespace, err)
+				return false
+			}
+
+			var credentials map[string][]byte
+			if template.Spec.Provider.SecretRef != nil {
+				secret, err := c.kubeClient.CoreV1().Secrets(namespace).Get(template.Spec.Provider.SecretRef.Name, metav1.GetOptions{})
+				if err != nil {
+					c.recordEventErrorf(canary, "Metric template %s.%s secret %s error: %v",
+						metric.TemplateRef.Name, namespace, template.Spec.Provider.SecretRef.Name, err)
+					return false
+				}
+				credentials = secret.Data
+			}
+
+			factory := providers.Factory{}
+			provider, err := factory.Provider(metric.Interval, template.Spec.Provider, credentials)
+			if err != nil {
+				c.recordEventErrorf(canary, "Metric template %s.%s provider %s error: %v",
+					metric.TemplateRef.Name, namespace, template.Spec.Provider.Type, err)
+				return false
+			}
+
+			query, err := observers.RenderQuery(template.Spec.Query, toMetricModel(canary, metric.Interval))
+			if err != nil {
+				c.recordEventErrorf(canary, "Metric template %s.%s query render error: %v",
+					metric.TemplateRef.Name, namespace, err)
+				return false
+			}
+
+			val, err := provider.RunQuery(query)
+			if err != nil {
+				if strings.Contains(err.Error(), "no values found") {
+					c.recordEventWarningf(canary, "Halt advancement no values found for custom metric: %s",
+						metric.Name)
+				} else {
+					c.recordEventErrorf(canary, "Metric query failed for %s: %v", metric.Name, err)
+				}
+				return false
+			}
+
+			if metric.ThresholdRange != nil {
+				tr := *metric.ThresholdRange
+				if tr.Min != nil && val < *tr.Min {
+					c.recordEventWarningf(canary, "Halt %s.%s advancement %s %.2f < %v",
+						canary.Name, canary.Namespace, metric.Name, val, *tr.Min)
+					return false
+				}
+				if tr.Max != nil && val > *tr.Max {
+					c.recordEventWarningf(canary, "Halt %s.%s advancement %s %.2f > %v",
+						canary.Name, canary.Namespace, metric.Name, val, *tr.Max)
+					return false
+				}
+			} else if val > metric.Threshold {
+				c.recordEventWarningf(canary, "Halt %s.%s advancement %s %.2f > %v",
+					canary.Name, canary.Namespace, metric.Name, val, metric.Threshold)
+				return false
+			}
+		}
+	}
+
+	return true
+}
+
+func toMetricModel(r *flaggerv1.Canary, interval string) flaggerv1.MetricTemplateModel {
+	service := r.Spec.TargetRef.Name
+	if r.Spec.Service.Name != "" {
+		service = r.Spec.Service.Name
+	}
+	ingress := r.Spec.TargetRef.Name
+	if r.Spec.IngressRef != nil {
+		ingress = r.Spec.IngressRef.Name
+	}
+	return flaggerv1.MetricTemplateModel{
+		Name:      r.Name,
+		Namespace: r.Namespace,
+		Target:    r.Spec.TargetRef.Name,
+		Service:   service,
+		Ingress:   ingress,
+		Interval:  interval,
+	}
+}
+
 func (c *Controller) rollback(canary *flaggerv1.Canary, canaryController canary.Controller, meshRouter router.Interface) {
 	if canary.Status.FailedChecks >= canary.GetAnalysisThreshold() {
 		c.recordEventWarningf(canary, "Rolling back %s.%s failed checks threshold reached %v",
@@ -703,7 +1030,7 @@ func (c *Controller) rollback(canary *flaggerv1.Canary, canaryController canary.
 	c.recorder.SetWeight(canary, primaryWeight, canaryWeight)
 
 	// shutdown canary
-	if err := canaryController.ScaleToZero(canary); err != nil {
+	if err := canaryController.Scale(canary, 0); err != nil {
 		c.recordEventWarningf(canary, "%v", err)
 		return
 	}
diff --git a/pkg/controller/scheduler_hooks.go b/pkg/controller/scheduler_hooks.go
deleted file mode 100644
index 9e72ee37..00000000
--- a/pkg/controller/scheduler_hooks.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package controller
-
-import (
-	"fmt"
-
-	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
-	"github.com/weaveworks/flagger/pkg/canary"
-)
-
-func (c *Controller) runConfirmRolloutHooks(canary *flaggerv1.Canary, canaryController canary.Controller) bool {
-	for _, webhook := range canary.GetAnalysis().Webhooks {
-		if webhook.Type == flaggerv1.ConfirmRolloutHook {
-			err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
-			if err != nil {
-				if canary.Status.Phase != flaggerv1.CanaryPhaseWaiting {
-					if err := canaryController.SetStatusPhase(canary, flaggerv1.CanaryPhaseWaiting); err != nil {
-						c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).Errorf("%v", err)
-					}
-					c.recordEventWarningf(canary, "Halt %s.%s advancement waiting for approval %s",
-						canary.Name, canary.Namespace, webhook.Name)
-					c.alert(canary, "Canary is waiting for approval.", false, flaggerv1.SeverityWarn)
-				}
-				return false
-			} else {
-				if canary.Status.Phase == flaggerv1.CanaryPhaseWaiting {
-					if err := canaryController.SetStatusPhase(canary, flaggerv1.CanaryPhaseProgressing); err != nil {
-						c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).Errorf("%v", err)
-						return false
-					}
-					c.recordEventInfof(canary, "Confirm-rollout check %s passed", webhook.Name)
-					return false
-				}
-			}
-		}
-	}
-	return true
-}
-
-func (c *Controller) runConfirmPromotionHooks(canary *flaggerv1.Canary) bool {
-	for _, webhook := range canary.GetAnalysis().Webhooks {
-		if webhook.Type == flaggerv1.ConfirmPromotionHook {
-			err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
-			if err != nil {
-				c.recordEventWarningf(canary, "Halt %s.%s advancement waiting for promotion approval %s",
-					canary.Name, canary.Namespace, webhook.Name)
-				c.alert(canary, "Canary promotion is waiting for approval.", false, flaggerv1.SeverityWarn)
-				return false
-			} else {
-				c.recordEventInfof(canary, "Confirm-promotion check %s passed", webhook.Name)
-			}
-		}
-	}
-	return true
-}
-
-func (c *Controller) runPreRolloutHooks(canary *flaggerv1.Canary) bool {
-	for _, webhook := range canary.GetAnalysis().Webhooks {
-		if webhook.Type == flaggerv1.PreRolloutHook {
-			err := CallWebhook(canary.Name, canary.Namespace, flaggerv1.CanaryPhaseProgressing, webhook)
-			if err != nil {
-				c.recordEventWarningf(canary, "Halt %s.%s advancement pre-rollout check %s failed %v",
-					canary.Name, canary.Namespace, webhook.Name, err)
-				return false
-			} else {
-				c.recordEventInfof(canary, "Pre-rollout check %s passed", webhook.Name)
-			}
-		}
-	}
-	return true
-}
-
-func (c *Controller) runPostRolloutHooks(canary *flaggerv1.Canary, phase flaggerv1.CanaryPhase) bool {
-	for _, webhook := range canary.GetAnalysis().Webhooks {
-		if webhook.Type == flaggerv1.PostRolloutHook {
-			err := CallWebhook(canary.Name, canary.Namespace, phase, webhook)
-			if err != nil {
-				c.recordEventWarningf(canary, "Post-rollout hook %s failed %v", webhook.Name, err)
-				return false
-			} else {
-				c.recordEventInfof(canary, "Post-rollout check %s passed", webhook.Name)
-			}
-		}
-	}
-	return true
-}
-
-func (c *Controller) runRollbackHooks(canary *flaggerv1.Canary, phase flaggerv1.CanaryPhase) bool {
-	for _, webhook := range canary.GetAnalysis().Webhooks {
-		if webhook.Type == flaggerv1.RollbackHook {
-			err := CallWebhook(canary.Name, canary.Namespace, phase, webhook)
-			if err != nil {
-				c.recordEventInfof(canary, "Rollback hook %s not signaling a rollback", webhook.Name)
-			} else {
-				c.recordEventWarningf(canary, "Rollback check %s passed", webhook.Name)
-				return true
-			}
-		}
-	}
-	return false
-}
diff --git a/pkg/controller/scheduler_metrics.go b/pkg/controller/scheduler_metrics.go
deleted file mode 100644
index 16731f09..00000000
--- a/pkg/controller/scheduler_metrics.go
+++ /dev/null
@@ -1,247 +0,0 @@
-package controller
-
-import (
-	"errors"
-	"strings"
-	"time"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
-	"github.com/weaveworks/flagger/pkg/metrics/observers"
-	"github.com/weaveworks/flagger/pkg/metrics/providers"
-)
-
-func (c *Controller) runBuiltinMetricChecks(canary *flaggerv1.Canary) bool {
-	// override the global provider if one is specified in the canary spec
-	var metricsProvider string
-	// set the metrics provider to Crossover Prometheus when Crossover is the mesh provider
-	// For example, `crossover` metrics provider should be used for `smi:crossover` mesh provider
-	if strings.Contains(c.meshProvider, "crossover") {
-		metricsProvider = "crossover"
-	} else {
-		metricsProvider = c.meshProvider
-	}
-
-	if canary.Spec.Provider != "" {
-		metricsProvider = canary.Spec.Provider
-
-		// set the metrics provider to Linkerd Prometheus when Linkerd is the default mesh provider
-		if strings.Contains(c.meshProvider, "linkerd") {
-			metricsProvider = "linkerd"
-		}
-	}
-	// set the metrics provider to query Prometheus for the canary Kubernetes service if the canary target is Service
-	if canary.Spec.TargetRef.Kind == "Service" {
-		metricsProvider = metricsProvider + MetricsProviderServiceSuffix
-	}
-
-	// create observer based on the mesh provider
-	observerFactory := c.observerFactory
-
-	// override the global metrics server if one is specified in the canary spec
-	if canary.Spec.MetricsServer != "" {
-		var err error
-		observerFactory, err = observers.NewFactory(canary.Spec.MetricsServer)
-		if err != nil {
-			c.recordEventErrorf(canary, "Error building Prometheus client for %s %v", canary.Spec.MetricsServer, err)
-			return false
-		}
-	}
-	observer := observerFactory.Observer(metricsProvider)
-
-	// run metrics checks
-	for _, metric := range canary.GetAnalysis().Metrics {
-		if metric.Interval == "" {
-			metric.Interval = canary.GetMetricInterval()
-		}
-
-		if metric.Name == "request-success-rate" {
-			val, err := observer.GetRequestSuccessRate(toMetricModel(canary, metric.Interval))
-			if err != nil {
-				if errors.Is(err, providers.ErrNoValuesFound) {
-					c.recordEventWarningf(canary,
-						"Halt advancement no values found for %s metric %s probably %s.%s is not receiving traffic: %v",
-						metricsProvider, metric.Name, canary.Spec.TargetRef.Name, canary.Namespace, err)
-				} else {
-					c.recordEventErrorf(canary, "Prometheus query failed: %v", err)
-				}
-				return false
-			}
-
-			if metric.ThresholdRange != nil {
-				tr := *metric.ThresholdRange
-				if tr.Min != nil && val < *tr.Min {
-					c.recordEventWarningf(canary, "Halt %s.%s advancement success rate %.2f%% < %v%%",
-						canary.Name, canary.Namespace, val, *tr.Min)
-					return false
-				}
-				if tr.Max != nil && val > *tr.Max {
-					c.recordEventWarningf(canary, "Halt %s.%s advancement success rate %.2f%% > %v%%",
-						canary.Name, canary.Namespace, val, *tr.Max)
-					return false
-				}
-			} else if metric.Threshold > val {
-				c.recordEventWarningf(canary, "Halt %s.%s advancement success rate %.2f%% < %v%%",
-					canary.Name, canary.Namespace, val, metric.Threshold)
-				return false
-			}
-		}
-
-		if metric.Name == "request-duration" {
-			val, err := observer.GetRequestDuration(toMetricModel(canary, metric.Interval))
-			if err != nil {
-				if errors.Is(err, providers.ErrNoValuesFound) {
-					c.recordEventWarningf(canary, "Halt advancement no values found for %s metric %s probably %s.%s is not receiving traffic",
-						metricsProvider, metric.Name, canary.Spec.TargetRef.Name, canary.Namespace)
-				} else {
-					c.recordEventErrorf(canary, "Prometheus query failed: %v", err)
-				}
-				return false
-			}
-			if metric.ThresholdRange != nil {
-				tr := *metric.ThresholdRange
-				if tr.Min != nil && val < time.Duration(*tr.Min)*time.Millisecond {
-					c.recordEventWarningf(canary, "Halt %s.%s advancement request duration %v < %v",
-						canary.Name, canary.Namespace, val, time.Duration(*tr.Min)*time.Millisecond)
-					return false
-				}
-				if tr.Max != nil && val > time.Duration(*tr.Max)*time.Millisecond {
-					c.recordEventWarningf(canary, "Halt %s.%s advancement request duration %v > %v",
-						canary.Name, canary.Namespace, val, time.Duration(*tr.Max)*time.Millisecond)
-					return false
-				}
-			} else if val > time.Duration(metric.Threshold)*time.Millisecond {
-				c.recordEventWarningf(canary, "Halt %s.%s advancement request duration %v > %v",
-					canary.Name, canary.Namespace, val, time.Duration(metric.Threshold)*time.Millisecond)
-				return false
-			}
-		}
-
-		// in-line PromQL
-		if metric.Query != "" {
-			val, err := observerFactory.Client.RunQuery(metric.Query)
-			if err != nil {
-				if errors.Is(err, providers.ErrNoValuesFound) {
-					c.recordEventWarningf(canary, "Halt advancement no values found for metric: %s",
-						metric.Name)
-				} else {
-					c.recordEventErrorf(canary, "Prometheus query failed for %s: %v", metric.Name, err)
-				}
-				return false
-			}
-			if metric.ThresholdRange != nil {
-				tr := *metric.ThresholdRange
-				if tr.Min != nil && val < *tr.Min {
-					c.recordEventWarningf(canary, "Halt %s.%s advancement %s %.2f < %v",
-						canary.Name, canary.Namespace, metric.Name, val, *tr.Min)
-					return false
-				}
-				if tr.Max != nil && val > *tr.Max {
-					c.recordEventWarningf(canary, "Halt %s.%s advancement %s %.2f > %v",
-						canary.Name, canary.Namespace, metric.Name, val, *tr.Max)
-					return false
-				}
-			} else if val > metric.Threshold {
-				c.recordEventWarningf(canary, "Halt %s.%s advancement %s %.2f > %v",
-					canary.Name, canary.Namespace, metric.Name, val, metric.Threshold)
-				return false
-			}
-		}
-	}
-
-	return true
-}
-
-func (c *Controller) runMetricChecks(canary *flaggerv1.Canary) bool {
-	for _, metric := range canary.GetAnalysis().Metrics {
-		if metric.TemplateRef != nil {
-			namespace := canary.Namespace
-			if metric.TemplateRef.Namespace != "" {
-				namespace = metric.TemplateRef.Namespace
-			}
-
-			template, err := c.flaggerInformers.MetricInformer.Lister().MetricTemplates(namespace).Get(metric.TemplateRef.Name)
-			if err != nil {
-				c.recordEventErrorf(canary, "Metric template %s.%s error: %v", metric.TemplateRef.Name, namespace, err)
-				return false
-			}
-
-			var credentials map[string][]byte
-			if template.Spec.Provider.SecretRef != nil {
-				secret, err := c.kubeClient.CoreV1().Secrets(namespace).Get(template.Spec.Provider.SecretRef.Name, metav1.GetOptions{})
-				if err != nil {
-					c.recordEventErrorf(canary, "Metric template %s.%s secret %s error: %v",
-						metric.TemplateRef.Name, namespace, template.Spec.Provider.SecretRef.Name, err)
-					return false
-				}
-				credentials = secret.Data
-			}
-
-			factory := providers.Factory{}
-			provider, err := factory.Provider(metric.Interval, template.Spec.Provider, credentials)
-			if err != nil {
-				c.recordEventErrorf(canary, "Metric template %s.%s provider %s error: %v",
-					metric.TemplateRef.Name, namespace, template.Spec.Provider.Type, err)
-				return false
-			}
-
-			query, err := observers.RenderQuery(template.Spec.Query, toMetricModel(canary, metric.Interval))
-			if err != nil {
-				c.recordEventErrorf(canary, "Metric template %s.%s query render error: %v",
-					metric.TemplateRef.Name, namespace, err)
-				return false
-			}
-
-			val, err := provider.RunQuery(query)
-			if err != nil {
-				if errors.Is(err, providers.ErrNoValuesFound) {
-					c.recordEventWarningf(canary, "Halt advancement no values found for custom metric: %s: %v",
-						metric.Name, err)
-				} else {
-					c.recordEventErrorf(canary, "Metric query failed for %s: %v", metric.Name, err)
-				}
-				return false
-			}
-
-			if metric.ThresholdRange != nil {
-				tr := *metric.ThresholdRange
-				if tr.Min != nil && val < *tr.Min {
-					c.recordEventWarningf(canary, "Halt %s.%s advancement %s %.2f < %v",
-						canary.Name, canary.Namespace, metric.Name, val, *tr.Min)
-					return false
-				}
-				if tr.Max != nil && val > *tr.Max {
-					c.recordEventWarningf(canary, "Halt %s.%s advancement %s %.2f > %v",
-						canary.Name, canary.Namespace, metric.Name, val, *tr.Max)
-					return false
-				}
-			} else if val > metric.Threshold {
-				c.recordEventWarningf(canary, "Halt %s.%s advancement %s %.2f > %v",
-					canary.Name, canary.Namespace, metric.Name, val, metric.Threshold)
-				return false
-			}
-		}
-	}
-
-	return true
-}
-
-func toMetricModel(r *flaggerv1.Canary, interval string) flaggerv1.MetricTemplateModel {
-	service := r.Spec.TargetRef.Name
-	if r.Spec.Service.Name != "" {
-		service = r.Spec.Service.Name
-	}
-	ingress := r.Spec.TargetRef.Name
-	if r.Spec.IngressRef != nil {
-		ingress = r.Spec.IngressRef.Name
-	}
-	return flaggerv1.MetricTemplateModel{
-		Name:      r.Name,
-		Namespace: r.Namespace,
-		Target:    r.Spec.TargetRef.Name,
-		Service:   service,
-		Ingress:   ingress,
-		Interval:  interval,
-	}
-}
diff --git a/pkg/metrics/observers/appmesh.go b/pkg/metrics/observers/appmesh.go
index b8e672f7..4d4a0362 100644
--- a/pkg/metrics/observers/appmesh.go
+++ b/pkg/metrics/observers/appmesh.go
@@ -1,7 +1,6 @@
 package observers
 
 import (
-	"fmt"
 	"time"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -50,12 +49,12 @@ type AppMeshObserver struct {
 func (ob *AppMeshObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
 	query, err := RenderQuery(appMeshQueries["request-success-rate"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	return value, nil
@@ -64,12 +63,12 @@ func (ob *AppMeshObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateM
 func (ob *AppMeshObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
 	query, err := RenderQuery(appMeshQueries["request-duration"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	ms := time.Duration(int64(value)) * time.Millisecond
diff --git a/pkg/metrics/observers/contour.go b/pkg/metrics/observers/contour.go
index 819b40cb..919797bf 100644
--- a/pkg/metrics/observers/contour.go
+++ b/pkg/metrics/observers/contour.go
@@ -1,7 +1,6 @@
 package observers
 
 import (
-	"fmt"
 	"time"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -49,12 +48,12 @@ type ContourObserver struct {
 func (ob *ContourObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
 	query, err := RenderQuery(contourQueries["request-success-rate"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	return value, nil
@@ -63,12 +62,12 @@ func (ob *ContourObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateM
 func (ob *ContourObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
 	query, err := RenderQuery(contourQueries["request-duration"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	ms := time.Duration(int64(value)) * time.Millisecond
diff --git a/pkg/metrics/observers/crossover.go b/pkg/metrics/observers/crossover.go
index 0ecf3d15..ac985201 100644
--- a/pkg/metrics/observers/crossover.go
+++ b/pkg/metrics/observers/crossover.go
@@ -1,7 +1,6 @@
 package observers
 
 import (
-	"fmt"
 	"time"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -64,12 +63,12 @@ func (ob *CrossoverObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplat
 func (ob *CrossoverObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
 	query, err := RenderQuery(crossoverQueries["request-duration"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	ms := time.Duration(int64(value)) * time.Millisecond
diff --git a/pkg/metrics/observers/crossover_service.go b/pkg/metrics/observers/crossover_service.go
index 0aa27a6a..c8c3565f 100644
--- a/pkg/metrics/observers/crossover_service.go
+++ b/pkg/metrics/observers/crossover_service.go
@@ -1,7 +1,6 @@
 package observers
 
 import (
-	"fmt"
 	"time"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -50,12 +49,12 @@ type CrossoverServiceObserver struct {
 func (ob *CrossoverServiceObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
 	query, err := RenderQuery(crossoverServiceQueries["request-success-rate"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	return value, nil
@@ -64,12 +63,12 @@ func (ob *CrossoverServiceObserver) GetRequestSuccessRate(model flaggerv1.Metric
 func (ob *CrossoverServiceObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
 	query, err := RenderQuery(crossoverServiceQueries["request-duration"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	ms := time.Duration(int64(value)) * time.Millisecond
diff --git a/pkg/metrics/observers/gloo.go b/pkg/metrics/observers/gloo.go
index 90350edb..116787a0 100644
--- a/pkg/metrics/observers/gloo.go
+++ b/pkg/metrics/observers/gloo.go
@@ -1,7 +1,6 @@
 package observers
 
 import (
-	"fmt"
 	"time"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -49,12 +48,12 @@ type GlooObserver struct {
 func (ob *GlooObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
 	query, err := RenderQuery(glooQueries["request-success-rate"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	return value, nil
@@ -63,12 +62,12 @@ func (ob *GlooObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateMode
 func (ob *GlooObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
 	query, err := RenderQuery(glooQueries["request-duration"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	ms := time.Duration(int64(value)) * time.Millisecond
diff --git a/pkg/metrics/observers/http.go b/pkg/metrics/observers/http.go
index 24dbc4b7..632fa72c 100644
--- a/pkg/metrics/observers/http.go
+++ b/pkg/metrics/observers/http.go
@@ -1,7 +1,6 @@
 package observers
 
 import (
-	"fmt"
 	"time"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -64,12 +63,12 @@ func (ob *HttpObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateMode
 func (ob *HttpObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
 	query, err := RenderQuery(httpQueries["request-duration"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	ms := time.Duration(int64(value*1000)) * time.Millisecond
diff --git a/pkg/metrics/observers/istio.go b/pkg/metrics/observers/istio.go
index 26084451..52945ea4 100644
--- a/pkg/metrics/observers/istio.go
+++ b/pkg/metrics/observers/istio.go
@@ -1,7 +1,6 @@
 package observers
 
 import (
-	"fmt"
 	"time"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -53,12 +52,12 @@ type IstioObserver struct {
 func (ob *IstioObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
 	query, err := RenderQuery(istioQueries["request-success-rate"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	return value, nil
@@ -67,12 +66,12 @@ func (ob *IstioObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateMod
 func (ob *IstioObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
 	query, err := RenderQuery(istioQueries["request-duration"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	ms := time.Duration(int64(value*1000)) * time.Millisecond
diff --git a/pkg/metrics/observers/linkerd.go b/pkg/metrics/observers/linkerd.go
index d27ffa6c..5257a7c6 100644
--- a/pkg/metrics/observers/linkerd.go
+++ b/pkg/metrics/observers/linkerd.go
@@ -1,7 +1,6 @@
 package observers
 
 import (
-	"fmt"
 	"time"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -53,12 +52,12 @@ type LinkerdObserver struct {
 func (ob *LinkerdObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
 	query, err := RenderQuery(linkerdQueries["request-success-rate"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	return value, nil
@@ -67,12 +66,12 @@ func (ob *LinkerdObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateM
 func (ob *LinkerdObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
 	query, err := RenderQuery(linkerdQueries["request-duration"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	ms := time.Duration(int64(value)) * time.Millisecond
diff --git a/pkg/metrics/observers/nginx.go b/pkg/metrics/observers/nginx.go
index 41667086..442dc5d1 100644
--- a/pkg/metrics/observers/nginx.go
+++ b/pkg/metrics/observers/nginx.go
@@ -1,7 +1,6 @@
 package observers
 
 import (
-	"fmt"
 	"time"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -57,12 +56,12 @@ type NginxObserver struct {
 func (ob *NginxObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) {
 	query, err := RenderQuery(nginxQueries["request-success-rate"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	return value, nil
@@ -71,12 +70,12 @@ func (ob *NginxObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateMod
 func (ob *NginxObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) {
 	query, err := RenderQuery(nginxQueries["request-duration"], model)
 	if err != nil {
-		return 0, fmt.Errorf("rendering query failed: %w", err)
+		return 0, err
 	}
 
 	value, err := ob.client.RunQuery(query)
 	if err != nil {
-		return 0, fmt.Errorf("running query failed: %w", err)
+		return 0, err
 	}
 
 	ms := time.Duration(int64(value)) * time.Millisecond
diff --git a/pkg/metrics/observers/render.go b/pkg/metrics/observers/render.go
index bcf11b57..ca3aadd4 100644
--- a/pkg/metrics/observers/render.go
+++ b/pkg/metrics/observers/render.go
@@ -3,7 +3,6 @@ package observers
 import (
 	"bufio"
 	"bytes"
-	"fmt"
 	"text/template"
 
 	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
@@ -12,18 +11,19 @@ import (
 func RenderQuery(queryTemplate string, model flaggerv1.MetricTemplateModel) (string, error) {
 	t, err := template.New("tmpl").Funcs(model.TemplateFunctions()).Parse(queryTemplate)
 	if err != nil {
-		return "", fmt.Errorf("template parsing failed: %w", err)
+		return "", err
 	}
 	var data bytes.Buffer
 	b := bufio.NewWriter(&data)
 
 	if err := t.Execute(b, nil); err != nil {
-		return "", fmt.Errorf("template excution failed: %w", err)
+		return "", err
 	}
 
 	err = b.Flush()
 	if err != nil {
-		return "", fmt.Errorf("buffer flush failed: %w", err)
+		return "", err
 	}
+
 	return data.String(), nil
 }
diff --git a/pkg/metrics/providers/cloudwatch.go b/pkg/metrics/providers/cloudwatch.go
index dc709641..700bef3f 100644
--- a/pkg/metrics/providers/cloudwatch.go
+++ b/pkg/metrics/providers/cloudwatch.go
@@ -77,12 +77,12 @@ func (p *CloudWatchProvider) RunQuery(query string) (float64, error) {
 
 	mr := res.MetricDataResults
 	if len(mr) < 1 {
-		return 0, fmt.Errorf("invalid response: %s: %w", res.String(), ErrNoValuesFound)
+		return 0, fmt.Errorf("no values found in response: %s", res.String())
 	}
 
-	vs := mr[0].Values
+	vs := res.MetricDataResults[0].Values
 	if len(vs) < 1 {
-		return 0, fmt.Errorf("invalid reponse %s: %w", res.String(), ErrNoValuesFound)
+		return 0, fmt.Errorf("no values found in response: %s", res.String())
 	}
 
 	return aws.Float64Value(vs[0]), nil
diff --git a/pkg/metrics/providers/datadog.go b/pkg/metrics/providers/datadog.go
index c71423ca..a27763fd 100644
--- a/pkg/metrics/providers/datadog.go
+++ b/pkg/metrics/providers/datadog.go
@@ -76,7 +76,7 @@ func NewDatadogProvider(metricInterval string,
 
 	md, err := time.ParseDuration(metricInterval)
 	if err != nil {
-		return nil, fmt.Errorf("error parsing metric interval: %w", err)
+		return nil, fmt.Errorf("error parsing metric interval: %s", err.Error())
 	}
 
 	dd.fromDelta = int64(datadogFromDeltaMultiplierOnMetricInterval * md.Seconds())
@@ -89,7 +89,7 @@ func (p *DatadogProvider) RunQuery(query string) (float64, error) {
 
 	req, err := http.NewRequest("GET", p.metricsQueryEndpoint, nil)
 	if err != nil {
-		return 0, fmt.Errorf("error http.NewRequest: %w", err)
+		return 0, fmt.Errorf("error http.NewRequest: %s", err.Error())
 	}
 
 	req.Header.Set(datadogAPIKeyHeaderKey, p.apiKey)
@@ -105,36 +105,32 @@ func (p *DatadogProvider) RunQuery(query string) (float64, error) {
 	defer cancel()
 	r, err := http.DefaultClient.Do(req.WithContext(ctx))
 	if err != nil {
-		return 0, fmt.Errorf("request failed: %w", err)
+		return 0, err
 	}
 
 	defer r.Body.Close()
 	b, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return 0, fmt.Errorf("error reading body: %w", err)
+		return 0, fmt.Errorf("error reading body: %s", err.Error())
 	}
 
 	if r.StatusCode != http.StatusOK {
-		return 0, fmt.Errorf("error response: %s: %w", string(b), err)
+		return 0, fmt.Errorf("error response: %s", string(b))
 	}
 
 	var res datadogResponse
 	if err := json.Unmarshal(b, &res); err != nil {
-		return 0, fmt.Errorf("error unmarshaling result: %w, '%s'", err, string(b))
+		return 0, fmt.Errorf("error unmarshaling result: %s, '%s'", err.Error(), string(b))
 	}
 
 	if len(res.Series) < 1 {
-		return 0, fmt.Errorf("invalid response: %s: %w", string(b), ErrNoValuesFound)
+		return 0, fmt.Errorf("no values found in response: %s", string(b))
 	}
 
-	pl := res.Series[0].Pointlist
-	if len(pl) < 1 {
-		return 0, fmt.Errorf("invalid response: %s: %w", string(b), ErrNoValuesFound)
-	}
-
-	vs := pl[len(pl)-1]
+	s := res.Series[0]
+	vs := s.Pointlist[len(s.Pointlist)-1]
 	if len(vs) < 1 {
-		return 0, fmt.Errorf("invalid response: %s: %w", string(b), ErrNoValuesFound)
+		return 0, fmt.Errorf("no values found in response: %s", string(b))
 	}
 
 	return vs[1], nil
@@ -145,7 +141,7 @@ func (p *DatadogProvider) RunQuery(query string) (float64, error) {
 func (p *DatadogProvider) IsOnline() (bool, error) {
 	req, err := http.NewRequest("GET", p.apiKeyValidationEndpoint, nil)
 	if err != nil {
-		return false, fmt.Errorf("error http.NewRequest: %w", err)
+		return false, fmt.Errorf("error http.NewRequest: %s", err.Error())
 	}
 
 	req.Header.Add(datadogAPIKeyHeaderKey, p.apiKey)
@@ -155,14 +151,13 @@ func (p *DatadogProvider) IsOnline() (bool, error) {
 	defer cancel()
 	r, err := http.DefaultClient.Do(req.WithContext(ctx))
 	if err != nil {
-		return false, fmt.Errorf("request failed: %w", err)
+		return false, err
 	}
-
 	defer r.Body.Close()
 
 	b, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return false, fmt.Errorf("error reading body: %w", err)
+		return false, fmt.Errorf("error reading body: %s", err.Error())
 	}
 
 	if r.StatusCode != http.StatusOK {
diff --git a/pkg/metrics/providers/errors.go b/pkg/metrics/providers/errors.go
deleted file mode 100644
index 90a6f01f..00000000
--- a/pkg/metrics/providers/errors.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package providers
-
-import "errors"
-
-var (
-	ErrNoValuesFound = errors.New("no values found")
-)
diff --git a/pkg/metrics/providers/prometheus.go b/pkg/metrics/providers/prometheus.go
index 146022bb..bd781529 100644
--- a/pkg/metrics/providers/prometheus.go
+++ b/pkg/metrics/providers/prometheus.go
@@ -74,7 +74,7 @@ func (p *PrometheusProvider) RunQuery(query string) (float64, error) {
 	query = url.QueryEscape(p.trimQuery(query))
 	u, err := url.Parse(fmt.Sprintf("./api/v1/query?query=%s", query))
 	if err != nil {
-		return 0, fmt.Errorf("url.Parase failed: %w", err)
+		return 0, err
 	}
 	u.Path = path.Join(p.url.Path, u.Path)
 
@@ -82,7 +82,7 @@ func (p *PrometheusProvider) RunQuery(query string) (float64, error) {
 
 	req, err := http.NewRequest("GET", u.String(), nil)
 	if err != nil {
-		return 0, fmt.Errorf("http.NewRequest failed: %w", err)
+		return 0, err
 	}
 
 	if p.username != "" && p.password != "" {
@@ -94,13 +94,13 @@ func (p *PrometheusProvider) RunQuery(query string) (float64, error) {
 
 	r, err := http.DefaultClient.Do(req.WithContext(ctx))
 	if err != nil {
-		return 0, fmt.Errorf("request failed: %w", err)
+		return 0, err
 	}
 	defer r.Body.Close()
 
 	b, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return 0, fmt.Errorf("error reading body: %w", err)
+		return 0, fmt.Errorf("error reading body: %s", err.Error())
 	}
 
 	if 400 <= r.StatusCode {
@@ -110,7 +110,7 @@ func (p *PrometheusProvider) RunQuery(query string) (float64, error) {
 	var result prometheusResponse
 	err = json.Unmarshal(b, &result)
 	if err != nil {
-		return 0, fmt.Errorf("error unmarshaling result: %w, '%s'", err, string(b))
+		return 0, fmt.Errorf("error unmarshaling result: %s, '%s'", err.Error(), string(b))
 	}
 
 	var value *float64
@@ -126,7 +126,7 @@ func (p *PrometheusProvider) RunQuery(query string) (float64, error) {
 		}
 	}
 	if value == nil {
-		return 0, fmt.Errorf("%w", ErrNoValuesFound)
+		return 0, fmt.Errorf("no values found")
 	}
 
 	return *value, nil
@@ -136,7 +136,7 @@ func (p *PrometheusProvider) RunQuery(query string) (float64, error) {
 func (p *PrometheusProvider) IsOnline() (bool, error) {
 	u, err := url.Parse("./api/v1/status/flags")
 	if err != nil {
-		return false, fmt.Errorf("url.Parse failed: %w", err)
+		return false, err
 	}
 	u.Path = path.Join(p.url.Path, u.Path)
 
@@ -144,7 +144,7 @@ func (p *PrometheusProvider) IsOnline() (bool, error) {
 
 	req, err := http.NewRequest("GET", u.String(), nil)
 	if err != nil {
-		return false, fmt.Errorf("http.NewRequest failed: %w", err)
+		return false, err
 	}
 
 	if p.username != "" && p.password != "" {
@@ -156,13 +156,13 @@ func (p *PrometheusProvider) IsOnline() (bool, error) {
 
 	r, err := http.DefaultClient.Do(req.WithContext(ctx))
 	if err != nil {
-		return false, fmt.Errorf("request failed: %w", err)
+		return false, err
 	}
 	defer r.Body.Close()
 
 	b, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return false, fmt.Errorf("error reading body: %w", err)
+		return false, fmt.Errorf("error reading body: %s", err.Error())
 	}
 
 	if 400 <= r.StatusCode {
diff --git a/pkg/notifier/client.go b/pkg/notifier/client.go
index b966368d..72d0b1bd 100644
--- a/pkg/notifier/client.go
+++ b/pkg/notifier/client.go
@@ -13,14 +13,14 @@ import (
 func postMessage(address string, payload interface{}) error {
 	data, err := json.Marshal(payload)
 	if err != nil {
-		return fmt.Errorf("marshalling notification payload failed: %w", err)
+		return fmt.Errorf("marshalling notification payload failed %v", err)
 	}
 
 	b := bytes.NewBuffer(data)
 
 	req, err := http.NewRequest("POST", address, b)
 	if err != nil {
-		return fmt.Errorf("http.NewRequest failed: %w", err)
+		return err
 	}
 	req.Header.Set("Content-type", "application/json")
 
@@ -29,14 +29,14 @@ func postMessage(address string, payload interface{}) error {
 
 	res, err := http.DefaultClient.Do(req.WithContext(ctx))
 	if err != nil {
-		return fmt.Errorf("sending notification failed: %w", err)
+		return fmt.Errorf("sending notification failed %v", err)
 	}
 
 	defer res.Body.Close()
 	statusCode := res.StatusCode
 	if statusCode != 200 {
 		body, _ := ioutil.ReadAll(res.Body)
-		return fmt.Errorf("sending notification failed: %s", string(body))
+		return fmt.Errorf("sending notification failed %v", string(body))
 	}
 
 	return nil
diff --git a/pkg/notifier/discord.go b/pkg/notifier/discord.go
index 61e45346..952f45e9 100644
--- a/pkg/notifier/discord.go
+++ b/pkg/notifier/discord.go
@@ -74,7 +74,7 @@ func (s *Discord) Post(workload string, namespace string, message string, fields
 
 	err := postMessage(s.URL, payload)
 	if err != nil {
-		return fmt.Errorf("postMessage failed: %w", err)
+		return err
 	}
 
 	return nil
diff --git a/pkg/notifier/factory.go b/pkg/notifier/factory.go
index 3aad5806..deabe4b8 100644
--- a/pkg/notifier/factory.go
+++ b/pkg/notifier/factory.go
@@ -1,8 +1,6 @@
 package notifier
 
-import (
-	"fmt"
-)
+import "fmt"
 
 type Factory struct {
 	URL      string
@@ -10,36 +8,25 @@ type Factory struct {
 	Channel  string
 }
 
-func NewFactory(url string, username string, channel string) *Factory {
+func NewFactory(URL string, username string, channel string) *Factory {
 	return &Factory{
-		URL:      url,
+		URL:      URL,
 		Channel:  channel,
 		Username: username,
 	}
 }
 
 func (f Factory) Notifier(provider string) (Interface, error) {
-	if f.URL == "" {
-		return &NopNotifier{}, nil
+	switch {
+	case provider == "slack":
+		return NewSlack(f.URL, f.Username, f.Channel)
+	case provider == "discord":
+		return NewDiscord(f.URL, f.Username, f.Channel)
+	case provider == "rocket":
+		return NewRocket(f.URL, f.Username, f.Channel)
+	case provider == "msteams":
+		return NewMSTeams(f.URL)
 	}
 
-	var n Interface
-	var err error
-	switch provider {
-	case "slack":
-		n, err = NewSlack(f.URL, f.Username, f.Channel)
-	case "discord":
-		n, err = NewDiscord(f.URL, f.Username, f.Channel)
-	case "rocket":
-		n, err = NewRocket(f.URL, f.Username, f.Channel)
-	case "msteams":
-		n, err = NewMSTeams(f.URL)
-	default:
-		err = fmt.Errorf("provider %s not supported", provider)
-	}
-
-	if err != nil {
-		n = &NopNotifier{}
-	}
-	return n, err
+	return nil, fmt.Errorf("provider %s not supported", provider)
 }
diff --git a/pkg/notifier/nop.go b/pkg/notifier/nop.go
deleted file mode 100644
index b8eced3b..00000000
--- a/pkg/notifier/nop.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package notifier
-
-type NopNotifier struct{}
-
-func (n *NopNotifier) Post(string, string, string, []Field, string) error {
-	return nil
-}
diff --git a/pkg/notifier/rocket.go b/pkg/notifier/rocket.go
index d15fef89..4bdf549f 100644
--- a/pkg/notifier/rocket.go
+++ b/pkg/notifier/rocket.go
@@ -65,7 +65,8 @@ func (s *Rocket) Post(workload string, namespace string, message string, fields
 
 	err := postMessage(s.URL, payload)
 	if err != nil {
-		return fmt.Errorf("postMessage failed: %w", err)
+		return err
 	}
+
 	return nil
 }
diff --git a/pkg/notifier/slack.go b/pkg/notifier/slack.go
index 88e4fb5e..52084e3e 100644
--- a/pkg/notifier/slack.go
+++ b/pkg/notifier/slack.go
@@ -90,7 +90,8 @@ func (s *Slack) Post(workload string, namespace string, message string, fields [
 
 	err := postMessage(s.URL, payload)
 	if err != nil {
-		return fmt.Errorf("postMessage failed: %w", err)
+		return err
 	}
+
 	return nil
 }
diff --git a/pkg/notifier/teams.go b/pkg/notifier/teams.go
index c08381ca..63e6ade3 100644
--- a/pkg/notifier/teams.go
+++ b/pkg/notifier/teams.go
@@ -70,7 +70,7 @@ func (s *MSTeams) Post(workload string, namespace string, message string, fields
 
 	err := postMessage(s.URL, payload)
 	if err != nil {
-		return fmt.Errorf("postMessage failed: %w", err)
+		return err
 	}
 
 	return nil
diff --git a/pkg/router/appmesh.go b/pkg/router/appmesh.go
index 83a32cf3..2f096417 100644
--- a/pkg/router/appmesh.go
+++ b/pkg/router/appmesh.go
@@ -42,35 +42,35 @@ func (ar *AppMeshRouter) Reconcile(canary *flaggerv1.Canary) error {
 	// DNS app.namespace
 	err := ar.reconcileVirtualNode(canary, apexName, primaryHost)
 	if err != nil {
-		return fmt.Errorf("reconcileVirtualNode failed: %w", err)
+		return err
 	}
 
 	// sync virtual node e.g. app-primary-namespace
 	// DNS app-primary.namespace
 	err = ar.reconcileVirtualNode(canary, primaryName, primaryHost)
 	if err != nil {
-		return fmt.Errorf("reconcileVirtualNode failed: %w", err)
+		return err
 	}
 
 	// sync virtual node e.g. app-canary-namespace
 	// DNS app-canary.namespace
 	err = ar.reconcileVirtualNode(canary, canaryName, canaryHost)
 	if err != nil {
-		return fmt.Errorf("reconcileVirtualNode failed: %w", err)
+		return err
 	}
 
 	// sync main virtual service
 	// DNS app.namespace
 	err = ar.reconcileVirtualService(canary, targetHost, 0)
 	if err != nil {
-		return fmt.Errorf("reconcileVirtualService failed: %w", err)
+		return err
 	}
 
 	// sync canary virtual service
 	// DNS app-canary.namespace
 	err = ar.reconcileVirtualService(canary, fmt.Sprintf("%s.%s", canaryName, canary.Namespace), 100)
 	if err != nil {
-		return fmt.Errorf("reconcileVirtualService failed: %w", err)
+		return err
 	}
 
 	return nil
@@ -97,15 +97,15 @@ func (ar *AppMeshRouter) reconcileVirtualNode(canary *flaggerv1.Canary, name str
 		},
 	}
 
-	backends := make([]appmeshv1.Backend, len(canary.Spec.Service.Backends))
-	for i, b := range canary.Spec.Service.Backends {
-		backends[i] = appmeshv1.Backend{
+	backends := []appmeshv1.Backend{}
+	for _, b := range canary.Spec.Service.Backends {
+		backend := appmeshv1.Backend{
 			VirtualService: appmeshv1.VirtualServiceBackend{
 				VirtualServiceName: b,
 			},
 		}
+		backends = append(backends, backend)
 	}
-
 	if len(backends) > 0 {
 		vnSpec.Backends = backends
 	}
@@ -130,13 +130,15 @@ func (ar *AppMeshRouter) reconcileVirtualNode(canary *flaggerv1.Canary, name str
 		}
 		_, err = ar.appmeshClient.AppmeshV1beta1().VirtualNodes(canary.Namespace).Create(virtualnode)
 		if err != nil {
-			return fmt.Errorf("VirtualNode %s.%s create error %w", name, canary.Namespace, err)
+			return fmt.Errorf("VirtualNode %s.%s create error %v", name, canary.Namespace, err)
 		}
 		ar.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 			Infof("VirtualNode %s.%s created", virtualnode.GetName(), canary.Namespace)
 		return nil
-	} else if err != nil {
-		return fmt.Errorf("VirtualNode %s get query error %w", name, err)
+	}
+
+	if err != nil {
+		return fmt.Errorf("VirtualNode %s query error %v", name, err)
 	}
 
 	// update virtual node
@@ -146,7 +148,7 @@ func (ar *AppMeshRouter) reconcileVirtualNode(canary *flaggerv1.Canary, name str
 			vnClone.Spec = vnSpec
 			_, err = ar.appmeshClient.AppmeshV1beta1().VirtualNodes(canary.Namespace).Update(vnClone)
 			if err != nil {
-				return fmt.Errorf("VirtualNode %s update error %w", name, err)
+				return fmt.Errorf("VirtualNode %s update error %v", name, err)
 			}
 			ar.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 				Infof("VirtualNode %s updated", virtualnode.GetName())
@@ -292,13 +294,15 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
 
 		_, err = ar.appmeshClient.AppmeshV1beta1().VirtualServices(canary.Namespace).Create(virtualService)
 		if err != nil {
-			return fmt.Errorf("VirtualService %s create error %w", name, err)
+			return fmt.Errorf("VirtualService %s create error %v", name, err)
 		}
 		ar.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 			Infof("VirtualService %s created", virtualService.GetName())
 		return nil
-	} else if err != nil {
-		return fmt.Errorf("VirtualService %s get query error: %w", name, err)
+	}
+
+	if err != nil {
+		return fmt.Errorf("VirtualService %s query error %v", name, err)
 	}
 
 	// update virtual service but keep the original target weights
@@ -318,7 +322,7 @@ func (ar *AppMeshRouter) reconcileVirtualService(canary *flaggerv1.Canary, name
 
 			_, err = ar.appmeshClient.AppmeshV1beta1().VirtualServices(canary.Namespace).Update(vsClone)
 			if err != nil {
-				return fmt.Errorf("VirtualService %s update error: %w", name, err)
+				return fmt.Errorf("VirtualService %s update error %v", name, err)
 			}
 			ar.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 				Infof("VirtualService %s updated", virtualService.GetName())
@@ -339,7 +343,11 @@ func (ar *AppMeshRouter) GetRoutes(canary *flaggerv1.Canary) (
 	vsName := fmt.Sprintf("%s.%s", apexName, canary.Namespace)
 	vs, err := ar.appmeshClient.AppmeshV1beta1().VirtualServices(canary.Namespace).Get(vsName, metav1.GetOptions{})
 	if err != nil {
-		err = fmt.Errorf("VirtualService %s get query error: %w", vsName, err)
+		if errors.IsNotFound(err) {
+			err = fmt.Errorf("VirtualService %s not found", vsName)
+			return
+		}
+		err = fmt.Errorf("VirtualService %s query error %v", vsName, err)
 		return
 	}
 
@@ -373,13 +381,16 @@ func (ar *AppMeshRouter) SetRoutes(
 	canary *flaggerv1.Canary,
 	primaryWeight int,
 	canaryWeight int,
-	_ bool,
+	mirrored bool,
 ) error {
 	apexName, _, _ := canary.GetServiceNames()
 	vsName := fmt.Sprintf("%s.%s", apexName, canary.Namespace)
 	vs, err := ar.appmeshClient.AppmeshV1beta1().VirtualServices(canary.Namespace).Get(vsName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("VirtualService %s get query error: %w", vsName, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("VirtualService %s not found", vsName)
+		}
+		return fmt.Errorf("VirtualService %s query error %v", vsName, err)
 	}
 
 	vsClone := vs.DeepCopy()
@@ -398,8 +409,9 @@ func (ar *AppMeshRouter) SetRoutes(
 
 	_, err = ar.appmeshClient.AppmeshV1beta1().VirtualServices(canary.Namespace).Update(vsClone)
 	if err != nil {
-		return fmt.Errorf("VirtualService %s update error: %w", vsName, err)
+		return fmt.Errorf("VirtualService %s update error %v", vsName, err)
 	}
+
 	return nil
 }
 
@@ -437,8 +449,8 @@ func makeRetryPolicy(canary *flaggerv1.Canary) *appmeshv1.HttpRetryPolicy {
 
 // makeRetryPolicy creates an App Mesh HttpRouteHeader from the Canary.CanaryAnalysis.Match
 func (ar *AppMeshRouter) makeHeaders(canary *flaggerv1.Canary) []appmeshv1.HttpRouteHeader {
+	headers := []appmeshv1.HttpRouteHeader{}
 
-	var headers []appmeshv1.HttpRouteHeader
 	for _, m := range canary.GetAnalysis().Match {
 		for key, value := range m.Headers {
 			header := appmeshv1.HttpRouteHeader{
diff --git a/pkg/router/contour.go b/pkg/router/contour.go
index dfd7dd11..8ad846ab 100644
--- a/pkg/router/contour.go
+++ b/pkg/router/contour.go
@@ -152,13 +152,15 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
 
 		_, err = cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Create(proxy)
 		if err != nil {
-			return fmt.Errorf("HTTPProxy %s.%s create error: %w", apexName, canary.Namespace, err)
+			return fmt.Errorf("HTTPProxy %s.%s create error %v", apexName, canary.Namespace, err)
 		}
 		cr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 			Infof("HTTPProxy %s.%s created", proxy.GetName(), canary.Namespace)
 		return nil
-	} else if err != nil {
-		return fmt.Errorf("HTTPProxy %s.%s get query error: %w", apexName, canary.Namespace, err)
+	}
+
+	if err != nil {
+		return fmt.Errorf("HTTPProxy %s.%s query error %v", apexName, canary.Namespace, err)
 	}
 
 	// update HTTPProxy but keep the original destination weights
@@ -173,7 +175,7 @@ func (cr *ContourRouter) Reconcile(canary *flaggerv1.Canary) error {
 
 			_, err = cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Update(clone)
 			if err != nil {
-				return fmt.Errorf("HTTPProxy %s.%s update error: %w", apexName, canary.Namespace, err)
+				return fmt.Errorf("HTTPProxy %s.%s update error %v", apexName, canary.Namespace, err)
 			}
 			cr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 				Infof("HTTPProxy %s.%s updated", proxy.GetName(), canary.Namespace)
@@ -194,7 +196,11 @@ func (cr *ContourRouter) GetRoutes(canary *flaggerv1.Canary) (
 
 	proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(apexName, metav1.GetOptions{})
 	if err != nil {
-		err = fmt.Errorf("HTTPProxy %s.%s get query error %w", apexName, canary.Namespace, err)
+		if errors.IsNotFound(err) {
+			err = fmt.Errorf("HTTPProxy %s.%s not found", apexName, canary.Namespace)
+			return
+		}
+		err = fmt.Errorf("HTTPProxy %s.%s query error %v", apexName, canary.Namespace, err)
 		return
 	}
 
@@ -219,7 +225,7 @@ func (cr *ContourRouter) SetRoutes(
 	canary *flaggerv1.Canary,
 	primaryWeight int,
 	canaryWeight int,
-	_ bool,
+	mirrored bool,
 ) error {
 	apexName, primaryName, canaryName := canary.GetServiceNames()
 
@@ -229,7 +235,11 @@ func (cr *ContourRouter) SetRoutes(
 
 	proxy, err := cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Get(apexName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("HTTPProxy %s.%s query error: %w", apexName, canary.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("HTTPProxy %s.%s not found", apexName, canary.Namespace)
+
+		}
+		return fmt.Errorf("HTTPProxy %s.%s query error %v", apexName, canary.Namespace, err)
 	}
 
 	proxy.Spec = contourv1.HTTPProxySpec{
@@ -334,7 +344,7 @@ func (cr *ContourRouter) SetRoutes(
 
 	_, err = cr.contourClient.ProjectcontourV1().HTTPProxies(canary.Namespace).Update(proxy)
 	if err != nil {
-		return fmt.Errorf("HTTPProxy %s.%s update error: %w", apexName, canary.Namespace, err)
+		return fmt.Errorf("HTTPProxy %s.%s update error %v", apexName, canary.Namespace, err)
 	}
 	return nil
 }
diff --git a/pkg/router/factory.go b/pkg/router/factory.go
index 4356fd74..8d2df93e 100644
--- a/pkg/router/factory.go
+++ b/pkg/router/factory.go
@@ -34,20 +34,25 @@ func NewFactory(kubeConfig *restclient.Config, kubeClient kubernetes.Interface,
 	}
 }
 
-// KubernetesRouter returns a KubernetesRouter interface implementation
+// KubernetesDeploymentRouter returns a ClusterIP service router
 func (factory *Factory) KubernetesRouter(kind string, labelSelector string, annotations map[string]string, ports map[string]int32) KubernetesRouter {
-	switch kind {
-	case "Service":
-		return &KubernetesNoopRouter{}
-	default: // Daemonset or Deployment
-		return &KubernetesDefaultRouter{
-			logger:        factory.logger,
-			flaggerClient: factory.flaggerClient,
-			kubeClient:    factory.kubeClient,
-			labelSelector: labelSelector,
-			annotations:   annotations,
-			ports:         ports,
-		}
+	deploymentRouter := &KubernetesDeploymentRouter{
+		logger:        factory.logger,
+		flaggerClient: factory.flaggerClient,
+		kubeClient:    factory.kubeClient,
+		labelSelector: labelSelector,
+		annotations:   annotations,
+		ports:         ports,
+	}
+	noopRouter := &KubernetesNoopRouter{}
+
+	switch {
+	case kind == "Deployment":
+		return deploymentRouter
+	case kind == "Service":
+		return noopRouter
+	default:
+		return deploymentRouter
 	}
 }
 
diff --git a/pkg/router/gloo.go b/pkg/router/gloo.go
index c2401e04..8648a7e3 100644
--- a/pkg/router/gloo.go
+++ b/pkg/router/gloo.go
@@ -73,13 +73,15 @@ func (gr *GlooRouter) Reconcile(canary *flaggerv1.Canary) error {
 
 		_, err = gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Create(upstreamGroup)
 		if err != nil {
-			return fmt.Errorf("UpstreamGroup %s.%s create error: %w", apexName, canary.Namespace, err)
+			return fmt.Errorf("UpstreamGroup %s.%s create error %v", apexName, canary.Namespace, err)
 		}
 		gr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 			Infof("UpstreamGroup %s.%s created", upstreamGroup.GetName(), canary.Namespace)
 		return nil
-	} else if err != nil {
-		return fmt.Errorf("UpstreamGroup %s.%s get query error: %w", apexName, canary.Namespace, err)
+	}
+
+	if err != nil {
+		return fmt.Errorf("UpstreamGroup %s.%s query error %v", apexName, canary.Namespace, err)
 	}
 
 	// update upstreamGroup but keep the original destination weights
@@ -94,7 +96,7 @@ func (gr *GlooRouter) Reconcile(canary *flaggerv1.Canary) error {
 
 			_, err = gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Update(clone)
 			if err != nil {
-				return fmt.Errorf("UpstreamGroup %s.%s update error: %w", apexName, canary.Namespace, err)
+				return fmt.Errorf("UpstreamGroup %s.%s update error %v", apexName, canary.Namespace, err)
 			}
 			gr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 				Infof("UpstreamGroup %s.%s updated", upstreamGroup.GetName(), canary.Namespace)
@@ -116,7 +118,11 @@ func (gr *GlooRouter) GetRoutes(canary *flaggerv1.Canary) (
 
 	upstreamGroup, err := gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Get(apexName, metav1.GetOptions{})
 	if err != nil {
-		err = fmt.Errorf("UpstreamGroup %s.%s get query error: %w", apexName, canary.Namespace, err)
+		if errors.IsNotFound(err) {
+			err = fmt.Errorf("UpstreamGroup %s.%s not found", apexName, canary.Namespace)
+			return
+		}
+		err = fmt.Errorf("UpstreamGroup %s.%s query error %v", apexName, canary.Namespace, err)
 		return
 	}
 
@@ -141,7 +147,7 @@ func (gr *GlooRouter) SetRoutes(
 	canary *flaggerv1.Canary,
 	primaryWeight int,
 	canaryWeight int,
-	_ bool,
+	mirrored bool,
 ) error {
 	apexName, _, _ := canary.GetServiceNames()
 	canaryName := fmt.Sprintf("%s-%s-canary-%v", canary.Namespace, apexName, canary.Spec.Service.Port)
@@ -153,7 +159,11 @@ func (gr *GlooRouter) SetRoutes(
 
 	upstreamGroup, err := gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Get(apexName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("UpstreamGroup %s.%s query error: %w", apexName, canary.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("UpstreamGroup %s.%s not found", apexName, canary.Namespace)
+
+		}
+		return fmt.Errorf("UpstreamGroup %s.%s query error %v", apexName, canary.Namespace, err)
 	}
 
 	upstreamGroup.Spec = gloov1.UpstreamGroupSpec{
@@ -181,7 +191,7 @@ func (gr *GlooRouter) SetRoutes(
 
 	_, err = gr.glooClient.GlooV1().UpstreamGroups(canary.Namespace).Update(upstreamGroup)
 	if err != nil {
-		return fmt.Errorf("UpstreamGroup %s.%s update error: %w", apexName, canary.Namespace, err)
+		return fmt.Errorf("UpstreamGroup %s.%s update error %v", apexName, canary.Namespace, err)
 	}
 	return nil
 }
diff --git a/pkg/router/ingress.go b/pkg/router/ingress.go
index 70682aac..e97d68c4 100644
--- a/pkg/router/ingress.go
+++ b/pkg/router/ingress.go
@@ -33,7 +33,7 @@ func (i *IngressRouter) Reconcile(canary *flaggerv1.Canary) error {
 
 	ingress, err := i.kubeClient.ExtensionsV1beta1().Ingresses(canary.Namespace).Get(canary.Spec.IngressRef.Name, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("ingress %s.%s get query error: %w", canary.Spec.IngressRef.Name, canary.Namespace, err)
+		return err
 	}
 
 	ingressClone := ingress.DeepCopy()
@@ -76,14 +76,16 @@ func (i *IngressRouter) Reconcile(canary *flaggerv1.Canary) error {
 
 		_, err := i.kubeClient.ExtensionsV1beta1().Ingresses(canary.Namespace).Create(ing)
 		if err != nil {
-			return fmt.Errorf("ingress %s.%s create error: %w", ing.Name, ing.Namespace, err)
+			return err
 		}
 
 		i.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 			Infof("Ingress %s.%s created", ing.GetName(), canary.Namespace)
 		return nil
-	} else if err != nil {
-		return fmt.Errorf("ingress %s.%s query error: %w", canaryIngressName, canary.Namespace, err)
+	}
+
+	if err != nil {
+		return fmt.Errorf("ingress %s query error %v", canaryIngressName, err)
 	}
 
 	if diff := cmp.Diff(ingressClone.Spec, canaryIngress.Spec); diff != "" {
@@ -92,7 +94,7 @@ func (i *IngressRouter) Reconcile(canary *flaggerv1.Canary) error {
 
 		_, err := i.kubeClient.ExtensionsV1beta1().Ingresses(canary.Namespace).Update(iClone)
 		if err != nil {
-			return fmt.Errorf("ingress %s.%s update error: %w", canaryIngressName, iClone.Namespace, err)
+			return fmt.Errorf("ingress %s update error %v", canaryIngressName, err)
 		}
 
 		i.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
@@ -111,8 +113,7 @@ func (i *IngressRouter) GetRoutes(canary *flaggerv1.Canary) (
 	canaryIngressName := fmt.Sprintf("%s-canary", canary.Spec.IngressRef.Name)
 	canaryIngress, err := i.kubeClient.ExtensionsV1beta1().Ingresses(canary.Namespace).Get(canaryIngressName, metav1.GetOptions{})
 	if err != nil {
-		err = fmt.Errorf("ingress %s.%s get query error: %w", canaryIngressName, canary.Namespace, err)
-		return
+		return 0, 0, false, err
 	}
 
 	// A/B testing
@@ -127,10 +128,9 @@ func (i *IngressRouter) GetRoutes(canary *flaggerv1.Canary) (
 	// Canary
 	for k, v := range canaryIngress.Annotations {
 		if k == i.GetAnnotationWithPrefix("canary-weight") {
-			val, errAtoi := strconv.Atoi(v)
-			if errAtoi != nil {
-				err = fmt.Errorf("failed to convert %s to int: %w", v, errAtoi)
-				return
+			val, err := strconv.Atoi(v)
+			if err != nil {
+				return 0, 0, false, err
 			}
 
 			canaryWeight = val
@@ -145,21 +145,23 @@ func (i *IngressRouter) GetRoutes(canary *flaggerv1.Canary) (
 
 func (i *IngressRouter) SetRoutes(
 	canary *flaggerv1.Canary,
-	_ int,
+	primaryWeight int,
 	canaryWeight int,
-	_ bool,
+	mirrored bool,
 ) error {
 	canaryIngressName := fmt.Sprintf("%s-canary", canary.Spec.IngressRef.Name)
 	canaryIngress, err := i.kubeClient.ExtensionsV1beta1().Ingresses(canary.Namespace).Get(canaryIngressName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("ingress %s.%s get query error: %w", canaryIngressName, canary.Namespace, err)
+		return err
 	}
 
 	iClone := canaryIngress.DeepCopy()
 
 	// A/B testing
 	if len(canary.GetAnalysis().Match) > 0 {
-		var cookie, header, headerValue string
+		cookie := ""
+		header := ""
+		headerValue := ""
 		for _, m := range canary.GetAnalysis().Match {
 			for k, v := range m.Headers {
 				if k == "cookie" {
@@ -186,7 +188,7 @@ func (i *IngressRouter) SetRoutes(
 
 	_, err = i.kubeClient.ExtensionsV1beta1().Ingresses(canary.Namespace).Update(iClone)
 	if err != nil {
-		return fmt.Errorf("ingress %s.%s update error %v", iClone.Name, iClone.Namespace, err)
+		return fmt.Errorf("ingress %s update error %v", canaryIngressName, err)
 	}
 
 	return nil
diff --git a/pkg/router/istio.go b/pkg/router/istio.go
index 3cdfd220..df809313 100644
--- a/pkg/router/istio.go
+++ b/pkg/router/istio.go
@@ -28,17 +28,21 @@ type IstioRouter struct {
 func (ir *IstioRouter) Reconcile(canary *flaggerv1.Canary) error {
 	_, primaryName, canaryName := canary.GetServiceNames()
 
-	if err := ir.reconcileDestinationRule(canary, canaryName); err != nil {
-		return fmt.Errorf("reconcileDestinationRule failed: %w", err)
+	err := ir.reconcileDestinationRule(canary, canaryName)
+	if err != nil {
+		return err
 	}
 
-	if err := ir.reconcileDestinationRule(canary, primaryName); err != nil {
-		return fmt.Errorf("reconcileDestinationRule failed: %w", err)
+	err = ir.reconcileDestinationRule(canary, primaryName)
+	if err != nil {
+		return err
 	}
 
-	if err := ir.reconcileVirtualService(canary); err != nil {
-		return fmt.Errorf("reconcileVirtualService failed: %w", err)
+	err = ir.reconcileVirtualService(canary)
+	if err != nil {
+		return err
 	}
+
 	return nil
 }
 
@@ -67,13 +71,15 @@ func (ir *IstioRouter) reconcileDestinationRule(canary *flaggerv1.Canary, name s
 		}
 		_, err = ir.istioClient.NetworkingV1alpha3().DestinationRules(canary.Namespace).Create(destinationRule)
 		if err != nil {
-			return fmt.Errorf("DestinationRule %s.%s create error: %w", name, canary.Namespace, err)
+			return fmt.Errorf("DestinationRule %s.%s create error %v", name, canary.Namespace, err)
 		}
 		ir.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 			Infof("DestinationRule %s.%s created", destinationRule.GetName(), canary.Namespace)
 		return nil
-	} else if err != nil {
-		return fmt.Errorf("DestinationRule %s.%s get query error: %w", name, canary.Namespace, err)
+	}
+
+	if err != nil {
+		return fmt.Errorf("DestinationRule %s.%s query error %v", name, canary.Namespace, err)
 	}
 
 	// update
@@ -83,7 +89,7 @@ func (ir *IstioRouter) reconcileDestinationRule(canary *flaggerv1.Canary, name s
 			clone.Spec = newSpec
 			_, err = ir.istioClient.NetworkingV1alpha3().DestinationRules(canary.Namespace).Update(clone)
 			if err != nil {
-				return fmt.Errorf("DestinationRule %s.%s update error: %w", name, canary.Namespace, err)
+				return fmt.Errorf("DestinationRule %s.%s update error %v", name, canary.Namespace, err)
 			}
 			ir.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 				Infof("DestinationRule %s.%s updated", destinationRule.GetName(), canary.Namespace)
@@ -191,13 +197,15 @@ func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
 		}
 		_, err = ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Create(virtualService)
 		if err != nil {
-			return fmt.Errorf("VirtualService %s.%s create error: %w", apexName, canary.Namespace, err)
+			return fmt.Errorf("VirtualService %s.%s create error %v", apexName, canary.Namespace, err)
 		}
 		ir.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 			Infof("VirtualService %s.%s created", virtualService.GetName(), canary.Namespace)
 		return nil
-	} else if err != nil {
-		return fmt.Errorf("VirtualService %s.%s get query error %v", apexName, canary.Namespace, err)
+	}
+
+	if err != nil {
+		return fmt.Errorf("VirtualService %s.%s query error %v", apexName, canary.Namespace, err)
 	}
 
 	// update service but keep the original destination weights and mirror
@@ -206,14 +214,14 @@ func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
 			newSpec,
 			virtualService.Spec,
 			cmpopts.IgnoreFields(istiov1alpha3.DestinationWeight{}, "Weight"),
-			cmpopts.IgnoreFields(istiov1alpha3.HTTPRoute{}, "Mirror", "MirrorPercentage"),
+			cmpopts.IgnoreFields(istiov1alpha3.HTTPRoute{}, "Mirror"),
 		); diff != "" {
 			vtClone := virtualService.DeepCopy()
 			vtClone.Spec = newSpec
 
 			_, err = ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Update(vtClone)
 			if err != nil {
-				return fmt.Errorf("VirtualService %s.%s update error: %w", apexName, canary.Namespace, err)
+				return fmt.Errorf("VirtualService %s.%s update error %v", apexName, canary.Namespace, err)
 			}
 			ir.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 				Infof("VirtualService %s.%s updated", virtualService.GetName(), canary.Namespace)
@@ -234,7 +242,11 @@ func (ir *IstioRouter) GetRoutes(canary *flaggerv1.Canary) (
 	vs := &istiov1alpha3.VirtualService{}
 	vs, err = ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Get(apexName, metav1.GetOptions{})
 	if err != nil {
-		err = fmt.Errorf("VirtualService %s.%s get query error %v", apexName, canary.Namespace, err)
+		if errors.IsNotFound(err) {
+			err = fmt.Errorf("VirtualService %s.%s not found", apexName, canary.Namespace)
+			return
+		}
+		err = fmt.Errorf("VirtualService %s.%s query error %v", apexName, canary.Namespace, err)
 		return
 	}
 
@@ -279,7 +291,11 @@ func (ir *IstioRouter) SetRoutes(
 
 	vs, err := ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Get(apexName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("VirtualService %s.%s get query error %v", apexName, canary.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("VirtualService %s.%s not found", apexName, canary.Namespace)
+
+		}
+		return fmt.Errorf("VirtualService %s.%s query error %v", apexName, canary.Namespace, err)
 	}
 
 	vsCopy := vs.DeepCopy()
@@ -304,10 +320,6 @@ func (ir *IstioRouter) SetRoutes(
 		vsCopy.Spec.Http[0].Mirror = &istiov1alpha3.Destination{
 			Host: canaryName,
 		}
-
-		if mw := canary.GetAnalysis().MirrorWeight; mw > 0 {
-			vsCopy.Spec.Http[0].MirrorPercentage = &istiov1alpha3.Percent{Value: float64(mw)}
-		}
 	}
 
 	// fix routing (A/B testing)
@@ -343,7 +355,8 @@ func (ir *IstioRouter) SetRoutes(
 
 	vs, err = ir.istioClient.NetworkingV1alpha3().VirtualServices(canary.Namespace).Update(vsCopy)
 	if err != nil {
-		return fmt.Errorf("VirtualService %s.%s update failed: %w", apexName, canary.Namespace, err)
+		return fmt.Errorf("VirtualService %s.%s update failed: %v", apexName, canary.Namespace, err)
+
 	}
 	return nil
 }
diff --git a/pkg/router/kubernetes_default.go b/pkg/router/kubernetes_deployment.go
similarity index 77%
rename from pkg/router/kubernetes_default.go
rename to pkg/router/kubernetes_deployment.go
index 4511ecb9..112a2be2 100644
--- a/pkg/router/kubernetes_default.go
+++ b/pkg/router/kubernetes_deployment.go
@@ -17,8 +17,8 @@ import (
 	clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned"
 )
 
-// KubernetesDefaultRouter is managing ClusterIP services
-type KubernetesDefaultRouter struct {
+// KubernetesDeploymentRouter is managing ClusterIP services
+type KubernetesDeploymentRouter struct {
 	kubeClient    kubernetes.Interface
 	flaggerClient clientset.Interface
 	logger        *zap.SugaredLogger
@@ -28,46 +28,46 @@ type KubernetesDefaultRouter struct {
 }
 
 // Initialize creates the primary and canary services
-func (c *KubernetesDefaultRouter) Initialize(canary *flaggerv1.Canary) error {
+func (c *KubernetesDeploymentRouter) Initialize(canary *flaggerv1.Canary) error {
 	_, primaryName, canaryName := canary.GetServiceNames()
 
 	// canary svc
 	err := c.reconcileService(canary, canaryName, canary.Spec.TargetRef.Name)
 	if err != nil {
-		return fmt.Errorf("reconcileService failed: %w", err)
+		return err
 	}
 
 	// primary svc
 	err = c.reconcileService(canary, primaryName, fmt.Sprintf("%s-primary", canary.Spec.TargetRef.Name))
 	if err != nil {
-		return fmt.Errorf("reconcileService failed: %w", err)
+		return err
 	}
 
 	return nil
 }
 
 // Reconcile creates or updates the main service
-func (c *KubernetesDefaultRouter) Reconcile(canary *flaggerv1.Canary) error {
+func (c *KubernetesDeploymentRouter) Reconcile(canary *flaggerv1.Canary) error {
 	apexName, _, _ := canary.GetServiceNames()
 
 	// main svc
 	err := c.reconcileService(canary, apexName, fmt.Sprintf("%s-primary", canary.Spec.TargetRef.Name))
 	if err != nil {
-		return fmt.Errorf("reconcileService failed: %w", err)
+		return err
 	}
 
 	return nil
 }
 
-func (c *KubernetesDefaultRouter) SetRoutes(_ *flaggerv1.Canary, _ int, _ int) error {
+func (c *KubernetesDeploymentRouter) SetRoutes(canary *flaggerv1.Canary, primaryRoute int, canaryRoute int) error {
 	return nil
 }
 
-func (c *KubernetesDefaultRouter) GetRoutes(_ *flaggerv1.Canary) (primaryRoute int, canaryRoute int, err error) {
+func (c *KubernetesDeploymentRouter) GetRoutes(canary *flaggerv1.Canary) (primaryRoute int, canaryRoute int, err error) {
 	return 0, 0, nil
 }
 
-func (c *KubernetesDefaultRouter) reconcileService(canary *flaggerv1.Canary, name string, podSelector string) error {
+func (c *KubernetesDeploymentRouter) reconcileService(canary *flaggerv1.Canary, name string, podSelector string) error {
 	portName := canary.Spec.Service.PortName
 	if portName == "" {
 		portName = "http"
@@ -128,16 +128,18 @@ func (c *KubernetesDefaultRouter) reconcileService(canary *flaggerv1.Canary, nam
 			Spec: svcSpec,
 		}
 
-		_, err := c.kubeClient.CoreV1().Services(canary.Namespace).Create(svc)
+		_, err = c.kubeClient.CoreV1().Services(canary.Namespace).Create(svc)
 		if err != nil {
-			return fmt.Errorf("service %s.%s create error: %w", svc.Name, canary.Namespace, err)
+			return err
 		}
 
 		c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 			Infof("Service %s.%s created", svc.GetName(), canary.Namespace)
 		return nil
-	} else if err != nil {
-		return fmt.Errorf("service %s get query error: %w", name, err)
+	}
+
+	if err != nil {
+		return fmt.Errorf("service %s query error %v", name, err)
 	}
 
 	if svc != nil {
@@ -153,7 +155,7 @@ func (c *KubernetesDefaultRouter) reconcileService(canary *flaggerv1.Canary, nam
 			svcClone.Spec.Selector = svcSpec.Selector
 			_, err = c.kubeClient.CoreV1().Services(canary.Namespace).Update(svcClone)
 			if err != nil {
-				return fmt.Errorf("service %s update error: %w", name, err)
+				return fmt.Errorf("service %s update error %v", name, err)
 			}
 			c.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 				Infof("Service %s updated", svc.GetName())
diff --git a/pkg/router/kubernetes_noop.go b/pkg/router/kubernetes_noop.go
index b516cee8..3a66249c 100644
--- a/pkg/router/kubernetes_noop.go
+++ b/pkg/router/kubernetes_noop.go
@@ -9,10 +9,10 @@ import (
 type KubernetesNoopRouter struct {
 }
 
-func (c *KubernetesNoopRouter) Initialize(_ *flaggerv1.Canary) error {
+func (c *KubernetesNoopRouter) Initialize(canary *flaggerv1.Canary) error {
 	return nil
 }
 
-func (c *KubernetesNoopRouter) Reconcile(_ *flaggerv1.Canary) error {
+func (c *KubernetesNoopRouter) Reconcile(canary *flaggerv1.Canary) error {
 	return nil
 }
diff --git a/pkg/router/nop.go b/pkg/router/nop.go
index 4775cfda..665389d3 100644
--- a/pkg/router/nop.go
+++ b/pkg/router/nop.go
@@ -8,11 +8,11 @@ import (
 type NopRouter struct {
 }
 
-func (*NopRouter) Reconcile(_ *flaggerv1.Canary) error {
+func (*NopRouter) Reconcile(canary *flaggerv1.Canary) error {
 	return nil
 }
 
-func (*NopRouter) SetRoutes(_ *flaggerv1.Canary, _ int, _ int, _ bool) error {
+func (*NopRouter) SetRoutes(canary *flaggerv1.Canary, primaryWeight int, canaryWeight int, mirror bool) error {
 	return nil
 }
 
diff --git a/pkg/router/smi.go b/pkg/router/smi.go
index 33282207..88d678d2 100644
--- a/pkg/router/smi.go
+++ b/pkg/router/smi.go
@@ -73,14 +73,16 @@ func (sr *SmiRouter) Reconcile(canary *flaggerv1.Canary) error {
 
 		_, err := sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Create(t)
 		if err != nil {
-			return fmt.Errorf("TrafficSplit %s.%s create error: %w", apexName, canary.Namespace, err)
+			return err
 		}
 
 		sr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 			Infof("TrafficSplit %s.%s created", t.GetName(), canary.Namespace)
 		return nil
-	} else if err != nil {
-		return fmt.Errorf("TrafficSplit %s.%s get query error: %w", apexName, canary.Namespace, err)
+	}
+
+	if err != nil {
+		return fmt.Errorf("traffic split %s query error %v", apexName, err)
 	}
 
 	// update traffic split
@@ -90,7 +92,7 @@ func (sr *SmiRouter) Reconcile(canary *flaggerv1.Canary) error {
 
 		_, err := sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Update(tsClone)
 		if err != nil {
-			return fmt.Errorf("TrafficSplit %s.%s update error: %w", apexName, canary.Namespace, err)
+			return fmt.Errorf("TrafficSplit %s update error %v", apexName, err)
 		}
 
 		sr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
@@ -111,7 +113,11 @@ func (sr *SmiRouter) GetRoutes(canary *flaggerv1.Canary) (
 	apexName, primaryName, canaryName := canary.GetServiceNames()
 	ts, err := sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Get(apexName, metav1.GetOptions{})
 	if err != nil {
-		err = fmt.Errorf("TrafficSplit %s.%s get query error %v", apexName, canary.Namespace, err)
+		if errors.IsNotFound(err) {
+			err = fmt.Errorf("TrafficSplit %s.%s not found", apexName, canary.Namespace)
+			return
+		}
+		err = fmt.Errorf("TrafficSplit %s.%s query error %v", apexName, canary.Namespace, err)
 		return
 	}
 
@@ -140,12 +146,16 @@ func (sr *SmiRouter) SetRoutes(
 	canary *flaggerv1.Canary,
 	primaryWeight int,
 	canaryWeight int,
-	_ bool,
+	mirrored bool,
 ) error {
 	apexName, primaryName, canaryName := canary.GetServiceNames()
 	ts, err := sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Get(apexName, metav1.GetOptions{})
 	if err != nil {
-		return fmt.Errorf("TrafficSplit %s.%s get query error %v", apexName, canary.Namespace, err)
+		if errors.IsNotFound(err) {
+			return fmt.Errorf("TrafficSplit %s.%s not found", apexName, canary.Namespace)
+
+		}
+		return fmt.Errorf("TrafficSplit %s.%s query error %v", apexName, canary.Namespace, err)
 	}
 
 	backends := []smiv1alpha1.TrafficSplitBackend{
@@ -164,7 +174,7 @@ func (sr *SmiRouter) SetRoutes(
 
 	_, err = sr.smiClient.SplitV1alpha1().TrafficSplits(canary.Namespace).Update(tsClone)
 	if err != nil {
-		return fmt.Errorf("TrafficSplit %s.%s update error %v", apexName, canary.Namespace, err)
+		return fmt.Errorf("TrafficSplit %s update error %v", apexName, err)
 	}
 
 	return nil
@@ -214,13 +224,11 @@ func (sr *SmiRouter) getWithConvert(canary *flaggerv1.Canary, host string) (*smi
 
 		_, err := sr.smiClient.SplitV1alpha2().TrafficSplits(canary.Namespace).Update(t)
 		if err != nil {
-			return nil, fmt.Errorf("TrafficSplit %s.%s update error: %w", apexName, canary.Namespace, err)
+			return nil, err
 		}
 
 		sr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)).
 			Infof("TrafficSplit %s.%s converted", t.GetName(), canary.Namespace)
-	} else if err != nil {
-		return nil, fmt.Errorf("TrafficSplit %s.%s get query error %v", apexName, canary.Namespace, err)
 	}
-	return ts, nil
+	return ts, err
 }


Test patch:
diff --git a/artifacts/examples/istio-abtest.yaml b/artifacts/examples/istio-abtest.yaml
index 28cdde14..f0492bc2 100644
--- a/artifacts/examples/istio-abtest.yaml
+++ b/artifacts/examples/istio-abtest.yaml
@@ -43,7 +43,7 @@ spec:
           regex: "^(.*?;)?(type=insider)(;.*)?$"
     - headers:
         user-agent:
-          regex: ".*Firefox.*"
+          regex: "(?=.*Safari)(?!.*Chrome).*$"
     metrics:
       - name: request-success-rate
         thresholdRange:
diff --git a/docs/gitbook/tutorials/istio-ab-testing.md b/docs/gitbook/tutorials/istio-ab-testing.md
index a416d7e3..e47afce8 100644
--- a/docs/gitbook/tutorials/istio-ab-testing.md
+++ b/docs/gitbook/tutorials/istio-ab-testing.md
@@ -65,7 +65,7 @@ Deploy the load testing service to generate traffic during the canary analysis:
 kubectl apply -k github.com/weaveworks/flagger//kustomize/tester
 ```
 
-Create a canary custom resource (replace example.com with your own domain):
+Create a canary custom resource \(replace example.com with your own domain\):
 
 ```yaml
 apiVersion: flagger.app/v1beta1
@@ -112,7 +112,7 @@ spec:
     match:
       - headers:
           user-agent:
-            regex: ".*Firefox.*"
+            regex: "^(?!.*Chrome).*Safari.*"
       - headers:
           cookie:
             regex: "^(.*?;)?(type=insider)(;.*)?$"
@@ -133,15 +133,12 @@ spec:
     webhooks:
       - name: load-test
         url: http://flagger-loadtester.test/
-        timeout: 15s
+        timeout: 5s
         metadata:
           cmd: "hey -z 1m -q 10 -c 2 -H 'Cookie: type=insider' http://podinfo.test:9898/"
 ```
 
-**Note** that when using Istio 1.5 you have to replace the `request-duration`
-with a [metric template](https://docs.flagger.app/dev/upgrade-guide#istio-telemetry-v2).
-
-The above configuration will run an analysis for ten minutes targeting Firefox users and those that have an insider cookie.
+The above configuration will run an analysis for ten minutes targeting Safari users and those that have an insider cookie.
 
 Save the above resource as podinfo-abtest.yaml and then apply it:
 
diff --git a/pkg/canary/daemonset_controller_test.go b/pkg/canary/daemonset_controller_test.go
index 0222a710..9bd5bc53 100644
--- a/pkg/canary/daemonset_controller_test.go
+++ b/pkg/canary/daemonset_controller_test.go
@@ -161,12 +161,12 @@ func TestDaemonSetController_HasTargetChanged(t *testing.T) {
 }
 
 func TestDaemonSetController_Scale(t *testing.T) {
-	t.Run("ScaleToZero", func(t *testing.T) {
+	t.Run("Scale", func(t *testing.T) {
 		mocks := newDaemonSetFixture()
 		err := mocks.controller.Initialize(mocks.canary, true)
 		require.NoError(t, err)
 
-		err = mocks.controller.ScaleToZero(mocks.canary)
+		err = mocks.controller.Scale(mocks.canary, 0)
 		require.NoError(t, err)
 
 		c, err := mocks.kubeClient.AppsV1().DaemonSets("default").Get("podinfo", metav1.GetOptions{})
diff --git a/pkg/canary/daemonset_ready_test.go b/pkg/canary/daemonset_ready_test.go
index 76a810cf..9754b866 100644
--- a/pkg/canary/daemonset_ready_test.go
+++ b/pkg/canary/daemonset_ready_test.go
@@ -16,7 +16,7 @@ func TestDaemonSetController_IsReady(t *testing.T) {
 	err := mocks.controller.Initialize(mocks.canary, true)
 	assert.NoError(t, err, "Expected primary readiness check to fail")
 
-	err = mocks.controller.IsPrimaryReady(mocks.canary)
+	_, err = mocks.controller.IsPrimaryReady(mocks.canary)
 	require.NoError(t, err)
 
 	_, err = mocks.controller.IsCanaryReady(mocks.canary)
diff --git a/pkg/canary/deployment_controller_test.go b/pkg/canary/deployment_controller_test.go
index 0c24d8e7..06958e3f 100644
--- a/pkg/canary/deployment_controller_test.go
+++ b/pkg/canary/deployment_controller_test.go
@@ -72,17 +72,79 @@ func TestDeploymentController_Promote(t *testing.T) {
 	assert.Equal(t, int32(2), hpaPrimary.Spec.MaxReplicas)
 }
 
-func TestDeploymentController_ScaleToZero(t *testing.T) {
+func TestDeploymentController_IsReady(t *testing.T) {
+	mocks := newDeploymentFixture()
+	err := mocks.controller.Initialize(mocks.canary, true)
+	require.NoError(t, err, "Expected primary readiness check to fail")
+
+	_, err = mocks.controller.IsPrimaryReady(mocks.canary)
+	require.Error(t, err)
+
+	_, err = mocks.controller.IsCanaryReady(mocks.canary)
+	require.NoError(t, err)
+}
+
+func TestDeploymentController_SetFailedChecks(t *testing.T) {
+	mocks := newDeploymentFixture()
+	err := mocks.controller.Initialize(mocks.canary, true)
+	require.NoError(t, err)
+
+	err = mocks.controller.SetStatusFailedChecks(mocks.canary, 1)
+	require.NoError(t, err)
+
+	res, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
+	require.NoError(t, err)
+	assert.Equal(t, 1, res.Status.FailedChecks)
+}
+
+func TestDeploymentController_SetState(t *testing.T) {
+	mocks := newDeploymentFixture()
+	err := mocks.controller.Initialize(mocks.canary, true)
+	require.NoError(t, err)
+
+	err = mocks.controller.SetStatusPhase(mocks.canary, flaggerv1.CanaryPhaseProgressing)
+	require.NoError(t, err)
+
+	res, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
+	require.NoError(t, err)
+	assert.Equal(t, flaggerv1.CanaryPhaseProgressing, res.Status.Phase)
+}
+
+func TestDeploymentController_SyncStatus(t *testing.T) {
+	mocks := newDeploymentFixture()
+	err := mocks.controller.Initialize(mocks.canary, true)
+	require.NoError(t, err)
+
+	status := flaggerv1.CanaryStatus{
+		Phase:        flaggerv1.CanaryPhaseProgressing,
+		FailedChecks: 2,
+	}
+	err = mocks.controller.SyncStatus(mocks.canary, status)
+	require.NoError(t, err)
+
+	res, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
+	require.NoError(t, err)
+	assert.Equal(t, status.Phase, res.Status.Phase)
+	assert.Equal(t, status.FailedChecks, res.Status.FailedChecks)
+
+	require.NotNil(t, res.Status.TrackedConfigs)
+	configs := *res.Status.TrackedConfigs
+	secret := newDeploymentControllerTestSecret()
+	_, exists := configs["secret/"+secret.GetName()]
+	assert.True(t, exists, "Secret %s not found in status", secret.GetName())
+}
+
+func TestDeploymentController_Scale(t *testing.T) {
 	mocks := newDeploymentFixture()
 	err := mocks.controller.Initialize(mocks.canary, true)
 	require.NoError(t, err)
 
-	err = mocks.controller.ScaleToZero(mocks.canary)
+	err = mocks.controller.Scale(mocks.canary, 2)
 	require.NoError(t, err)
 
 	c, err := mocks.kubeClient.AppsV1().Deployments("default").Get("podinfo", metav1.GetOptions{})
 	require.NoError(t, err)
-	assert.Equal(t, int32(0), *c.Spec.Replicas)
+	assert.Equal(t, int32(2), *c.Spec.Replicas)
 }
 
 func TestDeploymentController_NoConfigTracking(t *testing.T) {
diff --git a/pkg/canary/deployment_fixture_test.go b/pkg/canary/deployment_fixture_test.go
index b2a0d6a6..486b3472 100644
--- a/pkg/canary/deployment_fixture_test.go
+++ b/pkg/canary/deployment_fixture_test.go
@@ -207,7 +207,7 @@ func newDeploymentControllerTestCanary() *flaggerv1.Canary {
 				Kind:       "HorizontalPodAutoscaler",
 			}, Service: flaggerv1.CanaryService{
 				Port: 9898,
-			}, Analysis: &flaggerv1.CanaryAnalysis{
+			}, CanaryAnalysis: &flaggerv1.CanaryAnalysis{
 				Threshold:  10,
 				StepWeight: 10,
 				MaxWeight:  50,
diff --git a/pkg/canary/deployment_ready_test.go b/pkg/canary/deployment_ready_test.go
deleted file mode 100644
index bb6e8d9f..00000000
--- a/pkg/canary/deployment_ready_test.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package canary
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestDeploymentController_IsReady(t *testing.T) {
-	mocks := newDeploymentFixture()
-	err := mocks.controller.Initialize(mocks.canary, true)
-	require.NoError(t, err, "Expected primary readiness check to fail")
-
-	err = mocks.controller.IsPrimaryReady(mocks.canary)
-	require.Error(t, err)
-
-	_, err = mocks.controller.IsCanaryReady(mocks.canary)
-	require.NoError(t, err)
-}
diff --git a/pkg/canary/deployment_status_test.go b/pkg/canary/deployment_status_test.go
deleted file mode 100644
index 95b2b558..00000000
--- a/pkg/canary/deployment_status_test.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package canary
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-	flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1"
-)
-
-func TestDeploymentController_SyncStatus(t *testing.T) {
-	mocks := newDeploymentFixture()
-	err := mocks.controller.Initialize(mocks.canary, true)
-	require.NoError(t, err)
-
-	status := flaggerv1.CanaryStatus{
-		Phase:        flaggerv1.CanaryPhaseProgressing,
-		FailedChecks: 2,
-	}
-	err = mocks.controller.SyncStatus(mocks.canary, status)
-	require.NoError(t, err)
-
-	res, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
-	require.NoError(t, err)
-	assert.Equal(t, status.Phase, res.Status.Phase)
-	assert.Equal(t, status.FailedChecks, res.Status.FailedChecks)
-
-	require.NotNil(t, res.Status.TrackedConfigs)
-	configs := *res.Status.TrackedConfigs
-	secret := newDeploymentControllerTestSecret()
-	_, exists := configs["secret/"+secret.GetName()]
-	assert.True(t, exists, "Secret %s not found in status", secret.GetName())
-}
-
-func TestDeploymentController_SetFailedChecks(t *testing.T) {
-	mocks := newDeploymentFixture()
-	err := mocks.controller.Initialize(mocks.canary, true)
-	require.NoError(t, err)
-
-	err = mocks.controller.SetStatusFailedChecks(mocks.canary, 1)
-	require.NoError(t, err)
-
-	res, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
-	require.NoError(t, err)
-	assert.Equal(t, 1, res.Status.FailedChecks)
-}
-
-func TestDeploymentController_SetState(t *testing.T) {
-	mocks := newDeploymentFixture()
-	err := mocks.controller.Initialize(mocks.canary, true)
-	require.NoError(t, err)
-
-	err = mocks.controller.SetStatusPhase(mocks.canary, flaggerv1.CanaryPhaseProgressing)
-	require.NoError(t, err)
-
-	res, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
-	require.NoError(t, err)
-	assert.Equal(t, flaggerv1.CanaryPhaseProgressing, res.Status.Phase)
-}
diff --git a/pkg/controller/scheduler_daemonset_fixture_test.go b/pkg/controller/scheduler_daemonset_fixture_test.go
index 5ce0cacb..8d2cdd6d 100644
--- a/pkg/controller/scheduler_daemonset_fixture_test.go
+++ b/pkg/controller/scheduler_daemonset_fixture_test.go
@@ -24,7 +24,6 @@ import (
 	"github.com/weaveworks/flagger/pkg/logger"
 	"github.com/weaveworks/flagger/pkg/metrics"
 	"github.com/weaveworks/flagger/pkg/metrics/observers"
-	"github.com/weaveworks/flagger/pkg/notifier"
 	"github.com/weaveworks/flagger/pkg/router"
 )
 
@@ -91,6 +90,7 @@ func newDaemonSetFixture(c *flaggerv1.Canary) daemonSetFixture {
 
 	ctrl := &Controller{
 		kubeClient:       kubeClient,
+		istioClient:      flaggerClient,
 		flaggerClient:    flaggerClient,
 		flaggerInformers: fi,
 		flaggerSynced:    fi.CanaryInformer.Informer().HasSynced,
@@ -103,7 +103,6 @@ func newDaemonSetFixture(c *flaggerv1.Canary) daemonSetFixture {
 		observerFactory:  observerFactory,
 		recorder:         metrics.NewRecorder(controllerAgentName, false),
 		routerFactory:    rf,
-		notifier:         &notifier.NopNotifier{},
 	}
 	ctrl.flaggerSynced = alwaysReady
 	ctrl.flaggerInformers.CanaryInformer.Informer().GetIndexer().Add(c)
@@ -251,7 +250,7 @@ func newDaemonSetTestCanary() *flaggerv1.Canary {
 				Kind:       "DaemonSet",
 			}, Service: flaggerv1.CanaryService{
 				Port: 9898,
-			}, Analysis: &flaggerv1.CanaryAnalysis{
+			}, CanaryAnalysis: &flaggerv1.CanaryAnalysis{
 				Threshold:  10,
 				StepWeight: 10,
 				MaxWeight:  50,
@@ -290,7 +289,7 @@ func newDaemonSetTestCanary() *flaggerv1.Canary {
 
 func newDaemonSetTestCanaryMirror() *flaggerv1.Canary {
 	cd := newDaemonSetTestCanary()
-	cd.Spec.Analysis.Mirror = true
+	cd.Spec.CanaryAnalysis.Mirror = true
 	return cd
 }
 
@@ -308,7 +307,7 @@ func newDaemonSetTestCanaryAB() *flaggerv1.Canary {
 				Kind:       "DaemonSet",
 			}, Service: flaggerv1.CanaryService{
 				Port: 9898,
-			}, Analysis: &flaggerv1.CanaryAnalysis{
+			}, CanaryAnalysis: &flaggerv1.CanaryAnalysis{
 				Threshold:  10,
 				Iterations: 10,
 				Match: []istiov1alpha3.HTTPMatchRequest{
diff --git a/pkg/controller/scheduler_daemonset_test.go b/pkg/controller/scheduler_daemonset_test.go
index 3d650f45..4ed0ea33 100644
--- a/pkg/controller/scheduler_daemonset_test.go
+++ b/pkg/controller/scheduler_daemonset_test.go
@@ -55,7 +55,7 @@ func TestScheduler_DaemonSetRollback(t *testing.T) {
 	require.NoError(t, err)
 
 	cd := c.DeepCopy()
-	cd.Spec.Analysis.Metrics = append(c.Spec.Analysis.Metrics, flaggerv1.CanaryMetric{
+	cd.Spec.CanaryAnalysis.Metrics = append(c.Spec.CanaryAnalysis.Metrics, flaggerv1.CanaryMetric{
 		Name:     "fail",
 		Interval: "1m",
 		ThresholdRange: &flaggerv1.CanaryThresholdRange{
@@ -437,7 +437,7 @@ func TestScheduler_DaemonSetAlerts(t *testing.T) {
 	defer ts.Close()
 
 	canary := newDaemonSetTestCanary()
-	canary.Spec.Analysis.Alerts = []flaggerv1.CanaryAlert{
+	canary.Spec.CanaryAnalysis.Alerts = []flaggerv1.CanaryAlert{
 		{
 			Name:     "slack-dev",
 			Severity: "info",
diff --git a/pkg/controller/scheduler_deployment_fixture_test.go b/pkg/controller/scheduler_deployment_fixture_test.go
index 3006d3eb..74a7321e 100644
--- a/pkg/controller/scheduler_deployment_fixture_test.go
+++ b/pkg/controller/scheduler_deployment_fixture_test.go
@@ -25,7 +25,6 @@ import (
 	"github.com/weaveworks/flagger/pkg/logger"
 	"github.com/weaveworks/flagger/pkg/metrics"
 	"github.com/weaveworks/flagger/pkg/metrics/observers"
-	"github.com/weaveworks/flagger/pkg/notifier"
 	"github.com/weaveworks/flagger/pkg/router"
 )
 
@@ -93,6 +92,7 @@ func newDeploymentFixture(c *flaggerv1.Canary) fixture {
 
 	ctrl := &Controller{
 		kubeClient:       kubeClient,
+		istioClient:      flaggerClient,
 		flaggerClient:    flaggerClient,
 		flaggerInformers: fi,
 		flaggerSynced:    fi.CanaryInformer.Informer().HasSynced,
@@ -105,7 +105,6 @@ func newDeploymentFixture(c *flaggerv1.Canary) fixture {
 		observerFactory:  observerFactory,
 		recorder:         metrics.NewRecorder(controllerAgentName, false),
 		routerFactory:    rf,
-		notifier:         &notifier.NopNotifier{},
 	}
 	ctrl.flaggerSynced = alwaysReady
 	ctrl.flaggerInformers.CanaryInformer.Informer().GetIndexer().Add(c)
@@ -258,7 +257,7 @@ func newDeploymentTestCanary() *flaggerv1.Canary {
 				Kind:       "HorizontalPodAutoscaler",
 			}, Service: flaggerv1.CanaryService{
 				Port: 9898,
-			}, Analysis: &flaggerv1.CanaryAnalysis{
+			}, CanaryAnalysis: &flaggerv1.CanaryAnalysis{
 				Threshold:  10,
 				StepWeight: 10,
 				MaxWeight:  50,
@@ -297,7 +296,7 @@ func newDeploymentTestCanary() *flaggerv1.Canary {
 
 func newDeploymentTestCanaryMirror() *flaggerv1.Canary {
 	cd := newDeploymentTestCanary()
-	cd.Spec.Analysis.Mirror = true
+	cd.Spec.CanaryAnalysis.Mirror = true
 	return cd
 }
 
@@ -320,7 +319,7 @@ func newDeploymentTestCanaryAB() *flaggerv1.Canary {
 				Kind:       "HorizontalPodAutoscaler",
 			}, Service: flaggerv1.CanaryService{
 				Port: 9898,
-			}, Analysis: &flaggerv1.CanaryAnalysis{
+			}, CanaryAnalysis: &flaggerv1.CanaryAnalysis{
 				Threshold:  10,
 				Iterations: 10,
 				Match: []istiov1alpha3.HTTPMatchRequest{
diff --git a/pkg/controller/scheduler_deployment_test.go b/pkg/controller/scheduler_deployment_test.go
index b65b6447..d518956e 100644
--- a/pkg/controller/scheduler_deployment_test.go
+++ b/pkg/controller/scheduler_deployment_test.go
@@ -55,7 +55,7 @@ func TestScheduler_DeploymentRollback(t *testing.T) {
 	c, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Get("podinfo", metav1.GetOptions{})
 	require.NoError(t, err)
 	cd := c.DeepCopy()
-	cd.Spec.Analysis.Metrics = append(c.Spec.Analysis.Metrics, flaggerv1.CanaryMetric{
+	cd.Spec.CanaryAnalysis.Metrics = append(c.Spec.CanaryAnalysis.Metrics, flaggerv1.CanaryMetric{
 		Name:     "fail",
 		Interval: "1m",
 		ThresholdRange: &flaggerv1.CanaryThresholdRange{
@@ -519,7 +519,7 @@ func TestScheduler_DeploymentAlerts(t *testing.T) {
 	defer ts.Close()
 
 	canary := newDeploymentTestCanary()
-	canary.Spec.Analysis.Alerts = []flaggerv1.CanaryAlert{
+	canary.Spec.CanaryAnalysis.Alerts = []flaggerv1.CanaryAlert{
 		{
 			Name:     "slack-dev",
 			Severity: "info",
diff --git a/pkg/controller/scheduler_svc_test.go b/pkg/controller/scheduler_svc_test.go
index d1214adb..e552906b 100644
--- a/pkg/controller/scheduler_svc_test.go
+++ b/pkg/controller/scheduler_svc_test.go
@@ -98,7 +98,7 @@ func newTestServiceCanary() *flaggerv1.Canary {
 			Service: flaggerv1.CanaryService{
 				Port: 9898,
 			},
-			Analysis: &flaggerv1.CanaryAnalysis{
+			CanaryAnalysis: &flaggerv1.CanaryAnalysis{
 				Threshold:  10,
 				StepWeight: 10,
 				MaxWeight:  50,
diff --git a/pkg/loadtester/bash.go b/pkg/loadtester/bash.go
index eccc9d19..8631c6f0 100644
--- a/pkg/loadtester/bash.go
+++ b/pkg/loadtester/bash.go
@@ -24,7 +24,7 @@ func (task *BashTask) Run(ctx context.Context) (bool, error) {
 
 	if err != nil {
 		task.logger.With("canary", task.canary).Errorf("command failed %s %v %s", task.command, err, out)
-		return false, fmt.Errorf("command %s failed: %s: %w", task.command, out, err)
+		return false, fmt.Errorf(" %v %s", err, out)
 	} else {
 		if task.logCmdOutput {
 			fmt.Printf("%s\n", out)
diff --git a/pkg/loadtester/concord.go b/pkg/loadtester/concord.go
new file mode 100644
index 00000000..bef452a0
--- /dev/null
+++ b/pkg/loadtester/concord.go
@@ -0,0 +1,242 @@
+package loadtester
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"mime/multipart"
+	"net/http"
+	"net/url"
+	"os"
+	"strconv"
+	"time"
+
+	"go.uber.org/zap"
+)
+
+// TaskTypeConcord represents the concord type as string
+const TaskTypeConcord = "concord"
+
+// default process values
+const concordBasePath = "/api/v1/process"
+const defaultPollInterval = 5
+const defaultPollTimeout = 30
+
+// concord statuses
+const concordStatusSuccess = "FINISHED"
+const concordStatusFailed = "FAILED"
+
+// ConcordTask represents a concord task
+type ConcordTask struct {
+	TaskBase
+	Command      string
+	Org          string
+	Project      string
+	Repo         string
+	Entrypoint   string
+	APIKeyPath   string
+	Endpoint     string
+	PollInterval time.Duration
+	PollTimeout  time.Duration
+	BaseURL      *url.URL
+	httpClient   *http.Client
+}
+
+// NewConcordTask instantiates a new Concord Task
+func NewConcordTask(metadata map[string]string, canary string, logger *zap.SugaredLogger) (*ConcordTask, error) {
+	var pollIntervalInt, pollTimeoutInt int
+
+	if _, found := metadata["server"]; found == false {
+		return nil, errors.New("`server` is required with type concord")
+	}
+	pURL, err := url.Parse(metadata["server"])
+	if err != nil {
+		return nil, errors.New("failed to create base URL from metadata `concord_url`")
+	}
+	if _, found := metadata["org"]; found == false {
+		return nil, errors.New("`org` is required with type concord")
+	}
+	if _, found := metadata["project"]; found == false {
+		return nil, errors.New("`project` is required with type concord")
+	}
+	if _, found := metadata["repo"]; found == false {
+		return nil, errors.New("`repo` is required with type concord")
+	}
+	if _, found := metadata["entrypoint"]; found == false {
+		return nil, errors.New("`entrypoint` is required with type concord")
+	}
+	if _, found := metadata["apiKeyPath"]; found == false {
+		return nil, errors.New("`apiKeyPath` is required with type concord")
+	}
+	_, err = os.Stat(metadata["apiKeyPath"])
+	if os.IsNotExist(err) {
+		return nil, fmt.Errorf("`apiKeyPath` file doesn't exist %s", metadata["apiKeyPath"])
+	}
+	if _, found := metadata["endpoint"]; found == false {
+		return nil, errors.New("`endpoint` is required with type concord")
+	}
+	if _, found := metadata["pollInterval"]; found == false {
+		pollIntervalInt = defaultPollInterval
+	} else {
+		pollIntervalInt, err = strconv.Atoi(metadata["pollInterval"])
+		if err != nil {
+			return nil, errors.New("unable to convert `pollInterval` to int")
+		}
+	}
+	if _, found := metadata["pollTimeout"]; found == false {
+		pollTimeoutInt = defaultPollTimeout
+	} else {
+		pollTimeoutInt, err = strconv.Atoi(metadata["pollTimeout"])
+		if err != nil {
+			return nil, errors.New("unable to convert `pollTimeout` to int")
+		}
+	}
+
+	return &ConcordTask{
+		TaskBase: TaskBase{
+			logger: logger,
+		},
+		BaseURL:      pURL,
+		Org:          metadata["org"],
+		Project:      metadata["project"],
+		Repo:         metadata["repo"],
+		Entrypoint:   metadata["entrypoint"],
+		APIKeyPath:   metadata["apiKeyPath"],
+		Endpoint:     metadata["endpoint"],
+		PollInterval: time.Duration(pollIntervalInt) * time.Second,
+		PollTimeout:  time.Duration(pollTimeoutInt) * time.Second,
+		httpClient:   &http.Client{Timeout: 60 * time.Second},
+	}, nil
+}
+
+func (task *ConcordTask) Hash() string {
+	return hash(task.canary + task.Org + task.Project + task.Repo + task.Entrypoint)
+}
+
+func (task *ConcordTask) String() string {
+	return fmt.Sprintf("%s %s %s %s", task.Org, task.Project, task.Repo, task.Entrypoint)
+}
+
+func (task *ConcordTask) Run(ctx context.Context) (bool, error) {
+	instance, err := task.startProcess()
+	if err != nil {
+		task.logger.Errorf("failed to start process: %s", err.Error())
+		return false, err
+	}
+
+	return task.checkStatus(ctx, instance, task.PollInterval)
+}
+
+type concordProcess struct {
+	InstanceID       string   `json:"instanceId, omitempty"`
+	ParentInstanceID string   `json:"parentInstanceID,omitempty"`
+	ProjectName      string   `json:"projectName,omitempty"`
+	CreatedAt        string   `json:"createdAt,omitempty"`
+	Initiator        string   `json:"initiator,omitempty"`
+	LastUpdatedAt    string   `json:"lastUpdatedAt,omitempty"`
+	Status           string   `json:"status,omitempty"`
+	ChildrenIds      []string `json:"childrenIds,omitempty"`
+	OK               bool     `json:"ok,omitempty"`
+}
+
+func (task *ConcordTask) newRequest(method, path string, contentType string, body io.Reader) (*http.Request, error) {
+	rel := &url.URL{Path: path}
+	u := task.BaseURL.ResolveReference(rel)
+	req, err := http.NewRequest(method, u.String(), body)
+	if err != nil {
+		return nil, err
+	}
+
+	if contentType != "" {
+		req.Header.Set("Content-Type", contentType)
+	}
+
+	apiKey := ""
+	dat, err := ioutil.ReadFile(task.APIKeyPath)
+	if err != nil {
+		return req, err
+	}
+	apiKey = string(dat)
+
+	if apiKey != "" {
+		req.Header.Set("Authorization", apiKey)
+	}
+
+	return req, nil
+}
+
+func (task *ConcordTask) do(req *http.Request, v interface{}) (*http.Response, error) {
+	task.logger.With("canary", task.canary).Infof("calling endpoint %s", req.URL.String())
+	resp, err := task.httpClient.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	err = json.NewDecoder(resp.Body).Decode(v)
+	return resp, err
+}
+
+func (task *ConcordTask) startProcess() (string, error) {
+	requestBody := new(bytes.Buffer)
+	writer := multipart.NewWriter(requestBody)
+	_ = writer.WriteField("org", task.Org)
+	_ = writer.WriteField("project", task.Project)
+	_ = writer.WriteField("repo", task.Repo)
+	_ = writer.WriteField("entryPoint", task.Entrypoint)
+	_ = writer.WriteField("arguments.endpoint", task.Endpoint)
+
+	err := writer.Close()
+	if err != nil {
+		return "", nil
+	}
+
+	req, err := task.newRequest(http.MethodPost, concordBasePath, writer.FormDataContentType(), requestBody)
+	if err != nil {
+		return "", err
+	}
+
+	process := concordProcess{}
+	_, err = task.do(req, &process)
+	if err != nil {
+		return "", err
+	}
+
+	task.logger.With("canary", task.canary).Infof("created process id [%s] OK status is %t", process.InstanceID, process.OK)
+	return process.InstanceID, nil
+}
+
+func (task *ConcordTask) checkStatus(ctx context.Context, instanceID string, interval time.Duration) (bool, error) {
+	tickChan := time.NewTicker(interval).C
+	for {
+		select {
+		case <-tickChan:
+			req, err := task.newRequest(http.MethodGet, fmt.Sprintf("%s/%s", concordBasePath, instanceID), "", nil)
+			if err != nil {
+				return false, fmt.Errorf("failed to generate request: %s", err)
+			}
+
+			process := concordProcess{}
+			_, err = task.do(req, &process)
+			if err != nil {
+				return false, fmt.Errorf("failed checking status: %s", err)
+			}
+			task.logger.With("canary", task.canary).Infof("process id [%s] current status is %s", process.InstanceID, process.Status)
+
+			if process.Status == concordStatusSuccess {
+				return true, nil
+			}
+			if process.Status == concordStatusFailed {
+				return false, fmt.Errorf("concord instanceID: %s failed", instanceID)
+			}
+		case <-time.After(task.PollTimeout):
+			return false, fmt.Errorf("concord process timed out, after %d seconds", int64(task.PollTimeout/time.Second))
+		case <-ctx.Done():
+			return false, errors.New("context timedout")
+		}
+
+	}
+}
diff --git a/pkg/loadtester/helm.go b/pkg/loadtester/helm.go
index 17661139..591bfc7c 100644
--- a/pkg/loadtester/helm.go
+++ b/pkg/loadtester/helm.go
@@ -27,7 +27,7 @@ func (task *HelmTask) Run(ctx context.Context) (bool, error) {
 	out, err := cmd.CombinedOutput()
 	if err != nil {
 		task.logger.With("canary", task.canary).Errorf("command failed %s %v %s", task.command, err, out)
-		return false, fmt.Errorf("command %s failed: %s: %w", task.command, out, err)
+		return false, fmt.Errorf(" %v %s", err, out)
 	} else {
 		if task.logCmdOutput {
 			fmt.Printf("%s\n", out)
diff --git a/pkg/loadtester/helmv3.go b/pkg/loadtester/helmv3.go
index 3d824559..0970b55d 100644
--- a/pkg/loadtester/helmv3.go
+++ b/pkg/loadtester/helmv3.go
@@ -27,7 +27,7 @@ func (task *HelmTaskv3) Run(ctx context.Context) (bool, error) {
 	out, err := cmd.CombinedOutput()
 	if err != nil {
 		task.logger.With("canary", task.canary).Errorf("command failed %s %v %s", task.command, err, out)
-		return false, fmt.Errorf("command %s failed: %s: %w", task.command, out, err)
+		return false, fmt.Errorf(" %v %s", err, out)
 	} else {
 		if task.logCmdOutput {
 			fmt.Printf("%s\n", out)
diff --git a/pkg/loadtester/server.go b/pkg/loadtester/server.go
index f6dc79da..01c73e5d 100644
--- a/pkg/loadtester/server.go
+++ b/pkg/loadtester/server.go
@@ -288,6 +288,34 @@ func ListenAndServe(port string, timeout time.Duration, logger *zap.SugaredLogge
 				return
 			}
 
+			// run concord job (blocking task)
+			if typ == TaskTypeConcord {
+				concord, err := NewConcordTask(payload.Metadata, fmt.Sprintf("%s.%s", payload.Name, payload.Namespace), logger)
+
+				if err != nil {
+					logger.With("canary", payload.Name).Errorf("concord task init error: %s", err)
+					w.WriteHeader(http.StatusInternalServerError)
+					w.Write([]byte(err.Error()))
+					return
+				}
+
+				ctx, cancel := context.WithTimeout(context.Background(), taskRunner.timeout)
+				defer cancel()
+
+				ok, err := concord.Run(ctx)
+				if !ok {
+					if err != nil {
+						logger.With("canary", payload.Name).Errorf("concord task error: %s", err)
+					}
+					w.WriteHeader(http.StatusInternalServerError)
+					w.Write([]byte(err.Error()))
+					return
+				}
+
+				w.WriteHeader(http.StatusOK)
+				return
+			}
+
 			taskFactory, ok := GetTaskFactory(typ)
 			if !ok {
 				w.WriteHeader(http.StatusBadRequest)
diff --git a/pkg/loadtester/task_ngrinder.go b/pkg/loadtester/task_ngrinder.go
index c2c98862..9d7e8b86 100644
--- a/pkg/loadtester/task_ngrinder.go
+++ b/pkg/loadtester/task_ngrinder.go
@@ -29,16 +29,16 @@ func init() {
 		}
 		baseUrl, err := url.Parse(server)
 		if err != nil {
-			return nil, fmt.Errorf("invalid url: %s: %w", server, err)
+			return nil, errors.New(fmt.Sprintf("invalid url: %s", server))
 		}
 		cloneId, err := strconv.Atoi(clone)
 		if err != nil {
-			return nil, fmt.Errorf("metadata clone must be integer: %w", err)
+			return nil, errors.New("metadata clone must be integer")
 		}
 
 		passwdDecoded, err := base64.StdEncoding.DecodeString(passwd)
 		if err != nil {
-			return nil, fmt.Errorf("metadata password is invalid: %w", err)
+			return nil, errors.New("metadata auth provided is invalid, base64 encoded username:password required")
 		}
 		interval, err := time.ParseDuration(pollInterval)
 		if err != nil {
@@ -90,8 +90,7 @@ func (task *NGrinderTask) Run(ctx context.Context) bool {
 	url := task.CloneAndStartEndpoint().String()
 	result, err := task.request("POST", url, ctx)
 	if err != nil {
-		task.logger.With("canary", task.canary).
-			Errorf("failed to clone and start ngrinder test %s: %s", url, err.Error())
+		task.logger.With("canary", task.canary).Errorf("failed to clone and start ngrinder test %s: %s", url, err.Error())
 		return false
 	}
 	id := result["id"]
@@ -135,35 +134,26 @@ func (task *NGrinderTask) PollStatus(ctx context.Context) bool {
 // send request, handle error, and eavl response json
 func (task *NGrinderTask) request(method, url string, ctx context.Context) (map[string]interface{}, error) {
 	task.logger.Debugf("send %s request to %s", method, url)
-	req, err := http.NewRequest(method, url, nil)
-	if err != nil {
-		return nil, fmt.Errorf("http.NewRequest failed: %w", err)
-	}
-
+	req, _ := http.NewRequest(method, url, nil)
 	req.SetBasicAuth(task.username, task.passwd)
 	if ctx != nil {
 		req = req.WithContext(ctx)
 	}
 	resp, err := http.DefaultClient.Do(req)
-	if err != nil {
-		task.logger.Errorf("request failed: %s", err.Error())
-		return nil, fmt.Errorf("request failed: %w", err)
+	if resp != nil {
+		defer resp.Body.Close()
 	}
-	defer resp.Body.Close()
-
-	respBytes, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
-		return nil, fmt.Errorf("reading response body failed: %w", err)
+		task.logger.Errorf("bad request: %s", err.Error())
+		return nil, err
 	}
-
+	respBytes, err := ioutil.ReadAll(resp.Body)
 	res := make(map[string]interface{})
-	if err := json.Unmarshal(respBytes, &res); err != nil {
-		task.logger.Errorf("json unmarshalling failed: %s \n %s", err.Error(), string(respBytes))
-		return nil, fmt.Errorf("json unmarshalling failed: %w for %s", err, string(respBytes))
-	}
-
-	if success, ok := res["success"]; ok && success == false {
-		return nil, fmt.Errorf("request failed: %s", string(respBytes))
+	err = json.Unmarshal(respBytes, &res)
+	if err != nil {
+		task.logger.Errorf("bad response, %s ,json expected:\n %s", err.Error(), string(respBytes))
+	} else if success, ok := res["success"]; ok && success == false {
+		err = errors.New(res["message"].(string))
 	}
-	return res, nil
+	return res, err
 }
diff --git a/pkg/metrics/observers/nginx_test.go b/pkg/metrics/observers/nginx_test.go
index 75df49c3..10724c0d 100644
--- a/pkg/metrics/observers/nginx_test.go
+++ b/pkg/metrics/observers/nginx_test.go
@@ -1,7 +1,6 @@
 package observers
 
 import (
-	"errors"
 	"net/http"
 	"net/http/httptest"
 	"testing"
@@ -15,61 +14,38 @@ import (
 )
 
 func TestNginxObserver_GetRequestSuccessRate(t *testing.T) {
-	t.Run("ok", func(t *testing.T) {
-		expected := ` sum( rate( nginx_ingress_controller_requests{ namespace="nginx", ingress="podinfo", status!~"5.*" }[1m] ) ) / sum( rate( nginx_ingress_controller_requests{ namespace="nginx", ingress="podinfo" }[1m] ) ) * 100`
-		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			promql := r.URL.Query()["query"][0]
-			assert.Equal(t, expected, promql)
-
-			json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1,"100"]}]}}`
-			w.Write([]byte(json))
-		}))
-		defer ts.Close()
-
-		client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
-			Type:      "prometheus",
-			Address:   ts.URL,
-			SecretRef: nil,
-		}, nil)
-		require.NoError(t, err)
-
-		observer := &NginxObserver{
-			client: client,
-		}
-
-		val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{
-			Name:      "podinfo",
-			Namespace: "nginx",
-			Target:    "podinfo",
-			Ingress:   "podinfo",
-			Interval:  "1m",
-		})
-		require.NoError(t, err)
-
-		assert.Equal(t, float64(100), val)
-	})
+	expected := ` sum( rate( nginx_ingress_controller_requests{ namespace="nginx", ingress="podinfo", status!~"5.*" }[1m] ) ) / sum( rate( nginx_ingress_controller_requests{ namespace="nginx", ingress="podinfo" }[1m] ) ) * 100`
+
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		promql := r.URL.Query()["query"][0]
+		assert.Equal(t, expected, promql)
+
+		json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1,"100"]}]}}`
+		w.Write([]byte(json))
+	}))
+	defer ts.Close()
+
+	client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
+		Type:      "prometheus",
+		Address:   ts.URL,
+		SecretRef: nil,
+	}, nil)
+	require.NoError(t, err)
+
+	observer := &NginxObserver{
+		client: client,
+	}
 
-	t.Run("no values", func(t *testing.T) {
-		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			json := `{"status":"success","data":{"resultType":"vector","result":[]}}`
-			w.Write([]byte(json))
-		}))
-		defer ts.Close()
-
-		client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{
-			Type:      "prometheus",
-			Address:   ts.URL,
-			SecretRef: nil,
-		}, nil)
-		require.NoError(t, err)
-
-		observer := &NginxObserver{
-			client: client,
-		}
-
-		_, err = observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{})
-		require.True(t, errors.Is(err, providers.ErrNoValuesFound))
+	val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{
+		Name:      "podinfo",
+		Namespace: "nginx",
+		Target:    "podinfo",
+		Ingress:   "podinfo",
+		Interval:  "1m",
 	})
+	require.NoError(t, err)
+
+	assert.Equal(t, float64(100), val)
 }
 
 func TestNginxObserver_GetRequestDuration(t *testing.T) {
diff --git a/pkg/metrics/providers/cloudwatch_test.go b/pkg/metrics/providers/cloudwatch_test.go
index dd5b3ae3..ed51b70d 100644
--- a/pkg/metrics/providers/cloudwatch_test.go
+++ b/pkg/metrics/providers/cloudwatch_test.go
@@ -1,8 +1,8 @@
 package providers
 
 import (
-	"errors"
 	"net/http"
+	"strings"
 	"testing"
 	"time"
 
@@ -146,13 +146,13 @@ func TestCloudWatchProvider_RunQuery(t *testing.T) {
 
 		_, err := p.RunQuery(query)
 		require.Error(t, err)
-		require.True(t, errors.Is(err, ErrNoValuesFound))
+		require.True(t, strings.Contains(err.Error(), "no values"))
 
 		p = CloudWatchProvider{client: cloudWatchClientMock{
 			o: &cloudwatch.GetMetricDataOutput{}}}
 
 		_, err = p.RunQuery(query)
 		require.Error(t, err)
-		require.True(t, errors.Is(err, ErrNoValuesFound))
+		require.True(t, strings.Contains(err.Error(), "no values"))
 	})
 }
diff --git a/pkg/metrics/providers/datadog_test.go b/pkg/metrics/providers/datadog_test.go
index 8dd4c120..d863a2a3 100644
--- a/pkg/metrics/providers/datadog_test.go
+++ b/pkg/metrics/providers/datadog_test.go
@@ -1,7 +1,6 @@
 package providers
 
 import (
-	"errors"
 	"fmt"
 	"net/http"
 	"net/http/httptest"
@@ -37,65 +36,45 @@ func TestNewDatadogProvider(t *testing.T) {
 }
 
 func TestDatadogProvider_RunQuery(t *testing.T) {
+	eq := `avg:system.cpu.user\{*}by{host}`
 	appKey := "app-key"
 	apiKey := "api-key"
-	t.Run("ok", func(t *testing.T) {
-		expected := 1.11111
-		eq := `avg:system.cpu.user{*}by{host}`
-		now := time.Now().Unix()
-		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			aq := r.URL.Query().Get("query")
-			assert.Equal(t, eq, aq)
-			assert.Equal(t, appKey, r.Header.Get(datadogApplicationKeyHeaderKey))
-			assert.Equal(t, apiKey, r.Header.Get(datadogAPIKeyHeaderKey))
-
-			from, err := strconv.ParseInt(r.URL.Query().Get("from"), 10, 64)
-			if assert.NoError(t, err) {
-				assert.Less(t, from, now)
-			}
-
-			to, err := strconv.ParseInt(r.URL.Query().Get("to"), 10, 64)
-			if assert.NoError(t, err) {
-				assert.GreaterOrEqual(t, to, now)
-			}
+	expected := 1.11111
+
+	now := time.Now().Unix()
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		aq := r.URL.Query().Get("query")
+		assert.Equal(t, eq, aq)
+		assert.Equal(t, appKey, r.Header.Get(datadogApplicationKeyHeaderKey))
+		assert.Equal(t, apiKey, r.Header.Get(datadogAPIKeyHeaderKey))
+
+		from, err := strconv.ParseInt(r.URL.Query().Get("from"), 10, 64)
+		if assert.NoError(t, err) {
+			assert.Less(t, from, now)
+		}
+
+		to, err := strconv.ParseInt(r.URL.Query().Get("to"), 10, 64)
+		if assert.NoError(t, err) {
+			assert.GreaterOrEqual(t, to, now)
+		}
+
+		json := fmt.Sprintf(`{"series": [{"pointlist": [[1577232000000,29325.102158814265],[1577318400000,56294.46758591842],[1577404800000,%f]]}]}`, expected)
+		w.Write([]byte(json))
+	}))
+	defer ts.Close()
+
+	dp, err := NewDatadogProvider("1m",
+		flaggerv1.MetricTemplateProvider{Address: ts.URL},
+		map[string][]byte{
+			datadogApplicationKeySecretKey: []byte(appKey),
+			datadogAPIKeySecretKey:         []byte(apiKey),
+		},
+	)
+	require.NoError(t, err)
 
-			json := fmt.Sprintf(`{"series": [{"pointlist": [[1577232000000,29325.102158814265],[1577318400000,56294.46758591842],[1577404800000,%f]]}]}`, expected)
-			w.Write([]byte(json))
-		}))
-		defer ts.Close()
-
-		dp, err := NewDatadogProvider("1m",
-			flaggerv1.MetricTemplateProvider{Address: ts.URL},
-			map[string][]byte{
-				datadogApplicationKeySecretKey: []byte(appKey),
-				datadogAPIKeySecretKey:         []byte(apiKey),
-			},
-		)
-		require.NoError(t, err)
-
-		f, err := dp.RunQuery(eq)
-		require.NoError(t, err)
-		assert.Equal(t, expected, f)
-	})
-
-	t.Run("no values", func(t *testing.T) {
-		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			json := fmt.Sprintf(`{"series": [{"pointlist": []}]}`)
-			w.Write([]byte(json))
-		}))
-		defer ts.Close()
-
-		dp, err := NewDatadogProvider("1m",
-			flaggerv1.MetricTemplateProvider{Address: ts.URL},
-			map[string][]byte{
-				datadogApplicationKeySecretKey: []byte(appKey),
-				datadogAPIKeySecretKey:         []byte(apiKey),
-			},
-		)
-		require.NoError(t, err)
-		_, err = dp.RunQuery("")
-		require.True(t, errors.Is(err, ErrNoValuesFound))
-	})
+	f, err := dp.RunQuery(eq)
+	require.NoError(t, err)
+	assert.Equal(t, expected, f)
 }
 
 func TestDatadogProvider_IsOnline(t *testing.T) {
diff --git a/pkg/metrics/providers/prometheus_test.go b/pkg/metrics/providers/prometheus_test.go
index 4aae8c9c..effe82f5 100644
--- a/pkg/metrics/providers/prometheus_test.go
+++ b/pkg/metrics/providers/prometheus_test.go
@@ -1,7 +1,6 @@
 package providers
 
 import (
-	"errors"
 	"net/http"
 	"net/http/httptest"
 	"strings"
@@ -83,66 +82,40 @@ func TestNewPrometheusProvider(t *testing.T) {
 }
 
 func TestPrometheusProvider_RunQueryWithBasicAuth(t *testing.T) {
-	t.Run("ok", func(t *testing.T) {
-		expected := `sum(envoy_cluster_upstream_rq)`
-		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			promql := r.URL.Query()["query"][0]
-			assert.Equal(t, expected, promql)
-
-			if assert.Contains(t, r.Header, "Authorization") {
-
-			}
-			header, ok := r.Header["Authorization"]
-			if assert.True(t, ok, "Authorization header not found") {
-				assert.True(t, strings.Contains(header[0], "Basic"), "Basic authorization header not found")
-			}
-
-			json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1545905245.458,"100"]}]}}`
-			w.Write([]byte(json))
-		}))
-		defer ts.Close()
-
-		clients := prometheusFake()
-
-		template, err := clients.flaggerClient.FlaggerV1beta1().MetricTemplates("default").Get("prometheus", metav1.GetOptions{})
-		require.NoError(t, err)
-		template.Spec.Provider.Address = ts.URL
-
-		secret, err := clients.kubeClient.CoreV1().Secrets("default").Get("prometheus", metav1.GetOptions{})
-		require.NoError(t, err)
+	expected := `sum(envoy_cluster_upstream_rq)`
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		promql := r.URL.Query()["query"][0]
+		assert.Equal(t, expected, promql)
 
-		prom, err := NewPrometheusProvider(template.Spec.Provider, secret.Data)
-		require.NoError(t, err)
+		if assert.Contains(t, r.Header, "Authorization") {
 
-		val, err := prom.RunQuery(template.Spec.Query)
-		require.NoError(t, err)
+		}
+		header, ok := r.Header["Authorization"]
+		if assert.True(t, ok, "Authorization header not found") {
+			assert.True(t, strings.Contains(header[0], "Basic"), "Basic authorization header not found")
+		}
 
-		assert.Equal(t, float64(100), val)
-	})
+		json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1545905245.458,"100"]}]}}`
+		w.Write([]byte(json))
+	}))
+	defer ts.Close()
 
-	t.Run("no values", func(t *testing.T) {
-		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			json := `{"status":"success","data":{"resultType":"vector","result":[]}}`
-			w.Write([]byte(json))
-		}))
-		defer ts.Close()
+	clients := prometheusFake()
 
-		clients := prometheusFake()
+	template, err := clients.flaggerClient.FlaggerV1beta1().MetricTemplates("default").Get("prometheus", metav1.GetOptions{})
+	require.NoError(t, err)
+	template.Spec.Provider.Address = ts.URL
 
-		template, err := clients.flaggerClient.FlaggerV1beta1().
-			MetricTemplates("default").Get("prometheus", metav1.GetOptions{})
-		require.NoError(t, err)
-		template.Spec.Provider.Address = ts.URL
+	secret, err := clients.kubeClient.CoreV1().Secrets("default").Get("prometheus", metav1.GetOptions{})
+	require.NoError(t, err)
 
-		secret, err := clients.kubeClient.CoreV1().Secrets("default").Get("prometheus", metav1.GetOptions{})
-		require.NoError(t, err)
+	prom, err := NewPrometheusProvider(template.Spec.Provider, secret.Data)
+	require.NoError(t, err)
 
-		prom, err := NewPrometheusProvider(template.Spec.Provider, secret.Data)
-		require.NoError(t, err)
+	val, err := prom.RunQuery(template.Spec.Query)
+	require.NoError(t, err)
 
-		_, err = prom.RunQuery(template.Spec.Query)
-		require.True(t, errors.Is(err, ErrNoValuesFound))
-	})
+	assert.Equal(t, float64(100), val)
 }
 
 func TestPrometheusProvider_IsOnline(t *testing.T) {
diff --git a/pkg/router/contour_test.go b/pkg/router/contour_test.go
index 2c0c6c6b..064073c1 100644
--- a/pkg/router/contour_test.go
+++ b/pkg/router/contour_test.go
@@ -57,8 +57,8 @@ func TestContourRouter_Reconcile(t *testing.T) {
 	require.NoError(t, err)
 
 	cdClone = cd.DeepCopy()
-	cdClone.Spec.Analysis.Iterations = 5
-	cdClone.Spec.Analysis.Match = newTestABTest().Spec.Analysis.Match
+	cdClone.Spec.CanaryAnalysis.Iterations = 5
+	cdClone.Spec.CanaryAnalysis.Match = newTestABTest().Spec.CanaryAnalysis.Match
 	canary, err = mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Update(cdClone)
 	require.NoError(t, err)
 
@@ -104,8 +104,8 @@ func TestContourRouter_Routes(t *testing.T) {
 
 	// test update to A/B
 	cdClone := cd.DeepCopy()
-	cdClone.Spec.Analysis.Iterations = 5
-	cdClone.Spec.Analysis.Match = newTestABTest().Spec.Analysis.Match
+	cdClone.Spec.CanaryAnalysis.Iterations = 5
+	cdClone.Spec.CanaryAnalysis.Match = newTestABTest().Spec.CanaryAnalysis.Match
 	canary, err := mocks.flaggerClient.FlaggerV1beta1().Canaries("default").Update(cdClone)
 	require.NoError(t, err)
 
diff --git a/pkg/router/istio_test.go b/pkg/router/istio_test.go
index 0b9c3591..e48967f0 100644
--- a/pkg/router/istio_test.go
+++ b/pkg/router/istio_test.go
@@ -91,78 +91,69 @@ func TestIstioRouter_SetRoutes(t *testing.T) {
 	err := router.Reconcile(mocks.canary)
 	require.NoError(t, err)
 
-	pHost := fmt.Sprintf("%s-primary", mocks.canary.Spec.TargetRef.Name)
-	cHost := fmt.Sprintf("%s-canary", mocks.canary.Spec.TargetRef.Name)
+	p, c, m, err := router.GetRoutes(mocks.canary)
+	require.NoError(t, err)
 
-	t.Run("normal", func(t *testing.T) {
-		p, c := 60, 40
-		err := router.SetRoutes(mocks.canary, p, c, false)
-		require.NoError(t, err)
+	p = 60
+	c = 40
+	m = false
 
-		vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
-		require.NoError(t, err)
+	err = router.SetRoutes(mocks.canary, p, c, m)
+	require.NoError(t, err)
 
-		var pRoute, cRoute istiov1alpha3.DestinationWeight
-		var mirror *istiov1alpha3.Destination
-		for _, http := range vs.Spec.Http {
-			for _, route := range http.Route {
-				if route.Destination.Host == pHost {
-					pRoute = route
-				}
-				if route.Destination.Host == cHost {
-					cRoute = route
-					mirror = http.Mirror
-				}
+	vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
+	require.NoError(t, err)
+
+	pHost := fmt.Sprintf("%s-primary", mocks.canary.Spec.TargetRef.Name)
+	cHost := fmt.Sprintf("%s-canary", mocks.canary.Spec.TargetRef.Name)
+	pRoute := istiov1alpha3.DestinationWeight{}
+	cRoute := istiov1alpha3.DestinationWeight{}
+	var mirror *istiov1alpha3.Destination
+
+	for _, http := range vs.Spec.Http {
+		for _, route := range http.Route {
+			if route.Destination.Host == pHost {
+				pRoute = route
+			}
+			if route.Destination.Host == cHost {
+				cRoute = route
+				mirror = http.Mirror
 			}
 		}
+	}
 
-		assert.Equal(t, p, pRoute.Weight)
-		assert.Equal(t, c, cRoute.Weight)
-		assert.Nil(t, mirror)
-
-	})
-
-	t.Run("mirror", func(t *testing.T) {
-		for _, w := range []int{0, 10, 50} {
-			p, c := 100, 0
-
-			// set mirror weight
-			mocks.canary.Spec.Analysis.MirrorWeight = w
-			err := router.SetRoutes(mocks.canary, p, c, true)
-			require.NoError(t, err)
-
-			vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
-			require.NoError(t, err)
-
-			var pRoute, cRoute istiov1alpha3.DestinationWeight
-			var mirror *istiov1alpha3.Destination
-			var mirrorWeight *istiov1alpha3.Percent
-			for _, http := range vs.Spec.Http {
-				for _, route := range http.Route {
-					if route.Destination.Host == pHost {
-						pRoute = route
-					}
-					if route.Destination.Host == cHost {
-						cRoute = route
-						mirror = http.Mirror
-						mirrorWeight = http.MirrorPercentage
-					}
-				}
-			}
+	assert.Equal(t, p, pRoute.Weight)
+	assert.Equal(t, c, cRoute.Weight)
+	assert.Nil(t, mirror)
 
-			assert.Equal(t, p, pRoute.Weight)
-			assert.Equal(t, c, cRoute.Weight)
-			if assert.NotNil(t, mirror) {
-				assert.Equal(t, cHost, mirror.Host)
-			}
+	mirror = nil
+	p = 100
+	c = 0
+	m = true
 
-			if w > 0 && assert.NotNil(t, mirrorWeight) {
-				assert.Equal(t, w, int(mirrorWeight.Value))
-			} else {
-				assert.Nil(t, mirrorWeight)
+	err = router.SetRoutes(mocks.canary, p, c, m)
+	require.NoError(t, err)
+
+	vs, err = mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get("podinfo", metav1.GetOptions{})
+	require.NoError(t, err)
+
+	for _, http := range vs.Spec.Http {
+		for _, route := range http.Route {
+			if route.Destination.Host == pHost {
+				pRoute = route
+			}
+			if route.Destination.Host == cHost {
+				cRoute = route
+				mirror = http.Mirror
 			}
 		}
-	})
+	}
+
+	assert.Equal(t, p, pRoute.Weight)
+	assert.Equal(t, c, cRoute.Weight)
+	if assert.NotNil(t, mirror) {
+		assert.Equal(t, cHost, mirror.Host)
+	}
 }
 
 func TestIstioRouter_GetRoutes(t *testing.T) {
diff --git a/pkg/router/kubernetes_default_test.go b/pkg/router/kubernetes_deployment_test.go
similarity index 96%
rename from pkg/router/kubernetes_default_test.go
rename to pkg/router/kubernetes_deployment_test.go
index 89002885..faf0b516 100644
--- a/pkg/router/kubernetes_default_test.go
+++ b/pkg/router/kubernetes_deployment_test.go
@@ -11,7 +11,7 @@ import (
 
 func TestServiceRouter_Create(t *testing.T) {
 	mocks := newFixture(nil)
-	router := &KubernetesDefaultRouter{
+	router := &KubernetesDeploymentRouter{
 		kubeClient:    mocks.kubeClient,
 		flaggerClient: mocks.flaggerClient,
 		logger:        mocks.logger,
@@ -37,7 +37,7 @@ func TestServiceRouter_Create(t *testing.T) {
 
 func TestServiceRouter_Update(t *testing.T) {
 	mocks := newFixture(nil)
-	router := &KubernetesDefaultRouter{
+	router := &KubernetesDeploymentRouter{
 		kubeClient:    mocks.kubeClient,
 		flaggerClient: mocks.flaggerClient,
 		logger:        mocks.logger,
@@ -71,7 +71,7 @@ func TestServiceRouter_Update(t *testing.T) {
 
 func TestServiceRouter_Undo(t *testing.T) {
 	mocks := newFixture(nil)
-	router := &KubernetesDefaultRouter{
+	router := &KubernetesDeploymentRouter{
 		kubeClient:    mocks.kubeClient,
 		flaggerClient: mocks.flaggerClient,
 		logger:        mocks.logger,
diff --git a/pkg/router/router_test.go b/pkg/router/router_test.go
index f2c07a48..205a8ab3 100644
--- a/pkg/router/router_test.go
+++ b/pkg/router/router_test.go
@@ -112,7 +112,7 @@ func newTestCanary() *flaggerv1.Canary {
 					"public-gateway.istio",
 					"mesh",
 				},
-			}, Analysis: &flaggerv1.CanaryAnalysis{
+			}, CanaryAnalysis: &flaggerv1.CanaryAnalysis{
 				Threshold:  10,
 				StepWeight: 10,
 				MaxWeight:  50,
@@ -158,7 +158,7 @@ func newTestCanaryAppMesh() *flaggerv1.Canary {
 					PerTryTimeout: "gateway-error",
 					RetryOn:       "5s",
 				},
-			}, Analysis: &flaggerv1.CanaryAnalysis{
+			}, CanaryAnalysis: &flaggerv1.CanaryAnalysis{
 				Threshold:  10,
 				StepWeight: 10,
 				MaxWeight:  50,
@@ -203,7 +203,7 @@ func newTestSMICanary() *flaggerv1.Canary {
 				},
 				PortDiscovery: true,
 			},
-			Analysis: &flaggerv1.CanaryAnalysis{
+			CanaryAnalysis: &flaggerv1.CanaryAnalysis{
 				Threshold:  10,
 				StepWeight: 10,
 				MaxWeight:  50,
@@ -247,7 +247,7 @@ func newTestABTest() *flaggerv1.Canary {
 			Service: flaggerv1.CanaryService{
 				Port:     9898,
 				MeshName: "global",
-			}, Analysis: &flaggerv1.CanaryAnalysis{
+			}, CanaryAnalysis: &flaggerv1.CanaryAnalysis{
 				Threshold:  10,
 				Iterations: 2,
 				Match: []istiov1alpha3.HTTPMatchRequest{
@@ -397,7 +397,7 @@ func newTestCanaryIngress() *flaggerv1.Canary {
 			},
 			Service: flaggerv1.CanaryService{
 				Port: 9898,
-			}, Analysis: &flaggerv1.CanaryAnalysis{
+			}, CanaryAnalysis: &flaggerv1.CanaryAnalysis{
 				Threshold:  10,
 				StepWeight: 10,
 				MaxWeight:  50,
diff --git a/test/e2e-istio.sh b/test/e2e-istio.sh
index 576b200b..1cb04e95 100755
--- a/test/e2e-istio.sh
+++ b/test/e2e-istio.sh
@@ -2,17 +2,23 @@
 
 set -o errexit
 
-ISTIO_VER="1.5.0"
+ISTIO_VER="1.4.5"
 REPO_ROOT=$(git rev-parse --show-toplevel)
 
-echo ">>> Downloading Istio ${ISTIO_VER}"
-cd ${REPO_ROOT}/bin && \
-curl -L https://istio.io/downloadIstio | ISTIO_VERSION=${ISTIO_VER} sh -
-
 echo ">>> Installing Istio ${ISTIO_VER}"
-${REPO_ROOT}/bin/istio-${ISTIO_VER}/bin/istioctl manifest apply --set profile=default
+kubectl create ns istio-system
+helm repo add istio.io https://storage.googleapis.com/istio-release/releases/${ISTIO_VER}/charts
+
+echo '>>> Installing Istio CRDs'
+helm upgrade -i istio-init istio.io/istio-init --wait --namespace istio-system
+
+echo '>>> Waiting for Istio CRDs to be ready'
+kubectl -n istio-system wait --for=condition=complete job/istio-init-crd-10-${ISTIO_VER}
+kubectl -n istio-system wait --for=condition=complete job/istio-init-crd-11-${ISTIO_VER}
+kubectl -n istio-system wait --for=condition=complete job/istio-init-crd-14-${ISTIO_VER}
 
-kubectl -n istio-system rollout status deployment/prometheus
+echo '>>> Installing Istio control plane'
+helm upgrade -i istio istio.io/istio --wait --namespace istio-system -f ${REPO_ROOT}/test/e2e-istio-values.yaml
 
 kubectl -n istio-system get all
 
diff --git a/test/e2e-kubernetes-cleanup.sh b/test/e2e-kubernetes-cleanup.sh
deleted file mode 100755
index b289ac72..00000000
--- a/test/e2e-kubernetes-cleanup.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env bash
-
-set -o errexit
-
-echo '>>> Delete test namespace'
-kubectl delete namespace test --ignore-not-found=true --wait=true
diff --git a/test/e2e-nginx-custom-annotations.sh b/test/e2e-nginx-custom-annotations.sh
index 3853a081..3ae36f6c 100755
--- a/test/e2e-nginx-custom-annotations.sh
+++ b/test/e2e-nginx-custom-annotations.sh
@@ -3,10 +3,10 @@
 set -o errexit
 
 REPO_ROOT=$(git rev-parse --show-toplevel)
-NGINX_HELM_VERSION=1.33.4 # ingress v0.30.0
+NGINX_VERSION=1.24.4
 
 echo '>>> Installing NGINX Ingress'
-helm upgrade -i nginx-ingress stable/nginx-ingress --version=${NGINX_HELM_VERSION} \
+helm upgrade -i nginx-ingress stable/nginx-ingress --version=${NGINX_VERSION} \
 --wait \
 --namespace ingress-nginx \
 --set controller.stats.enabled=true \
diff --git a/test/e2e-nginx.sh b/test/e2e-nginx.sh
index aaf696f9..5c33c194 100755
--- a/test/e2e-nginx.sh
+++ b/test/e2e-nginx.sh
@@ -3,11 +3,11 @@
 set -o errexit
 
 REPO_ROOT=$(git rev-parse --show-toplevel)
-NGINX_HELM_VERSION=1.33.4 # ingress v0.30.0
+NGINX_VERSION=1.33.0
 
 echo '>>> Installing NGINX Ingress'
 kubectl create ns ingress-nginx
-helm upgrade -i nginx-ingress stable/nginx-ingress --version=${NGINX_HELM_VERSION} \
+helm upgrade -i nginx-ingress stable/nginx-ingress --version=${NGINX_VERSION} \
 --wait \
 --namespace ingress-nginx \
 --set controller.stats.enabled=true \
diff --git a/test/e2e-istio-tests.sh b/test/e2e-tests.sh
similarity index 85%
rename from test/e2e-istio-tests.sh
rename to test/e2e-tests.sh
index 31600601..a19ef5ed 100755
--- a/test/e2e-istio-tests.sh
+++ b/test/e2e-tests.sh
@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
 # This script runs e2e tests for Canary, B/G and A/B initialization, analysis and promotion
-# Prerequisites: Kubernetes Kind and Istio
+# Prerequisites: Kubernetes Kind, Helm and Istio
 
 set -o errexit
 
@@ -15,36 +15,9 @@ echo '>>> Installing the load tester'
 kubectl apply -k ${REPO_ROOT}/kustomize/tester
 kubectl -n test rollout status deployment/flagger-loadtester
 
-echo '>>> Deploy podinfo'
+echo '>>> Initialising canary'
 kubectl apply -f ${REPO_ROOT}/test/e2e-workload.yaml
 
-echo '>>> Create latency metric template'
-cat <<EOF | kubectl apply -f -
-apiVersion: flagger.app/v1beta1
-kind: MetricTemplate
-metadata:
-  name: latency
-  namespace: istio-system
-spec:
-  provider:
-    type: prometheus
-    address: http://prometheus.istio-system:9090
-  query: |
-    histogram_quantile(
-        0.99,
-        sum(
-            rate(
-                istio_request_duration_milliseconds_bucket{
-                    reporter="destination",
-                    destination_workload_namespace="{{ namespace }}",
-                    destination_workload=~"{{ target }}"
-                }[{{ interval }}]
-            )
-        ) by (le)
-    )
-EOF
-
-echo '>>> Initialising canary'
 cat <<EOF | kubectl apply -f -
 apiVersion: flagger.app/v1beta1
 kind: Canary
@@ -73,16 +46,35 @@ spec:
     stepWeight: 10
     metrics:
     - name: request-success-rate
-      thresholdRange:
-        min: 99
+      threshold: 99
       interval: 1m
-    - name: latency
-      templateRef:
-        name: latency
-        namespace: istio-system
-      thresholdRange:
-        max: 500
+    - name: request-duration
+      threshold: 500
+      interval: 30s
+    - name: "404s percentage"
+      threshold: 5
       interval: 1m
+      query: |
+        100 - sum(
+            rate(
+                istio_requests_total{
+                  reporter="destination",
+                  destination_workload_namespace=~"test",
+                  destination_workload=~"podinfo",
+                  response_code!="404"
+                }[1m]
+            )
+        )
+        /
+        sum(
+            rate(
+                istio_requests_total{
+                  reporter="destination",
+                  destination_workload_namespace=~"test",
+                  destination_workload=~"podinfo"
+                }[1m]
+            )
+        ) * 100
     webhooks:
       - name: load-test
         url: http://flagger-loadtester.test/
@@ -175,15 +167,10 @@ spec:
     iterations: 5
     metrics:
     - name: request-success-rate
-      thresholdRange:
-        min: 99
+      threshold: 99
       interval: 1m
-    - name: latency
-      templateRef:
-        name: latency
-        namespace: istio-system
-      thresholdRange:
-        max: 500
+    - name: request-duration
+      threshold: 500
       interval: 30s
     webhooks:
       - name: http-acceptance-test
@@ -273,15 +260,10 @@ spec:
             regex: "^(.*?;)?(type=insider)(;.*)?$"
     metrics:
     - name: request-success-rate
-      thresholdRange:
-        min: 99
+      threshold: 99
       interval: 1m
-    - name: latency
-      templateRef:
-        name: latency
-        namespace: istio-system
-      thresholdRange:
-        max: 500
+    - name: request-duration
+      threshold: 500
       interval: 30s
     webhooks:
       - name: pre