Repository: knative/serving
Pull request #7897 (2020-05-08 23:02:30)

Implement Feature Flags
<!--
Request Prow to automatically lint any go code in this PR:

/lint
-->
See #6857.

Adds a `config-features` ConfigMap to control which features (extensions of the Knative API) are enabled. 

It is reusing the logic in place to inject settings to the context passed to the API validation methods. This way, adding a new feature flags simply involves:
1) Adding an entry to the `Features` struct
2) Using it in the API validation

@markusthoemmes I moved the Multi Container flag in the new ConfigMap. It is a breaking change.
Is this something I need to handle or since it is a recent feature maybe it is not widely used?

/cc @mattmoor @dprotaso 

Context: [design doc](https://docs.google.com/document/d/15BFfLXv8wNAAdDnEqKfKt5vOEDAfbGLLSWkZvmSwnG0)

## Next
Add a page about features in the docs.

Should the features be namespaced (e.g. k8s.io/node-selector)?

Should we remove the `enabled-` prefix? It is cleaner.


knative-prow-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/serving/pull/7897#" title="Author self-approved">JRBANCEL</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **mattmoor**
You can assign the PR to them by writing `/assign @mattmoor` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fserving).

<details open>
Needs approval from an approver in each of these files:

- **[config/OWNERS](https://github.com/knative/serving/blob/master/config/OWNERS)**
- **[pkg/apis/OWNERS](https://github.com/knative/serving/blob/master/pkg/apis/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["mattmoor"]} -->
knative-prow-robot: @JRBANCEL: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-serving-unit-tests | 4ec56c564e6c5b08c5683117091853475b33fda6 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-unit-tests/1258895236361359361) | `/test pull-knative-serving-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_serving/7897). [Your PR dashboard](https://gubernator.knative.dev/pr/JRBANCEL).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-prow-robot: @JRBANCEL: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-serving-unit-tests | ae07d67c3880821becb5258daff904e4f78abca8 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-unit-tests/1258917885762342912) | `/test pull-knative-serving-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_serving/7897). [Your PR dashboard](https://gubernator.knative.dev/pr/JRBANCEL).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
vagababov: Multicontainer is not yet launched, so in theory migrating to a different file should be alright, I think.
JRBANCEL: /assign @mattmoor @markusthoemmes 
vagababov: Tests fail 😊 
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-serving-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/apis/config/defaults.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1259953710381928450/artifacts/line-cov.html#file1) | 97.1% | 97.1% | -0.1
[pkg/apis/config/features.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1259953710381928450/artifacts/line-cov.html#file2) | Do not exist | 100.0% | 
[pkg/apis/serving/v1/revision_validation.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1259953710381928450/artifacts/line-cov.html#file7) | 94.4% | 94.6% | 0.2
[pkg/apis/serving/v1alpha1/revision_validation.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1259953710381928450/artifacts/line-cov.html#file9) | 96.1% | 95.6% | -0.5

knative-prow-robot: @JRBANCEL: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-serving-unit-tests | ae07d67c3880821becb5258daff904e4f78abca8 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-unit-tests/1258917885762342912) | `/test pull-knative-serving-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_serving/7897). [Your PR dashboard](https://gubernator.knative.dev/pr/JRBANCEL).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-prow-robot: @JRBANCEL: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-serving-unit-tests | 4ec56c564e6c5b08c5683117091853475b33fda6 | [link](https://prow.knative.dev/view/gcs/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-unit-tests/1258895236361359361) | `/test pull-knative-serving-unit-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_serving/7897). [Your PR dashboard](https://gubernator.knative.dev/pr/JRBANCEL).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-serving-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/apis/config/defaults.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1260628452956966913/artifacts/line-cov.html#file1) | 97.1% | 97.1% | -0.1
[pkg/apis/config/features.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1260628452956966913/artifacts/line-cov.html#file2) | Do not exist | 100.0% | 
[pkg/apis/serving/v1/revision_validation.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1260628452956966913/artifacts/line-cov.html#file7) | 94.4% | 94.6% | 0.2
[pkg/apis/serving/v1alpha1/revision_validation.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1260628452956966913/artifacts/line-cov.html#file9) | 96.1% | 95.6% | -0.5

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-serving-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/apis/config/defaults.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1260652361123106818/artifacts/line-cov.html#file1) | 97.1% | 97.1% | -0.1
[pkg/apis/config/features.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1260652361123106818/artifacts/line-cov.html#file2) | Do not exist | 100.0% | 
[pkg/apis/serving/v1/revision_validation.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1260652361123106818/artifacts/line-cov.html#file7) | 94.4% | 94.6% | 0.2
[pkg/apis/serving/v1alpha1/revision_validation.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1260652361123106818/artifacts/line-cov.html#file9) | 96.1% | 95.6% | -0.5

JRBANCEL: /ping @mattmoor @markusthoemmes 
knative-prow-robot: New changes are detected. LGTM label has been removed.
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-serving-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/apis/config/defaults.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1261058663250923520/artifacts/line-cov.html#file1) | 97.1% | 97.3% | 0.2
[pkg/apis/config/features.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1261058663250923520/artifacts/line-cov.html#file2) | Do not exist | 100.0% | 
[pkg/apis/serving/v1/revision_validation.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1261058663250923520/artifacts/line-cov.html#file7) | 94.4% | 94.6% | 0.2
[pkg/apis/serving/v1alpha1/revision_validation.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1261058663250923520/artifacts/line-cov.html#file9) | 96.1% | 95.6% | -0.5

mattmoor: cc @savitaashture @markusthoemmes because this changes the multi-container work.
knative-prow-robot: @JRBANCEL: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
dprotaso: Another though here should we be using something else besides 'true/false'

ie. I could foresee

1) Enabled by operator and on by default
1) Enabled by operator and off by default (opt-in via. annotation?)
1) Disabled by operator

JRBANCEL: > Another though here should we be using something else besides 'true/false'
> 
> ie. I could foresee
> 
> 1. Enabled by operator and on by default
> 2. Enabled by operator and off by default (opt-in via. annotation?)
> 3. Disabled by operator

Should we use something like?
1. Enabled
2. Allowed
3. Disabled

Maybe formalize this in another PR to scope this down.
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-serving-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/apis/config/defaults.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1262509077947224067/artifacts/line-cov.html#file1) | 97.1% | 96.6% | -0.6
[pkg/apis/config/features.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1262509077947224067/artifacts/line-cov.html#file2) | Do not exist | 83.3% | 
[pkg/apis/serving/v1/revision_validation.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1262509077947224067/artifacts/line-cov.html#file7) | 94.4% | 94.6% | 0.2
[pkg/apis/serving/v1alpha1/revision_validation.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/7897/pull-knative-serving-go-coverage/1262509077947224067/artifacts/line-cov.html#file9) | 96.1% | 95.6% | -0.5


Patch:
diff --git a/OWNERS b/OWNERS
index e90244b19..ef04db24b 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,7 +1,4 @@
 # The OWNERS file is used by prow to automatically merge approved PRs.
 
 approvers:
-- evankanderson
-- mattmoor
-- mdemirhan
-- vaikas
+- serving-wg-leads
diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES
index 7a301f00b..662e1c14d 100644
--- a/OWNERS_ALIASES
+++ b/OWNERS_ALIASES
@@ -1,29 +1,42 @@
 aliases:
   serving-api-approvers:
-  - dgerd
   - dprotaso
   - markusthoemmes
   - mattmoor
   - tcnghia
   - vagababov
   serving-api-reviewers:
-  - dgerd
   - dprotaso
   - markusthoemmes
   - mattmoor
   - tcnghia
   - vagababov
 
+  toc:
+  - evankanderson
+  - grantr
+  - markusthoemmes
+  - mattmoor
+  - tcnghia
+
+  wg-leads:
+  - dprotaso        # API
+  - markusthoemmes  # Scaling
+  - mattmoor        # API
+  - mdemirhan       # Network
+  - tcnghia         # Network
+  - vagababov       # Scaling
+
   # TOC + WG leads
   serving-wg-leads:
-  - evankanderson  # TOC
-  - mattmoor       # TOC/Serving
-  - vaikas         # TOC
-  - dprotaso       # Serving
-  - markusthoemmes # Scaling
-  - mdemirhan      # Networking
-  - tcnghia        # Networking
-  - vagababov      # Scaling
+  - dprotaso        # API
+  - evankanderson   # TOC
+  - grantr          # TOC
+  - markusthoemmes  # Scaling/TOC
+  - mattmoor        # API/TOC
+  - mdemirhan       # Network
+  - tcnghia         # Network/TOC
+  - vagababov       # Scaling
 
   autoscaling-approvers:
   - markusthoemmes
diff --git a/cmd/autoscaler/main.go b/cmd/autoscaler/main.go
index 039e1c1f3..2f2ac4e8c 100644
--- a/cmd/autoscaler/main.go
+++ b/cmd/autoscaler/main.go
@@ -225,11 +225,17 @@ func uniScalerFactoryFunc(endpointsInformer corev1informers.EndpointsInformer,
 func statsScraperFactoryFunc(endpointsLister corev1listers.EndpointsLister,
 	podLister corev1listers.PodLister) asmetrics.StatsScraperFactory {
 	return func(metric *av1alpha1.Metric, logger *zap.SugaredLogger) (asmetrics.StatsScraper, error) {
-		podCounter := resources.NewScopedEndpointsCounter(
-			endpointsLister, metric.Namespace, metric.Spec.ScrapeTarget)
-		// TODO(vagababov): while metric name == revision name, we should utilize the proper
-		// values from the labels.
-		podAccessor := resources.NewPodAccessor(podLister, metric.Namespace, metric.Name)
+		if metric.Spec.ScrapeTarget == "" {
+			return nil, nil
+		}
+
+		revisionName := metric.Labels[serving.RevisionLabelKey]
+		if revisionName == "" {
+			return nil, fmt.Errorf("label %q not found or empty in Metric %s", serving.RevisionLabelKey, metric.Name)
+		}
+
+		podCounter := resources.NewScopedEndpointsCounter(endpointsLister, metric.Namespace, metric.Spec.ScrapeTarget)
+		podAccessor := resources.NewPodAccessor(podLister, metric.Namespace, revisionName)
 		return asmetrics.NewStatsScraper(metric, podCounter, podAccessor, logger)
 	}
 }
diff --git a/cmd/queue/main.go b/cmd/queue/main.go
index e533e4c66..95ef861fc 100644
--- a/cmd/queue/main.go
+++ b/cmd/queue/main.go
@@ -355,7 +355,7 @@ func main() {
 	reportTicker := time.NewTicker(reportingPeriod)
 	defer reportTicker.Stop()
 
-	queue.NewStats(time.Now(), reqChan, reportTicker.C, promStatReporter.Report)
+	go queue.ReportStats(time.Now(), reqChan, reportTicker.C, promStatReporter.Report)
 
 	// Setup probe to run for checking user-application healthiness.
 	probe := buildProbe(env.ServingReadinessProbe)
diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go
index fdf3a6007..1c59a288f 100644
--- a/cmd/webhook/main.go
+++ b/cmd/webhook/main.go
@@ -225,10 +225,11 @@ func NewConversionController(ctx context.Context, cmw configmap.Watcher) *contro
 }
 
 func main() {
+
 	// Set up a signal context with our webhook options
 	ctx := webhook.WithOptions(signals.NewContext(), webhook.Options{
 		ServiceName: "webhook",
-		Port:        8443,
+		Port:        webhook.PortFromEnv(8443),
 		SecretName:  "webhook-certs",
 	})
 
diff --git a/config/core/configmaps/features.yaml b/config/core/configmaps/features.yaml
new file mode 100644
index 000000000..6336e627b
--- /dev/null
+++ b/config/core/configmaps/features.yaml
@@ -0,0 +1,41 @@
+# Copyright 2020 The Knative Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: config-features
+  namespace: knative-serving
+  labels:
+    serving.knative.dev/release: devel
+
+data:
+  _example: |
+    ################################
+    #                              #
+    #    EXAMPLE CONFIGURATION     #
+    #                              #
+    ################################
+
+    # This block is not actually functional configuration,
+    # but serves to illustrate the available configuration
+    # options and document them in a way that is accessible
+    # to users that `kubectl edit` this config map.
+    #
+    # These sample configuration options may be copied out of
+    # this example block and unindented to be in the data block
+    # to actually change the configuration.
+
+    # Indicates whether multi container support is enabled
+    multi-container: "disabled"
diff --git a/config/core/deployments/webhook.yaml b/config/core/deployments/webhook.yaml
index 663ab019e..eb361b147 100644
--- a/config/core/deployments/webhook.yaml
+++ b/config/core/deployments/webhook.yaml
@@ -42,11 +42,11 @@ spec:
 
         resources:
           requests:
-            cpu: 20m
-            memory: 20Mi
+            cpu: 100m
+            memory: 100Mi
           limits:
-            cpu: 200m
-            memory: 200Mi
+            cpu: 500m
+            memory: 500Mi
 
         env:
         - name: SYSTEM_NAMESPACE
@@ -57,6 +57,8 @@ spec:
           value: config-logging
         - name: CONFIG_OBSERVABILITY_NAME
           value: config-observability
+        - name: WEBHOOK_PORT
+          value: "8443"
 
         # TODO(https://github.com/knative/pkg/pull/953): Remove stackdriver specific config
         - name: METRICS_DOMAIN
diff --git a/config/core/webhooks/configmap-validation.yaml b/config/core/webhooks/configmap-validation.yaml
index 5da98fa56..18f9461f4 100644
--- a/config/core/webhooks/configmap-validation.yaml
+++ b/config/core/webhooks/configmap-validation.yaml
@@ -32,4 +32,4 @@ webhooks:
     matchExpressions:
     - key: serving.knative.dev/release
       operator: Exists
-  timeoutSeconds: 2
+  timeoutSeconds: 10
diff --git a/config/core/webhooks/defaulting.yaml b/config/core/webhooks/defaulting.yaml
index a29a0fa9d..18b66f43f 100644
--- a/config/core/webhooks/defaulting.yaml
+++ b/config/core/webhooks/defaulting.yaml
@@ -28,4 +28,4 @@ webhooks:
   failurePolicy: Fail
   sideEffects: None
   name: webhook.serving.knative.dev
-  timeoutSeconds: 2
+  timeoutSeconds: 10
diff --git a/config/core/webhooks/resource-validation.yaml b/config/core/webhooks/resource-validation.yaml
index 55001151d..2e7c9af8b 100644
--- a/config/core/webhooks/resource-validation.yaml
+++ b/config/core/webhooks/resource-validation.yaml
@@ -28,4 +28,4 @@ webhooks:
   failurePolicy: Fail
   sideEffects: None
   name: validation.webhook.serving.knative.dev
-  timeoutSeconds: 2
+  timeoutSeconds: 10
diff --git a/go.mod b/go.mod
index 470e81741..aab2a0681 100644
--- a/go.mod
+++ b/go.mod
@@ -43,9 +43,9 @@ require (
 	k8s.io/code-generator v0.18.0
 	k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
 	k8s.io/metrics v0.17.2
-	knative.dev/caching v0.0.0-20200515173804-a2fc9c5de2ef
-	knative.dev/pkg v0.0.0-20200517014302-3c29a1ce8906
-	knative.dev/test-infra v0.0.0-20200515184601-7a28f47cdbcb
+	knative.dev/caching v0.0.0-20200521155757-e78d17bc250e
+	knative.dev/pkg v0.0.0-20200519155757-14eb3ae3a5a7
+	knative.dev/test-infra v0.0.0-20200519161858-554a95a37986
 )
 
 replace (
diff --git a/go.sum b/go.sum
index bb516d99b..42ce87121 100644
--- a/go.sum
+++ b/go.sum
@@ -1430,26 +1430,25 @@ k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl
 k8s.io/utils v0.0.0-20200124190032-861946025e34 h1:HjlUD6M0K3P8nRXmr2B9o4F9dUy9TCj/aEpReeyi6+k=
 k8s.io/utils v0.0.0-20200124190032-861946025e34/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 knative.dev/caching v0.0.0-20190719140829-2032732871ff/go.mod h1:dHXFU6CGlLlbzaWc32g80cR92iuBSpsslDNBWI8C7eg=
-knative.dev/caching v0.0.0-20200515173804-a2fc9c5de2ef h1:rX5pdlCet244RNg1SNbrO137EyAn7lMvuFu25R4YJfI=
-knative.dev/caching v0.0.0-20200515173804-a2fc9c5de2ef/go.mod h1:n5kQRfe9itUy2wpuik7d1CW6sXyPw0OJhs9xlmwB0H4=
+knative.dev/caching v0.0.0-20200521155757-e78d17bc250e h1:LN2scNClE8G4a9XzM0AGZkK/rT56A0ZlweaMjNzPMD0=
+knative.dev/caching v0.0.0-20200521155757-e78d17bc250e/go.mod h1:bsKPIG2XZFYL8uw5BAxg7EMBLp0vNpu9jZIGus8HUPU=
 knative.dev/eventing-contrib v0.6.1-0.20190723221543-5ce18048c08b/go.mod h1:SnXZgSGgMSMLNFTwTnpaOH7hXDzTFtw0J8OmHflNx3g=
 knative.dev/pkg v0.0.0-20191101194912-56c2594e4f11/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q=
 knative.dev/pkg v0.0.0-20191111150521-6d806b998379/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q=
 knative.dev/pkg v0.0.0-20200428194351-90fc61bae7f7/go.mod h1:o+e8OVEJKIuvXPsGVPIautjXgs05xbos7G+QMRjuUps=
 knative.dev/pkg v0.0.0-20200505191044-3da93ebb24c2 h1:Qu2NlOHb9p3g+CSL/ok9+FySowN60URFEKRSXfWtDv4=
 knative.dev/pkg v0.0.0-20200505191044-3da93ebb24c2/go.mod h1:Q6sL35DdGs8hIQZKdaCXJGgY8f90BmNBKSb8z6d/BTM=
-knative.dev/pkg v0.0.0-20200514052058-c75d324f8b8b h1:lM0uwBgNcS9S4cWLqIS6Fvjt3kR/6oAPXgbXfEzBJwU=
-knative.dev/pkg v0.0.0-20200514052058-c75d324f8b8b/go.mod h1:tMOHGbxtRz8zYFGEGpV/bpoTEM1o89MwYFC4YJXl3GY=
 knative.dev/pkg v0.0.0-20200515002500-16d7b963416f/go.mod h1:tMOHGbxtRz8zYFGEGpV/bpoTEM1o89MwYFC4YJXl3GY=
-knative.dev/pkg v0.0.0-20200517014302-3c29a1ce8906 h1:Xo/FFyVEZic+75UZJ0MJMuXgdfh+99YixIMAd3nCVys=
-knative.dev/pkg v0.0.0-20200517014302-3c29a1ce8906/go.mod h1:GinFtW8LEi9rzCkzCMHAIlAnZjP1RzauxwhRAI3rimI=
+knative.dev/pkg v0.0.0-20200519155757-14eb3ae3a5a7 h1:9S2r59HZJF9nKvoRLg5zJzx6XpVlVyvVRqz/C/h6h2s=
+knative.dev/pkg v0.0.0-20200519155757-14eb3ae3a5a7/go.mod h1:QgNZTxnwpB/oSpNcfnLVlw+WpEwwyKAvJlvR3hgeltA=
 knative.dev/test-infra v0.0.0-20200407185800-1b88cb3b45a5/go.mod h1:xcdUkMJrLlBswIZqL5zCuBFOC22WIPMQoVX1L35i0vQ=
 knative.dev/test-infra v0.0.0-20200505052144-5ea2f705bb55/go.mod h1:WqF1Azka+FxPZ20keR2zCNtiQA1MP9ZB4BH4HuI+SIU=
 knative.dev/test-infra v0.0.0-20200513011557-d03429a76034 h1:JxqONCZVS7or+Fv3ebVQoipuIBH7Ig3Qbx170hgIF+A=
 knative.dev/test-infra v0.0.0-20200513011557-d03429a76034/go.mod h1:aMif0KXL4g19YCYwsy4Ocjjz5xgPlseYV+B95Oo4JGE=
-knative.dev/test-infra v0.0.0-20200514223200-ef4fd3ad398f/go.mod h1:+uml4upluwrnWHRSezi4WVcKJ0OJHynF16Ot7fu0ky4=
-knative.dev/test-infra v0.0.0-20200515184601-7a28f47cdbcb h1:g1NE1OdGOh8vgLaKhi6O/fXFhlewq7wnL2EQNLKe+bE=
-knative.dev/test-infra v0.0.0-20200515184601-7a28f47cdbcb/go.mod h1:A5b2OAXTOeHT3hHhVQm3dmtbuWvIDP7qzgtqxA3/2pE=
+knative.dev/test-infra v0.0.0-20200519015156-82551620b0a9 h1:kKfV3QWsxugwXsqgjFd72MjZeAHJ381dWqAxH2KMknc=
+knative.dev/test-infra v0.0.0-20200519015156-82551620b0a9/go.mod h1:A5b2OAXTOeHT3hHhVQm3dmtbuWvIDP7qzgtqxA3/2pE=
+knative.dev/test-infra v0.0.0-20200519161858-554a95a37986 h1:ZDy43jkWPQ75d4l4DGy+ENQIXlNcnHIh4tB6XxgovNc=
+knative.dev/test-infra v0.0.0-20200519161858-554a95a37986/go.mod h1:LeNa1Wvn47efeQUkpkn3XG7Fx9Ga+rhAP13SZyjaEGg=
 modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
 modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
 modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
diff --git a/hack/generate-yamls.sh b/hack/generate-yamls.sh
index 6cb1e66d0..4343957b2 100755
--- a/hack/generate-yamls.sh
+++ b/hack/generate-yamls.sh
@@ -67,8 +67,6 @@ readonly MONITORING_TRACE_JAEGER_YAML=${YAML_OUTPUT_DIR}/monitoring-tracing-jaeg
 readonly MONITORING_TRACE_JAEGER_IN_MEM_YAML=${YAML_OUTPUT_DIR}/monitoring-tracing-jaeger-in-mem.yaml
 readonly MONITORING_LOG_ELASTICSEARCH_YAML=${YAML_OUTPUT_DIR}/monitoring-logs-elasticsearch.yaml
 
-readonly NET_ISTIO_YAML=${YAML_OUTPUT_DIR}/net-istio.yaml
-
 # Flags for all ko commands
 KO_YAML_FLAGS="-P"
 [[ "${KO_DOCKER_REPO}" != gcr.io/* ]] && KO_YAML_FLAGS=""
@@ -76,14 +74,10 @@ readonly KO_YAML_FLAGS="${KO_YAML_FLAGS} ${KO_FLAGS} --strict"
 
 if [[ -n "${TAG}" ]]; then
   LABEL_YAML_CMD=(sed -e "s|serving.knative.dev/release: devel|serving.knative.dev/release: \"${TAG}\"|")
-  NET_ISTIO_YAML_LINK=https://github.com/knative/net-istio/releases/download/${TAG}/release.yaml
 else
   LABEL_YAML_CMD=(cat)
-  NET_ISTIO_YAML_LINK=https://storage.googleapis.com/knative-nightly/net-istio/latest/release.yaml
 fi
 
-readonly NET_ISTIO_YAML_LINK
-
 : ${KO_DOCKER_REPO:="ko.local"}
 export KO_DOCKER_REPO
 
@@ -102,16 +96,16 @@ ko resolve ${KO_YAML_FLAGS} -f config/core/resources/ -f config/300-imagecache.y
 # Create hpa-class autoscaling related yaml
 ko resolve ${KO_YAML_FLAGS} -f config/hpa-autoscaling/ | "${LABEL_YAML_CMD[@]}" > "${SERVING_HPA_YAML}"
 
+# Create cert-manager related yaml
+cat "./third_party/cert-manager-0.12.0/net-certmanager.yaml" > "${SERVING_CERT_MANAGER_YAML}"
+
 # Create nscert related yaml
 ko resolve ${KO_YAML_FLAGS} -f config/namespace-wildcard-certs | "${LABEL_YAML_CMD[@]}" > "${SERVING_NSCERT_YAML}"
 
-# Download the net-istio.yaml
-wget "${NET_ISTIO_YAML_LINK}" -O "${NET_ISTIO_YAML}"
-
 # Create serving.yaml with all of the default components
 cat "${SERVING_CORE_YAML}" > "${SERVING_YAML}"
 cat "${SERVING_HPA_YAML}" >> "${SERVING_YAML}"
-cat "${NET_ISTIO_YAML}" >> "${SERVING_YAML}"
+cat "./third_party/net-istio.yaml" >> "${SERVING_YAML}"
 
 # Metrics via Prometheus & Grafana
 ko resolve ${KO_YAML_FLAGS} -R \
diff --git a/hack/update-deps.sh b/hack/update-deps.sh
index b277dd3ff..1774b9386 100755
--- a/hack/update-deps.sh
+++ b/hack/update-deps.sh
@@ -27,7 +27,7 @@ cd ${ROOT_DIR}
 export GO111MODULE=on
 
 # This controls the release branch we track.
-VERSION="master"
+VERSION="release-0.15"
 
 # The list of dependencies that we track at HEAD and periodically
 # float forward in this repository.
diff --git a/pkg/apis/autoscaling/annotation_validation.go b/pkg/apis/autoscaling/annotation_validation.go
index bf908fd95..518faae0a 100644
--- a/pkg/apis/autoscaling/annotation_validation.go
+++ b/pkg/apis/autoscaling/annotation_validation.go
@@ -31,9 +31,16 @@ func getIntGE0(m map[string]string, k string) (int64, *apis.FieldError) {
 		return 0, nil
 	}
 	i, err := strconv.ParseInt(v, 10, 32)
-	if err != nil || i < 0 {
-		return 0, apis.ErrOutOfBoundsValue(v, 1, math.MaxInt32, k)
+	if err == nil && i < 0 {
+		return 0, apis.ErrOutOfBoundsValue(v, 0, math.MaxInt32, k)
 	}
+	if err != nil {
+		if nerr, ok := err.(*strconv.NumError); ok && nerr.Err == strconv.ErrRange {
+			return 0, apis.ErrOutOfBoundsValue(v, 0, math.MaxInt32, k)
+		}
+		return 0, apis.ErrInvalidValue(v, k)
+	}
+
 	return i, nil
 }
 
@@ -43,8 +50,8 @@ func ValidateAnnotations(allowInitScaleZero bool, anns map[string]string) *apis.
 		return nil
 	}
 	return validateMinMaxScale(anns).Also(validateFloats(anns)).
-		Also(validateWindows(anns).Also(validateMetric(anns).
-			Also(validateInitialScale(allowInitScaleZero, anns))))
+		Also(validateWindow(anns).Also(validateLastPodRetention(anns)).
+			Also(validateMetric(anns).Also(validateInitialScale(allowInitScaleZero, anns))))
 }
 
 func validateFloats(annotations map[string]string) *apis.FieldError {
@@ -61,8 +68,8 @@ func validateFloats(annotations map[string]string) *apis.FieldError {
 		if fv, err := strconv.ParseFloat(v, 64); err != nil {
 			errs = errs.Also(apis.ErrInvalidValue(v, PanicThresholdPercentageAnnotationKey))
 		} else if fv < PanicThresholdPercentageMin || fv > PanicThresholdPercentageMax {
-			errs = errs.Also(apis.ErrOutOfBoundsValue(v, PanicThresholdPercentageMin, PanicThresholdPercentageMax,
-				PanicThresholdPercentageAnnotationKey))
+			errs = errs.Also(apis.ErrOutOfBoundsValue(v, PanicThresholdPercentageMin,
+				PanicThresholdPercentageMax, PanicThresholdPercentageAnnotationKey))
 		}
 	}
 
@@ -88,14 +95,27 @@ func validateFloats(annotations map[string]string) *apis.FieldError {
 	return errs
 }
 
-func validateWindows(annotations map[string]string) *apis.FieldError {
+func validateLastPodRetention(annotations map[string]string) *apis.FieldError {
+	var errs *apis.FieldError
+	if w, ok := annotations[ScaleToZeroPodRetentionPeriodKey]; ok {
+		if d, err := time.ParseDuration(w); err != nil {
+			errs = apis.ErrInvalidValue(w, ScaleToZeroPodRetentionPeriodKey)
+		} else if d < 0 || d > WindowMax {
+			// Since we disallow windows longer than WindowMax, so we should limit this
+			// as well.
+			errs = apis.ErrOutOfBoundsValue(w, 0*time.Second, WindowMax, ScaleToZeroPodRetentionPeriodKey)
+		}
+	}
+	return errs
+}
+
+func validateWindow(annotations map[string]string) *apis.FieldError {
 	var errs *apis.FieldError
 	if w, ok := annotations[WindowAnnotationKey]; ok {
 		if annotations[ClassAnnotationKey] == HPA && annotations[MetricAnnotationKey] == CPU {
 			return apis.ErrInvalidKeyName(WindowAnnotationKey, fmt.Sprintf("%s for %s %s", HPA, MetricAnnotationKey, CPU))
 		}
-		d, err := time.ParseDuration(w)
-		if err != nil {
+		if d, err := time.ParseDuration(w); err != nil {
 			errs = apis.ErrInvalidValue(w, WindowAnnotationKey)
 		} else if d < WindowMin || d > WindowMax {
 			errs = apis.ErrOutOfBoundsValue(w, WindowMin, WindowMax, WindowAnnotationKey)
@@ -105,11 +125,7 @@ func validateWindows(annotations map[string]string) *apis.FieldError {
 }
 
 func validateMinMaxScale(annotations map[string]string) *apis.FieldError {
-	var errs *apis.FieldError
-
-	min, err := getIntGE0(annotations, MinScaleAnnotationKey)
-	errs = errs.Also(err)
-
+	min, errs := getIntGE0(annotations, MinScaleAnnotationKey)
 	max, err := getIntGE0(annotations, MaxScaleAnnotationKey)
 	errs = errs.Also(err)
 
diff --git a/pkg/apis/autoscaling/register.go b/pkg/apis/autoscaling/register.go
index 91438920a..2c26d4dd5 100644
--- a/pkg/apis/autoscaling/register.go
+++ b/pkg/apis/autoscaling/register.go
@@ -71,6 +71,13 @@ const (
 	// below 1.
 	TargetMin = 0.01
 
+	// ScaleToZeroPodRetentionPeriodKey is the annotation to specify the minimum
+	// time duration the last pod will not be scaled down, after autoscaler has
+	// made the decision to scale to 0.
+	// This is the per-revision setting compliment to the
+	// scale-to-zero-pod-retention-period global setting.
+	ScaleToZeroPodRetentionPeriodKey = GroupName + "/scaleToZeroPodRetentionPeriod"
+
 	// WindowAnnotationKey is the annotation to specify the time
 	// interval over which to calculate the average metric.  Larger
 	// values result in more smoothing. For example,
diff --git a/pkg/apis/autoscaling/v1alpha1/pa_lifecycle.go b/pkg/apis/autoscaling/v1alpha1/pa_lifecycle.go
index 4a5d187a7..7bca3dd52 100644
--- a/pkg/apis/autoscaling/v1alpha1/pa_lifecycle.go
+++ b/pkg/apis/autoscaling/v1alpha1/pa_lifecycle.go
@@ -112,16 +112,26 @@ func (pa *PodAutoscaler) TargetBC() (float64, bool) {
 	return pa.annotationFloat64(autoscaling.TargetBurstCapacityKey)
 }
 
-// Window returns the window annotation value or false if not present.
-func (pa *PodAutoscaler) Window() (window time.Duration, ok bool) {
-	// The value is validated in the webhook.
-	if s, ok := pa.Annotations[autoscaling.WindowAnnotationKey]; ok {
+func (pa *PodAutoscaler) annotationDuration(key string) (time.Duration, bool) {
+	if s, ok := pa.Annotations[key]; ok {
 		d, err := time.ParseDuration(s)
 		return d, err == nil
 	}
 	return 0, false
 }
 
+// ScaleToZeroPodRetention returns the window annotation value or false if not present.
+func (pa *PodAutoscaler) ScaleToZeroPodRetention() (time.Duration, bool) {
+	// The value is validated in the webhook.
+	return pa.annotationDuration(autoscaling.ScaleToZeroPodRetentionPeriodKey)
+}
+
+// Window returns the window annotation value or false if not present.
+func (pa *PodAutoscaler) Window() (time.Duration, bool) {
+	// The value is validated in the webhook.
+	return pa.annotationDuration(autoscaling.WindowAnnotationKey)
+}
+
 // PanicWindowPercentage returns panic window annotation value or false if not present.
 func (pa *PodAutoscaler) PanicWindowPercentage() (percentage float64, ok bool) {
 	// The value is validated in the webhook.
diff --git a/pkg/apis/config/defaults.go b/pkg/apis/config/defaults.go
index 75b5db4e1..a458e71fd 100644
--- a/pkg/apis/config/defaults.go
+++ b/pkg/apis/config/defaults.go
@@ -58,7 +58,7 @@ const (
 	DefaultAllowContainerConcurrencyZero bool = true
 )
 
-func defaultConfig() *Defaults {
+func defaultDefaultsConfig() *Defaults {
 	return &Defaults{
 		RevisionTimeoutSeconds:        DefaultRevisionTimeoutSeconds,
 		MaxRevisionTimeoutSeconds:     DefaultMaxRevisionTimeoutSeconds,
@@ -71,12 +71,11 @@ func defaultConfig() *Defaults {
 
 // NewDefaultsConfigFromMap creates a Defaults from the supplied Map.
 func NewDefaultsConfigFromMap(data map[string]string) (*Defaults, error) {
-	nc := defaultConfig()
+	nc := defaultDefaultsConfig()
 
 	if err := cm.Parse(data,
 		cm.AsString("container-name-template", &nc.UserContainerNameTemplate),
 
-		cm.AsBool("enable-multi-container", &nc.EnableMultiContainer),
 		cm.AsBool("allow-container-concurrency-zero", &nc.AllowContainerConcurrencyZero),
 
 		cm.AsInt64("revision-timeout-seconds", &nc.RevisionTimeoutSeconds),
@@ -123,9 +122,6 @@ func NewDefaultsConfigFromConfigMap(config *corev1.ConfigMap) (*Defaults, error)
 
 // Defaults includes the default values to be populated by the webhook.
 type Defaults struct {
-	// Feature flag to enable multi container support.
-	EnableMultiContainer bool
-
 	RevisionTimeoutSeconds int64
 	// This is the timeout set for ingress.
 	// RevisionTimeoutSeconds must be less than this value.
diff --git a/pkg/apis/config/features.go b/pkg/apis/config/features.go
new file mode 100644
index 000000000..b81fb6796
--- /dev/null
+++ b/pkg/apis/config/features.go
@@ -0,0 +1,76 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package config
+
+import (
+	"strings"
+
+	corev1 "k8s.io/api/core/v1"
+	cm "knative.dev/pkg/configmap"
+)
+
+type Flag string
+
+const (
+	// FeaturesConfigName is the name of the ConfigMap for the features.
+	FeaturesConfigName = "config-features"
+
+	Enabled  Flag = "Enabled"
+	Allowed  Flag = "Allowed"
+	Disabled Flag = "Disabled"
+)
+
+func defaultFeaturesConfig() *Features {
+	return &Features{
+		MultiContainer: Disabled,
+	}
+}
+
+// NewFeaturesConfigFromMap creates a Features from the supplied Map
+func NewFeaturesConfigFromMap(data map[string]string) (*Features, error) {
+	nc := defaultFeaturesConfig()
+
+	if err := cm.Parse(data, AsFlag("multi-container", &nc.MultiContainer)); err != nil {
+		return nil, err
+	}
+	return nc, nil
+}
+
+// NewFeaturesConfigFromConfigMap creates a Features from the supplied ConfigMap
+func NewFeaturesConfigFromConfigMap(config *corev1.ConfigMap) (*Features, error) {
+	return NewFeaturesConfigFromMap(config.Data)
+}
+
+// Features specifies which features are allowed by the webhook.
+type Features struct {
+	MultiContainer Flag
+}
+
+// AsFlag parses the value at key as a Flag into the target, if it exists.
+func AsFlag(key string, target *Flag) cm.ParseFunc {
+	return func(data map[string]string) error {
+		if raw, ok := data[key]; ok {
+			for _, flag := range []Flag{Enabled, Allowed, Disabled} {
+				if strings.EqualFold(raw, string(flag)) {
+					*target = flag
+					return nil
+				}
+			}
+		}
+		return nil
+	}
+}
diff --git a/pkg/apis/config/store.go b/pkg/apis/config/store.go
index 2d28d2090..5288d0b05 100644
--- a/pkg/apis/config/store.go
+++ b/pkg/apis/config/store.go
@@ -29,6 +29,7 @@ type cfgKey struct{}
 // +k8s:deepcopy-gen=false
 type Config struct {
 	Defaults   *Defaults
+	Features   *Features
 	Autoscaler *autoscalerconfig.Config
 }
 
@@ -48,9 +49,11 @@ func FromContextOrDefaults(ctx context.Context) *Config {
 		return cfg
 	}
 	defaults, _ := NewDefaultsConfigFromMap(map[string]string{})
+	features, _ := NewFeaturesConfigFromMap(map[string]string{})
 	autoscaler, _ := autoscalerconfig.NewConfigFromMap(map[string]string{})
 	return &Config{
 		Defaults:   defaults,
+		Features:   features,
 		Autoscaler: autoscaler,
 	}
 }
@@ -71,10 +74,11 @@ type Store struct {
 func NewStore(logger configmap.Logger, onAfterStore ...func(name string, value interface{})) *Store {
 	store := &Store{
 		UntypedStore: configmap.NewUntypedStore(
-			"defaults",
+			"apis",
 			logger,
 			configmap.Constructors{
 				DefaultsConfigName:          NewDefaultsConfigFromConfigMap,
+				FeaturesConfigName:          NewFeaturesConfigFromConfigMap,
 				autoscalerconfig.ConfigName: autoscalerconfig.NewConfigFromConfigMap,
 			},
 			onAfterStore...,
@@ -93,6 +97,7 @@ func (s *Store) ToContext(ctx context.Context) context.Context {
 func (s *Store) Load() *Config {
 	return &Config{
 		Defaults:   s.UntypedLoad(DefaultsConfigName).(*Defaults).DeepCopy(),
+		Features:   s.UntypedLoad(FeaturesConfigName).(*Features).DeepCopy(),
 		Autoscaler: s.UntypedLoad(autoscalerconfig.ConfigName).(*autoscalerconfig.Config).DeepCopy(),
 	}
 }
diff --git a/pkg/apis/config/zz_generated.deepcopy.go b/pkg/apis/config/zz_generated.deepcopy.go
index 341f6334b..484d01ca3 100644
--- a/pkg/apis/config/zz_generated.deepcopy.go
+++ b/pkg/apis/config/zz_generated.deepcopy.go
@@ -55,3 +55,19 @@ func (in *Defaults) DeepCopy() *Defaults {
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Features) DeepCopyInto(out *Features) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Features.
+func (in *Features) DeepCopy() *Features {
+	if in == nil {
+		return nil
+	}
+	out := new(Features)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/pkg/apis/networking/v1alpha1/certificate_types.go b/pkg/apis/networking/v1alpha1/certificate_types.go
index 4691da7cb..3a78d007e 100644
--- a/pkg/apis/networking/v1alpha1/certificate_types.go
+++ b/pkg/apis/networking/v1alpha1/certificate_types.go
@@ -25,7 +25,7 @@ import (
 )
 
 // +genclient
-// +genreconciler:class=networking.knative.dev/certificate.class
+// +genreconciler:class=networking.knative.dev/certificate.class,krshapedlogic=true
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
 // Certificate is responsible for provisioning a SSL certificate for the
@@ -120,11 +120,6 @@ type HTTP01Challenge struct {
 	ServicePort intstr.IntOrString `json:"servicePort,omitempty"`
 }
 
-// GetTypeMeta retrieves the ObjectMeta of the Certificate. Implements the KRShaped interface.
-func (t *Certificate) GetTypeMeta() *metav1.TypeMeta {
-	return &t.TypeMeta
-}
-
 // GetStatus retrieves the status of the Certificate. Implements the KRShaped interface.
 func (t *Certificate) GetStatus() *duckv1.Status {
 	return &t.Status.Status
diff --git a/pkg/apis/networking/v1alpha1/ingress_types.go b/pkg/apis/networking/v1alpha1/ingress_types.go
index 1ddf40e87..de465c013 100644
--- a/pkg/apis/networking/v1alpha1/ingress_types.go
+++ b/pkg/apis/networking/v1alpha1/ingress_types.go
@@ -25,7 +25,7 @@ import (
 )
 
 // +genclient
-// +genreconciler
+// +genreconciler:krshapedlogic=true
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
 // Ingress is a collection of rules that allow inbound connections to reach the endpoints defined
@@ -346,11 +346,6 @@ const (
 	IngressConditionLoadBalancerReady apis.ConditionType = "LoadBalancerReady"
 )
 
-// GetTypeMeta retrieves the ObjectMeta of the Ingress. Implements the KRShaped interface.
-func (t *Ingress) GetTypeMeta() *metav1.TypeMeta {
-	return &t.TypeMeta
-}
-
 // GetStatus retrieves the status of the Ingress. Implements the KRShaped interface.
 func (t *Ingress) GetStatus() *duckv1.Status {
 	return &t.Status.Status
diff --git a/pkg/apis/networking/v1alpha1/serverlessservice_types.go b/pkg/apis/networking/v1alpha1/serverlessservice_types.go
index 8c2d0cd5a..f61525e62 100644
--- a/pkg/apis/networking/v1alpha1/serverlessservice_types.go
+++ b/pkg/apis/networking/v1alpha1/serverlessservice_types.go
@@ -26,7 +26,7 @@ import (
 )
 
 // +genclient
-// +genreconciler
+// +genreconciler:krshapedlogic=true
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
 // ServerlessService is a proxy for the K8s service objects containing the
@@ -143,11 +143,6 @@ const (
 	ActivatorEndpointsPopulated apis.ConditionType = "ActivatorEndpointsPopulated"
 )
 
-// GetTypeMeta retrieves the ObjectMeta of the ServerlessService. Implements the KRShaped interface.
-func (t *ServerlessService) GetTypeMeta() *metav1.TypeMeta {
-	return &t.TypeMeta
-}
-
 // GetStatus retrieves the status of the ServerlessService. Implements the KRShaped interface.
 func (t *ServerlessService) GetStatus() *duckv1.Status {
 	return &t.Status.Status
diff --git a/pkg/apis/serving/k8s_lifecycle.go b/pkg/apis/serving/k8s_lifecycle.go
index 23f7bc723..de5d60c14 100644
--- a/pkg/apis/serving/k8s_lifecycle.go
+++ b/pkg/apis/serving/k8s_lifecycle.go
@@ -40,7 +40,7 @@ const (
 	DeploymentConditionProgressing apis.ConditionType = "Progressing"
 )
 
-// transformDeploymentStatus transforms the kubernetes DeploymentStatus into a
+// TransformDeploymentStatus transforms the Kubernetes DeploymentStatus into a
 // duckv1.Status that uses ConditionSets to propagate failures and expose
 // a top-level happy state, per our condition conventions.
 func TransformDeploymentStatus(ds *appsv1.DeploymentStatus) *duckv1.Status {
diff --git a/pkg/apis/serving/k8s_validation.go b/pkg/apis/serving/k8s_validation.go
index 8de357613..c363073f7 100644
--- a/pkg/apis/serving/k8s_validation.go
+++ b/pkg/apis/serving/k8s_validation.go
@@ -272,9 +272,9 @@ func ValidatePodSpec(ctx context.Context, ps corev1.PodSpec) *apis.FieldError {
 
 func validateContainers(ctx context.Context, containers []corev1.Container, volumes sets.String) *apis.FieldError {
 	var errs *apis.FieldError
-	cfg := config.FromContextOrDefaults(ctx).Defaults
-	if !cfg.EnableMultiContainer {
-		errs = errs.Also(&apis.FieldError{Message: fmt.Sprintf("enable-multi-container is off, "+
+	features := config.FromContextOrDefaults(ctx).Features
+	if features.MultiContainer != config.Enabled {
+		errs = errs.Also(&apis.FieldError{Message: fmt.Sprintf("multi-container is off, "+
 			"but found %d containers", len(containers))})
 	} else {
 		errs = errs.Also(validateContainersPorts(containers).ViaField("containers"))
diff --git a/pkg/apis/serving/metadata_validation.go b/pkg/apis/serving/metadata_validation.go
index 30798d9fc..36c138617 100644
--- a/pkg/apis/serving/metadata_validation.go
+++ b/pkg/apis/serving/metadata_validation.go
@@ -25,18 +25,20 @@ import (
 	"k8s.io/apimachinery/pkg/api/equality"
 	"k8s.io/apimachinery/pkg/api/validation"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/sets"
+
 	"knative.dev/pkg/apis"
 	"knative.dev/serving/pkg/apis/autoscaling"
 	"knative.dev/serving/pkg/apis/config"
 )
 
 var (
-	allowedAnnotations = map[string]struct{}{
-		UpdaterAnnotation:                {},
-		CreatorAnnotation:                {},
-		RevisionLastPinnedAnnotationKey:  {},
-		GroupNamePrefix + "forceUpgrade": {},
-	}
+	allowedAnnotations = sets.NewString(
+		UpdaterAnnotation,
+		CreatorAnnotation,
+		RevisionLastPinnedAnnotationKey,
+		GroupNamePrefix+"forceUpgrade",
+	)
 )
 
 // ValidateObjectMetadata validates that `metadata` stanza of the
@@ -51,7 +53,7 @@ func ValidateObjectMetadata(ctx context.Context, meta metav1.Object) *apis.Field
 
 func validateKnativeAnnotations(annotations map[string]string) (errs *apis.FieldError) {
 	for key := range annotations {
-		if _, ok := allowedAnnotations[key]; !ok && strings.HasPrefix(key, GroupNamePrefix) {
+		if !allowedAnnotations.Has(key) && strings.HasPrefix(key, GroupNamePrefix) {
 			errs = errs.Also(apis.ErrInvalidKeyName(key, apis.CurrentField))
 		}
 	}
@@ -71,8 +73,8 @@ func ValidateQueueSidecarAnnotation(annotations map[string]string) *apis.FieldEr
 	if err != nil {
 		return apis.ErrInvalidValue(v, apis.CurrentField).ViaKey(QueueSideCarResourcePercentageAnnotation)
 	}
-	if value <= 0.1 || value > 100 {
-		return apis.ErrOutOfBoundsValue(value, 0.1, 100.0, QueueSideCarResourcePercentageAnnotation)
+	if value < 0.1 || value > 100 {
+		return apis.ErrOutOfBoundsValue(value, 0.1, 100.0, apis.CurrentField).ViaKey(QueueSideCarResourcePercentageAnnotation)
 	}
 	return nil
 }
diff --git a/pkg/apis/serving/v1/configuration_types.go b/pkg/apis/serving/v1/configuration_types.go
index fbf1ae155..cad8cfdf9 100644
--- a/pkg/apis/serving/v1/configuration_types.go
+++ b/pkg/apis/serving/v1/configuration_types.go
@@ -24,7 +24,7 @@ import (
 )
 
 // +genclient
-// +genreconciler
+// +genreconciler:krshapedlogic=true
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
 // Configuration represents the "floating HEAD" of a linear history of Revisions.
diff --git a/pkg/apis/serving/v1/revision_lifecycle.go b/pkg/apis/serving/v1/revision_lifecycle.go
index ca1b69da5..1b7649c3b 100644
--- a/pkg/apis/serving/v1/revision_lifecycle.go
+++ b/pkg/apis/serving/v1/revision_lifecycle.go
@@ -136,9 +136,6 @@ func (rs *RevisionStatus) MarkResourcesAvailableUnknown(reason, message string)
 func (rs *RevisionStatus) PropagateDeploymentStatus(original *appsv1.DeploymentStatus) {
 	ds := serving.TransformDeploymentStatus(original)
 	cond := ds.GetCondition(serving.DeploymentConditionReady)
-	if cond == nil {
-		return
-	}
 
 	m := revisionCondSet.Manage(rs)
 	switch cond.Status {
diff --git a/pkg/apis/serving/v1/revision_types.go b/pkg/apis/serving/v1/revision_types.go
index dbad280b1..72fc15fd4 100644
--- a/pkg/apis/serving/v1/revision_types.go
+++ b/pkg/apis/serving/v1/revision_types.go
@@ -25,7 +25,7 @@ import (
 )
 
 // +genclient
-// +genreconciler
+// +genreconciler:krshapedlogic=true
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
 // Revision is an immutable snapshot of code and configuration.  A revision
@@ -139,7 +139,7 @@ type RevisionStatus struct {
 	// may be empty if the image comes from a registry listed to skip resolution.
 	// If multiple containers specified then DeprecatedImageDigest holds the digest
 	// for serving container.
-	// DEPRECATED Use ContainerStatuses instead.
+	// DEPRECATED: Use ContainerStatuses instead.
 	// TODO(savitaashture) Remove deprecatedImageDigest.
 	// ref https://kubernetes.io/docs/reference/using-api/deprecation-policy for deprecation.
 	// +optional
diff --git a/pkg/apis/serving/v1/route_lifecycle.go b/pkg/apis/serving/v1/route_lifecycle.go
index 1942731f5..81de8ed8a 100644
--- a/pkg/apis/serving/v1/route_lifecycle.go
+++ b/pkg/apis/serving/v1/route_lifecycle.go
@@ -29,6 +29,7 @@ import (
 var routeCondSet = apis.NewLivingConditionSet(
 	RouteConditionAllTrafficAssigned,
 	RouteConditionIngressReady,
+	RouteConditionCertificateProvisioned,
 )
 
 // GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface.
@@ -104,43 +105,41 @@ func (rs *RouteStatus) MarkMissingTrafficTarget(kind, name string) {
 }
 
 func (rs *RouteStatus) MarkCertificateProvisionFailed(name string) {
-	routeCondSet.Manage(rs).SetCondition(apis.Condition{
-		Type:     RouteConditionCertificateProvisioned,
-		Status:   corev1.ConditionFalse,
-		Severity: apis.ConditionSeverityWarning,
-		Reason:   "CertificateProvisionFailed",
-		Message:  fmt.Sprintf("Certificate %s fails to be provisioned.", name),
-	})
+	routeCondSet.Manage(rs).MarkFalse(RouteConditionCertificateProvisioned,
+		"CertificateProvisionFailed",
+		"Certificate %s fails to be provisioned.", name)
 }
 
 func (rs *RouteStatus) MarkCertificateReady(name string) {
-	routeCondSet.Manage(rs).SetCondition(apis.Condition{
-		Type:     RouteConditionCertificateProvisioned,
-		Status:   corev1.ConditionTrue,
-		Severity: apis.ConditionSeverityWarning,
-		Reason:   "CertificateReady",
-		Message:  fmt.Sprintf("Certificate %s is successfully provisioned", name),
-	})
+	routeCondSet.Manage(rs).MarkTrue(RouteConditionCertificateProvisioned)
 }
 
 func (rs *RouteStatus) MarkCertificateNotReady(name string) {
-	routeCondSet.Manage(rs).SetCondition(apis.Condition{
-		Type:     RouteConditionCertificateProvisioned,
-		Status:   corev1.ConditionUnknown,
-		Severity: apis.ConditionSeverityWarning,
-		Reason:   "CertificateNotReady",
-		Message:  fmt.Sprintf("Certificate %s is not ready.", name),
-	})
+	routeCondSet.Manage(rs).MarkUnknown(RouteConditionCertificateProvisioned,
+		"CertificateNotReady",
+		"Certificate %s is not ready.", name)
 }
 
 func (rs *RouteStatus) MarkCertificateNotOwned(name string) {
-	routeCondSet.Manage(rs).SetCondition(apis.Condition{
-		Type:     RouteConditionCertificateProvisioned,
-		Status:   corev1.ConditionFalse,
-		Severity: apis.ConditionSeverityWarning,
-		Reason:   "CertificateNotOwned",
-		Message:  fmt.Sprintf("There is an existing certificate %s that we don't own.", name),
-	})
+	routeCondSet.Manage(rs).MarkFalse(RouteConditionCertificateProvisioned,
+		"CertificateNotOwned",
+		"There is an existing certificate %s that we don't own.", name)
+}
+
+// MarkAutoTLSNotEnabled sets RouteConditionCertificateProvisioned to true when
+// certificate config such as autoTLS is not enabled.
+func (rs *RouteStatus) MarkAutoTLSNotEnabled() {
+	routeCondSet.Manage(rs).MarkTrueWithReason(RouteConditionCertificateProvisioned,
+		"AutoTLSNotEnabled",
+		"autoTLS is not enabled")
+}
+
+// MarkHTTPDowngrade sets RouteConditionCertificateProvisioned to true when plain
+// HTTP is enabled even when Certificated is not ready.
+func (rs *RouteStatus) MarkHTTPDowngrade(name string) {
+	routeCondSet.Manage(rs).MarkTrueWithReason(RouteConditionCertificateProvisioned,
+		"HTTPDowngrade",
+		"Certificate %s is not ready downgrade HTTP.", name)
 }
 
 // PropagateIngressStatus update RouteConditionIngressReady condition
diff --git a/pkg/apis/serving/v1/service_types.go b/pkg/apis/serving/v1/service_types.go
index ce5174319..d91818e05 100644
--- a/pkg/apis/serving/v1/service_types.go
+++ b/pkg/apis/serving/v1/service_types.go
@@ -25,7 +25,7 @@ import (
 )
 
 // +genclient
-// +genreconciler
+// +genreconciler:krshapedlogic=true
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
 // Service acts as a top-level container that manages a Route and Configuration
diff --git a/pkg/apis/serving/v1alpha1/revision_types.go b/pkg/apis/serving/v1alpha1/revision_types.go
index f0a7ca295..51cf78435 100644
--- a/pkg/apis/serving/v1alpha1/revision_types.go
+++ b/pkg/apis/serving/v1alpha1/revision_types.go
@@ -160,7 +160,7 @@ type RevisionStatus struct {
 	// may be empty if the image comes from a registry listed to skip resolution.
 	// If multiple containers specified then DeprecatedImageDigest holds the digest
 	// for serving container.
-	// DEPRECATED Use ImageDigests instead.
+	// DEPRECATED: Use ContainerStatuses instead.
 	// TODO(savitaashture) Remove deprecatedImageDigest.
 	// ref https://kubernetes.io/docs/reference/using-api/deprecation-policy for deprecation.
 	// +optional
diff --git a/pkg/apis/serving/v1alpha1/route_lifecycle.go b/pkg/apis/serving/v1alpha1/route_lifecycle.go
index 017e6dc42..523187d49 100644
--- a/pkg/apis/serving/v1alpha1/route_lifecycle.go
+++ b/pkg/apis/serving/v1alpha1/route_lifecycle.go
@@ -29,6 +29,7 @@ import (
 var routeCondSet = apis.NewLivingConditionSet(
 	RouteConditionAllTrafficAssigned,
 	RouteConditionIngressReady,
+	RouteConditionCertificateProvisioned,
 )
 
 func (r *Route) GetGroupVersionKind() schema.GroupVersionKind {
@@ -112,43 +113,41 @@ func (rs *RouteStatus) MarkMissingTrafficTarget(kind, name string) {
 }
 
 func (rs *RouteStatus) MarkCertificateProvisionFailed(name string) {
-	routeCondSet.Manage(rs).SetCondition(apis.Condition{
-		Type:     RouteConditionCertificateProvisioned,
-		Status:   corev1.ConditionFalse,
-		Severity: apis.ConditionSeverityWarning,
-		Reason:   "CertificateProvisionFailed",
-		Message:  fmt.Sprintf("Certificate %s fails to be provisioned.", name),
-	})
+	routeCondSet.Manage(rs).MarkFalse(RouteConditionCertificateProvisioned,
+		"CertificateProvisionFailed",
+		"Certificate %s fails to be provisioned.", name)
 }
 
 func (rs *RouteStatus) MarkCertificateReady(name string) {
-	routeCondSet.Manage(rs).SetCondition(apis.Condition{
-		Type:     RouteConditionCertificateProvisioned,
-		Status:   corev1.ConditionTrue,
-		Severity: apis.ConditionSeverityWarning,
-		Reason:   "CertificateReady",
-		Message:  fmt.Sprintf("Certificate %s is successfully provisioned", name),
-	})
+	routeCondSet.Manage(rs).MarkTrue(RouteConditionCertificateProvisioned)
 }
 
 func (rs *RouteStatus) MarkCertificateNotReady(name string) {
-	routeCondSet.Manage(rs).SetCondition(apis.Condition{
-		Type:     RouteConditionCertificateProvisioned,
-		Status:   corev1.ConditionUnknown,
-		Severity: apis.ConditionSeverityWarning,
-		Reason:   "CertificateNotReady",
-		Message:  fmt.Sprintf("Certificate %s is not ready.", name),
-	})
+	routeCondSet.Manage(rs).MarkUnknown(RouteConditionCertificateProvisioned,
+		"CertificateNotReady",
+		"Certificate %s is not ready.", name)
 }
 
 func (rs *RouteStatus) MarkCertificateNotOwned(name string) {
-	routeCondSet.Manage(rs).SetCondition(apis.Condition{
-		Type:     RouteConditionCertificateProvisioned,
-		Status:   corev1.ConditionFalse,
-		Severity: apis.ConditionSeverityWarning,
-		Reason:   "CertificateNotOwned",
-		Message:  fmt.Sprintf("There is an existing certificate %s that we don't own.", name),
-	})
+	routeCondSet.Manage(rs).MarkFalse(RouteConditionCertificateProvisioned,
+		"CertificateNotOwned",
+		"There is an existing certificate %s that we don't own.", name)
+}
+
+// MarkAutoTLSNotEnabled sets RouteConditionCertificateProvisioned to true when
+// certificate config such as autoTLS is not enabled.
+func (rs *RouteStatus) MarkAutoTLSNotEnabled() {
+	routeCondSet.Manage(rs).MarkTrueWithReason(RouteConditionCertificateProvisioned,
+		"AutoTLSNotEnabled",
+		"autoTLS is not enabled")
+}
+
+// MarkHTTPDowngrade sets RouteConditionCertificateProvisioned to true when plain
+// HTTP is enabled even when Certificated is not ready.
+func (rs *RouteStatus) MarkHTTPDowngrade(name string) {
+	routeCondSet.Manage(rs).MarkTrueWithReason(RouteConditionCertificateProvisioned,
+		"HTTPDowngrade",
+		"Certificate %s is not ready downgrade HTTP.", name)
 }
 
 // PropagateIngressStatus update RouteConditionIngressReady condition
diff --git a/pkg/autoscaler/config/config.go b/pkg/autoscaler/config/config.go
index 17f26acda..6fe41e1ca 100644
--- a/pkg/autoscaler/config/config.go
+++ b/pkg/autoscaler/config/config.go
@@ -193,9 +193,12 @@ func validate(lc *Config) (*Config, error) {
 	}
 
 	// We can't permit stable window be less than our aggregation window for correctness.
-	if lc.StableWindow < autoscaling.WindowMin {
-		return nil, fmt.Errorf("stable-window = %v, must be at least %v", lc.StableWindow, autoscaling.WindowMin)
+	// Or too big, so that our desisions are too imprecise.
+	if lc.StableWindow < autoscaling.WindowMin || lc.StableWindow > autoscaling.WindowMax {
+		return nil, fmt.Errorf("stable-window = %v, must be in [%v; %v] range", lc.StableWindow,
+			autoscaling.WindowMin, autoscaling.WindowMax)
 	}
+
 	if lc.StableWindow.Round(time.Second) != lc.StableWindow {
 		return nil, fmt.Errorf("stable-window = %v, must be specified with at most second precision", lc.StableWindow)
 	}
diff --git a/pkg/autoscaler/metrics/collector.go b/pkg/autoscaler/metrics/collector.go
index 21e659a6a..267d23aa9 100644
--- a/pkg/autoscaler/metrics/collector.go
+++ b/pkg/autoscaler/metrics/collector.go
@@ -184,7 +184,7 @@ func (c *MetricCollector) Watch(fn func(types.NamespacedName)) {
 	defer c.watcherMutex.Unlock()
 
 	if c.watcher != nil {
-		c.logger.Fatal("Multiple calls to Watch() not supported")
+		c.logger.Panic("Multiple calls to Watch() not supported")
 	}
 	c.watcher = fn
 }
@@ -302,15 +302,16 @@ func newCollection(metric *av1alpha1.Metric, scraper StatsScraper, tickFactory f
 			case <-c.stopCh:
 				return
 			case <-scrapeTicker.C:
-				currentMetric := c.currentMetric()
-				if currentMetric.Spec.ScrapeTarget == "" {
+				scraper := c.getScraper()
+				if scraper == nil {
 					// Don't scrape empty target service.
 					if c.updateLastError(nil) {
 						callback(key)
 					}
 					continue
 				}
-				stat, err := c.getScraper().Scrape(currentMetric.Spec.StableWindow)
+
+				stat, err := scraper.Scrape(c.currentMetric().Spec.StableWindow)
 				if err != nil {
 					logger.Errorw("Failed to scrape metrics", zap.Error(err))
 				}
diff --git a/pkg/autoscaler/metrics/stats_scraper.go b/pkg/autoscaler/metrics/stats_scraper.go
index 6c019570a..4387dba71 100644
--- a/pkg/autoscaler/metrics/stats_scraper.go
+++ b/pkg/autoscaler/metrics/stats_scraper.go
@@ -315,33 +315,37 @@ func (s *serviceScraper) scrapeService(window time.Duration, readyPods int) (Sta
 		grp.Go(func() error {
 			for tries := 1; ; tries++ {
 				stat, err := s.tryScrape(scrapedPods)
-				if err == nil {
-					if stat.ProcessUptime >= youngPodCutOffSecs {
-						// We run |sampleSize| goroutines and each of them terminates
-						// as soon as it sees stat from an `oldPod`.
-						// The channel is allocated to |sampleSize|, thus this will never
-						// deadlock.
-						oldStatCh <- stat
-						return nil
-					} else {
-						select {
-						// This in theory might loop over all the possible pods, thus might
-						// fill up the channel.
-						case youngStatCh <- stat:
-						default:
-							// If so, just return.
-							return nil
-						}
-					}
-				} else if tries >= scraperMaxRetries {
+				if err != nil {
 					// Return the error if we exhausted our retries and
 					// we had an error returned (we can end up here if
 					// all the pods were young, which is not an error condition).
-					return err
+					if tries >= scraperMaxRetries {
+						return err
+					}
+					continue
+				}
+
+				if stat.ProcessUptime >= youngPodCutOffSecs {
+					// We run |sampleSize| goroutines and each of them terminates
+					// as soon as it sees a stat from an `oldPod`.
+					// The channel is allocated to |sampleSize|, thus this will never
+					// deadlock.
+					oldStatCh <- stat
+					return nil
+				}
+
+				select {
+				// This in theory might loop over all the possible pods, thus might
+				// fill up the channel.
+				case youngStatCh <- stat:
+				default:
+					// If so, just return.
+					return nil
 				}
 			}
 		})
 	}
+
 	// Now at this point we have two possibilities.
 	// 1. We scraped |sampleSize| distinct pods, with the invariant of
 	// 		   sampleSize <= len(oldStatCh) + len(youngStatCh) <= sampleSize*2.
diff --git a/pkg/client/injection/reconciler/networking/v1alpha1/certificate/reconciler.go b/pkg/client/injection/reconciler/networking/v1alpha1/certificate/reconciler.go
index 3a4483101..7ef6fa43a 100644
--- a/pkg/client/injection/reconciler/networking/v1alpha1/certificate/reconciler.go
+++ b/pkg/client/injection/reconciler/networking/v1alpha1/certificate/reconciler.go
@@ -175,10 +175,14 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			logger.Warnw("Failed to set finalizers", zap.Error(err))
 		}
 
+		reconciler.PreProcessReconcile(ctx, resource)
+
 		// Reconcile this copy of the resource and then write back any status
 		// updates regardless of whether the reconciliation errored out.
 		reconcileEvent = r.reconciler.ReconcileKind(ctx, resource)
 
+		reconciler.PostProcessReconcile(ctx, resource)
+
 	} else if fin, ok := r.reconciler.(Finalizer); ok {
 		// Append the target method to the logger.
 		logger = logger.With(zap.String("targetMethod", "FinalizeKind"))
diff --git a/pkg/client/injection/reconciler/networking/v1alpha1/ingress/reconciler.go b/pkg/client/injection/reconciler/networking/v1alpha1/ingress/reconciler.go
index 2560e7511..0938d5b00 100644
--- a/pkg/client/injection/reconciler/networking/v1alpha1/ingress/reconciler.go
+++ b/pkg/client/injection/reconciler/networking/v1alpha1/ingress/reconciler.go
@@ -164,10 +164,14 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			logger.Warnw("Failed to set finalizers", zap.Error(err))
 		}
 
+		reconciler.PreProcessReconcile(ctx, resource)
+
 		// Reconcile this copy of the resource and then write back any status
 		// updates regardless of whether the reconciliation errored out.
 		reconcileEvent = r.reconciler.ReconcileKind(ctx, resource)
 
+		reconciler.PostProcessReconcile(ctx, resource)
+
 	} else if fin, ok := r.reconciler.(Finalizer); ok {
 		// Append the target method to the logger.
 		logger = logger.With(zap.String("targetMethod", "FinalizeKind"))
diff --git a/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice/reconciler.go b/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice/reconciler.go
index 0d8d91383..478e647ee 100644
--- a/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice/reconciler.go
+++ b/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice/reconciler.go
@@ -164,10 +164,14 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			logger.Warnw("Failed to set finalizers", zap.Error(err))
 		}
 
+		reconciler.PreProcessReconcile(ctx, resource)
+
 		// Reconcile this copy of the resource and then write back any status
 		// updates regardless of whether the reconciliation errored out.
 		reconcileEvent = r.reconciler.ReconcileKind(ctx, resource)
 
+		reconciler.PostProcessReconcile(ctx, resource)
+
 	} else if fin, ok := r.reconciler.(Finalizer); ok {
 		// Append the target method to the logger.
 		logger = logger.With(zap.String("targetMethod", "FinalizeKind"))
diff --git a/pkg/client/injection/reconciler/serving/v1/configuration/reconciler.go b/pkg/client/injection/reconciler/serving/v1/configuration/reconciler.go
index 868fdfd08..e2e4c5b6e 100644
--- a/pkg/client/injection/reconciler/serving/v1/configuration/reconciler.go
+++ b/pkg/client/injection/reconciler/serving/v1/configuration/reconciler.go
@@ -164,10 +164,14 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			logger.Warnw("Failed to set finalizers", zap.Error(err))
 		}
 
+		reconciler.PreProcessReconcile(ctx, resource)
+
 		// Reconcile this copy of the resource and then write back any status
 		// updates regardless of whether the reconciliation errored out.
 		reconcileEvent = r.reconciler.ReconcileKind(ctx, resource)
 
+		reconciler.PostProcessReconcile(ctx, resource)
+
 	} else if fin, ok := r.reconciler.(Finalizer); ok {
 		// Append the target method to the logger.
 		logger = logger.With(zap.String("targetMethod", "FinalizeKind"))
diff --git a/pkg/client/injection/reconciler/serving/v1/revision/reconciler.go b/pkg/client/injection/reconciler/serving/v1/revision/reconciler.go
index 25c59cd4c..ed3c6d2e3 100644
--- a/pkg/client/injection/reconciler/serving/v1/revision/reconciler.go
+++ b/pkg/client/injection/reconciler/serving/v1/revision/reconciler.go
@@ -164,10 +164,14 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			logger.Warnw("Failed to set finalizers", zap.Error(err))
 		}
 
+		reconciler.PreProcessReconcile(ctx, resource)
+
 		// Reconcile this copy of the resource and then write back any status
 		// updates regardless of whether the reconciliation errored out.
 		reconcileEvent = r.reconciler.ReconcileKind(ctx, resource)
 
+		reconciler.PostProcessReconcile(ctx, resource)
+
 	} else if fin, ok := r.reconciler.(Finalizer); ok {
 		// Append the target method to the logger.
 		logger = logger.With(zap.String("targetMethod", "FinalizeKind"))
diff --git a/pkg/client/injection/reconciler/serving/v1/service/reconciler.go b/pkg/client/injection/reconciler/serving/v1/service/reconciler.go
index ba494db10..3680eb6aa 100644
--- a/pkg/client/injection/reconciler/serving/v1/service/reconciler.go
+++ b/pkg/client/injection/reconciler/serving/v1/service/reconciler.go
@@ -164,10 +164,14 @@ func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
 			logger.Warnw("Failed to set finalizers", zap.Error(err))
 		}
 
+		reconciler.PreProcessReconcile(ctx, resource)
+
 		// Reconcile this copy of the resource and then write back any status
 		// updates regardless of whether the reconciliation errored out.
 		reconcileEvent = r.reconciler.ReconcileKind(ctx, resource)
 
+		reconciler.PostProcessReconcile(ctx, resource)
+
 	} else if fin, ok := r.reconciler.(Finalizer); ok {
 		// Append the target method to the logger.
 		logger = logger.With(zap.String("targetMethod", "FinalizeKind"))
diff --git a/pkg/deployment/config.go b/pkg/deployment/config.go
index 4000f05f1..21f65c6da 100644
--- a/pkg/deployment/config.go
+++ b/pkg/deployment/config.go
@@ -17,8 +17,8 @@ limitations under the License.
 package deployment
 
 import (
+	"errors"
 	"fmt"
-	"strings"
 	"time"
 
 	corev1 "k8s.io/api/core/v1"
@@ -56,15 +56,19 @@ func NewConfigFromMap(configMap map[string]string) (*Config, error) {
 	nc := defaultConfig()
 
 	if err := cm.Parse(configMap,
-		asRequiredString(QueueSidecarImageKey, &nc.QueueSidecarImage),
+		cm.AsString(QueueSidecarImageKey, &nc.QueueSidecarImage),
 		cm.AsDuration(ProgressDeadlineKey, &nc.ProgressDeadline),
-		asStringSet(registriesSkippingTagResolvingKey, &nc.RegistriesSkippingTagResolving),
+		cm.AsStringSet(registriesSkippingTagResolvingKey, &nc.RegistriesSkippingTagResolving),
 	); err != nil {
 		return nil, err
 	}
 
+	if nc.QueueSidecarImage == "" {
+		return nil, errors.New("queueSidecarImage cannot be empty or unset")
+	}
+
 	if nc.ProgressDeadline <= 0 {
-		return nil, fmt.Errorf("ProgressDeadline cannot be a non-positive duration, was %v", nc.ProgressDeadline)
+		return nil, fmt.Errorf("progressDeadline cannot be a non-positive duration, was %v", nc.ProgressDeadline)
 	}
 
 	return nc, nil
@@ -88,26 +92,3 @@ type Config struct {
 	// be ready before considering it failed.
 	ProgressDeadline time.Duration
 }
-
-// asRequiredString is the same as cm.AsString but errors if the key is not present.
-func asRequiredString(key string, target *string) cm.ParseFunc {
-	return func(data map[string]string) error {
-		if raw, ok := data[key]; ok {
-			*target = raw
-		} else {
-			return fmt.Errorf("%q is missing", key)
-		}
-		return nil
-	}
-}
-
-// asStringSet parses the value at key as a sets.String (splitting at ',') into the
-// target, if it exists.
-func asStringSet(key string, target *sets.String) cm.ParseFunc {
-	return func(data map[string]string) error {
-		if raw, ok := data[key]; ok {
-			*target = sets.NewString(strings.Split(raw, ",")...)
-		}
-		return nil
-	}
-}
diff --git a/pkg/queue/stats.go b/pkg/queue/stats.go
index 3118b2d55..b727e4bc0 100644
--- a/pkg/queue/stats.go
+++ b/pkg/queue/stats.go
@@ -22,24 +22,26 @@ import (
 	"knative.dev/serving/pkg/network"
 )
 
-// NewStats instantiates a new instance of Stats.
-func NewStats(startedAt time.Time, reqCh chan network.ReqEvent, reportCh <-chan time.Time, report func(float64, float64, float64, float64)) {
-	go func() {
-		state := network.NewRequestStats(startedAt)
-
-		for {
-			select {
-			case event := <-reqCh:
-				state.HandleEvent(event)
-			case now := <-reportCh:
-				stats := state.Report(now)
-				report(
-					stats.AverageConcurrency,
-					stats.AverageProxiedConcurrency,
-					stats.RequestCount,
-					stats.ProxiedRequestCount,
-				)
+// ReportStats continually processes network events from reqCh and reports
+// aggregated stats via the `report` function whenever reportCh ticks.
+func ReportStats(startedAt time.Time, reqCh chan network.ReqEvent, reportCh <-chan time.Time, report func(float64, float64, float64, float64)) {
+	state := network.NewRequestStats(startedAt)
+
+	for {
+		select {
+		case event, ok := <-reqCh:
+			if !ok {
+				return
 			}
+			state.HandleEvent(event)
+		case now := <-reportCh:
+			stats := state.Report(now)
+			report(
+				stats.AverageConcurrency,
+				stats.AverageProxiedConcurrency,
+				stats.RequestCount,
+				stats.ProxiedRequestCount,
+			)
 		}
-	}()
+	}
 }
diff --git a/pkg/reconciler/autoscaling/kpa/scaler.go b/pkg/reconciler/autoscaling/kpa/scaler.go
index 3a5999212..71a4d72c4 100644
--- a/pkg/reconciler/autoscaling/kpa/scaler.go
+++ b/pkg/reconciler/autoscaling/kpa/scaler.go
@@ -32,6 +32,7 @@ import (
 	pav1alpha1 "knative.dev/serving/pkg/apis/autoscaling/v1alpha1"
 	"knative.dev/serving/pkg/apis/networking"
 	nv1a1 "knative.dev/serving/pkg/apis/networking/v1alpha1"
+	asconfig "knative.dev/serving/pkg/autoscaler/config"
 	"knative.dev/serving/pkg/network"
 	"knative.dev/serving/pkg/reconciler/autoscaling/config"
 	aresources "knative.dev/serving/pkg/reconciler/autoscaling/resources"
@@ -129,6 +130,14 @@ func activatorProbe(pa *pav1alpha1.PodAutoscaler, transport http.RoundTripper) (
 	return prober.Do(context.Background(), transport, paToProbeTarget(pa), probeOptions...)
 }
 
+func lastPodRetention(pa *pav1alpha1.PodAutoscaler, cfg *asconfig.Config) time.Duration {
+	d, ok := pa.ScaleToZeroPodRetention()
+	if ok {
+		return d
+	}
+	return cfg.ScaleToZeroPodRetentionPeriod
+}
+
 // pre: 0 <= min <= max && 0 <= x
 func applyBounds(min, max, x int32) int32 {
 	if x < min {
@@ -213,7 +222,7 @@ func (ks *scaler) handleScaleToZero(ctx context.Context, pa *pav1alpha1.PodAutos
 			// And at least ScaleToZeroPodRetentionPeriod since PA became inactive.
 
 			// Most conservative check, if it passes we're good.
-			lastPodTimeout := cfgAS.ScaleToZeroPodRetentionPeriod
+			lastPodTimeout := lastPodRetention(pa, cfgAS)
 			lastPodMaxTimeout := durationMax(cfgAS.ScaleToZeroGracePeriod, lastPodTimeout)
 			if pa.Status.CanScaleToZero(now, lastPodMaxTimeout) {
 				return desiredScale, true
diff --git a/pkg/reconciler/configuration/configuration.go b/pkg/reconciler/configuration/configuration.go
index da44dc858..31f7ee8cf 100644
--- a/pkg/reconciler/configuration/configuration.go
+++ b/pkg/reconciler/configuration/configuration.go
@@ -61,10 +61,6 @@ func (c *Reconciler) ReconcileKind(ctx context.Context, config *v1.Configuration
 	config.SetDefaults(ctx)
 	config.Status.InitializeConditions()
 
-	// Bump observed generation to denote that we have processed this
-	// generation regardless of success or failure.
-	config.Status.ObservedGeneration = config.Generation
-
 	// First, fetch the revision that should exist for the current generation.
 	lcr, err := c.latestCreatedRevision(config)
 	if errors.IsNotFound(err) {
diff --git a/pkg/reconciler/revision/revision.go b/pkg/reconciler/revision/revision.go
index caec98227..47546b435 100644
--- a/pkg/reconciler/revision/revision.go
+++ b/pkg/reconciler/revision/revision.go
@@ -168,7 +168,6 @@ func (c *Reconciler) ReconcileKind(ctx context.Context, rev *v1.Revision) pkgrec
 			"Revision becomes ready upon all resources being ready")
 	}
 
-	rev.Status.ObservedGeneration = rev.Generation
 	return nil
 }
 
diff --git a/pkg/reconciler/route/route.go b/pkg/reconciler/route/route.go
index 3977b4502..52272d3bb 100644
--- a/pkg/reconciler/route/route.go
+++ b/pkg/reconciler/route/route.go
@@ -190,6 +190,7 @@ func (c *Reconciler) reconcileIngressResources(ctx context.Context, r *v1.Route,
 func (c *Reconciler) tls(ctx context.Context, host string, r *v1.Route, traffic *traffic.Config) ([]netv1alpha1.IngressTLS, []netv1alpha1.HTTP01Challenge, error) {
 	tls := []netv1alpha1.IngressTLS{}
 	if !config.FromContext(ctx).Network.AutoTLS {
+		r.Status.MarkAutoTLSNotEnabled()
 		return tls, nil, nil
 	}
 	domainToTagMap, err := domains.GetAllDomainsAndTags(ctx, r, getTrafficNames(traffic.Targets), traffic.Visibility)
@@ -249,7 +250,7 @@ func (c *Reconciler) tls(ctx context.Context, host string, r *v1.Route, traffic
 		} else {
 			acmeChallenges = append(acmeChallenges, cert.Status.HTTP01Challenges...)
 			r.Status.MarkCertificateNotReady(cert.Name)
-			// When httpProtocol is enabled, downward http scheme.
+			// When httpProtocol is enabled, downgrade http scheme.
 			if config.FromContext(ctx).Network.HTTPProtocol == network.HTTPEnabled {
 				if dnsNames.Has(host) {
 					r.Status.URL = &apis.URL{
@@ -258,6 +259,7 @@ func (c *Reconciler) tls(ctx context.Context, host string, r *v1.Route, traffic
 					}
 				}
 				setTargetsScheme(&r.Status, dnsNames.List(), "http")
+				r.Status.MarkHTTPDowngrade(cert.Name)
 			}
 		}
 	}
diff --git a/pkg/reconciler/serverlessservice/serverlessservice.go b/pkg/reconciler/serverlessservice/serverlessservice.go
index 5e1e99146..16fd92427 100644
--- a/pkg/reconciler/serverlessservice/serverlessservice.go
+++ b/pkg/reconciler/serverlessservice/serverlessservice.go
@@ -82,7 +82,6 @@ func (r *reconciler) ReconcileKind(ctx context.Context, sks *netv1alpha1.Serverl
 			return err
 		}
 	}
-	sks.Status.ObservedGeneration = sks.Generation
 	return nil
 }
 
diff --git a/pkg/reconciler/service/service.go b/pkg/reconciler/service/service.go
index e20b3174c..666b2dc7f 100644
--- a/pkg/reconciler/service/service.go
+++ b/pkg/reconciler/service/service.go
@@ -117,8 +117,6 @@ func (c *Reconciler) ReconcileKind(ctx context.Context, service *v1.Service) pkg
 	}
 
 	c.checkRoutesNotReady(config, logger, route, service)
-	service.Status.ObservedGeneration = service.Generation
-
 	return nil
 }
 
diff --git a/third_party/VENDOR-LICENSE/github.com/prometheus/client_golang/LICENSE b/third_party/VENDOR-LICENSE/github.com/prometheus/client_golang/LICENSE
deleted file mode 100644
index 261eeb9e9..000000000
--- a/third_party/VENDOR-LICENSE/github.com/prometheus/client_golang/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/third_party/VENDOR-LICENSE/github.com/prometheus/client_golang/NOTICE b/third_party/VENDOR-LICENSE/github.com/prometheus/client_golang/NOTICE
deleted file mode 100644
index dd878a30e..000000000
--- a/third_party/VENDOR-LICENSE/github.com/prometheus/client_golang/NOTICE
+++ /dev/null
@@ -1,23 +0,0 @@
-Prometheus instrumentation library for Go applications
-Copyright 2012-2015 The Prometheus Authors
-
-This product includes software developed at
-SoundCloud Ltd. (http://soundcloud.com/).
-
-
-The following components are included in this product:
-
-perks - a fork of https://github.com/bmizerany/perks
-https://github.com/beorn7/perks
-Copyright 2013-2015 Blake Mizerany, Björn Rabenstein
-See https://github.com/beorn7/perks/blob/master/README.md for license details.
-
-Go support for Protocol Buffers - Google's data interchange format
-http://github.com/golang/protobuf/
-Copyright 2010 The Go Authors
-See source code for license details.
-
-Support for streaming Protocol Buffer messages for the Go language (golang).
-https://github.com/matttproud/golang_protobuf_extensions
-Copyright 2013 Matt T. Proud
-Licensed under the Apache License, Version 2.0
diff --git a/third_party/cert-manager-0.12.0/net-certmanager.yaml b/third_party/cert-manager-0.12.0/net-certmanager.yaml
index 9450683e9..d3afecab7 100644
--- a/third_party/cert-manager-0.12.0/net-certmanager.yaml
+++ b/third_party/cert-manager-0.12.0/net-certmanager.yaml
@@ -18,7 +18,7 @@ metadata:
   # These are the permissions needed by the `cert-manager` `Certificate` implementation.
   name: knative-serving-certmanager
   labels:
-    serving.knative.dev/release: "v20200501-507f8f4"
+    serving.knative.dev/release: "v20200525-e037dfb"
     serving.knative.dev/controller: "true"
     networking.knative.dev/certificate-provider: cert-manager
 rules:
@@ -44,13 +44,72 @@ rules:
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: ValidatingWebhookConfiguration
+metadata:
+  name: config.webhook.net-certmanager.networking.internal.knative.dev
+  labels:
+    serving.knative.dev/release: "v20200525-e037dfb"
+webhooks:
+- admissionReviewVersions:
+  - v1beta1
+  clientConfig:
+    service:
+      name: net-certmanager-webhook
+      namespace: knative-serving
+  failurePolicy: Fail
+  sideEffects: None
+  name: config.webhook.net-certmanager.networking.internal.knative.dev
+  namespaceSelector:
+    matchExpressions:
+    - key: serving.knative.dev/release
+      operator: Exists
+
+---
+# Copyright 2020 The Knative Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: v1
+kind: Secret
+metadata:
+  name: net-certmanager-webhook-certs
+  namespace: knative-serving
+  labels:
+    serving.knative.dev/release: "v20200525-e037dfb"
+
+---
+# Copyright 2020 The Knative Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: config-certmanager
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20200501-507f8f4"
+    serving.knative.dev/release: "v20200525-e037dfb"
     networking.knative.dev/certificate-provider: cert-manager
 data:
   _example: |
@@ -97,7 +156,7 @@ metadata:
   name: networking-certmanager
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20200501-507f8f4"
+    serving.knative.dev/release: "v20200525-e037dfb"
     networking.knative.dev/certificate-provider: cert-manager
 spec:
   selector:
@@ -109,14 +168,14 @@ spec:
         cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
       labels:
         app: networking-certmanager
-        serving.knative.dev/release: "v20200501-507f8f4"
+        serving.knative.dev/release: "v20200525-e037dfb"
     spec:
       serviceAccountName: controller
       containers:
       - name: networking-certmanager
         # This is the Go import path for the binary that is containerized
         # and substituted here.
-        image: gcr.io/knative-nightly/knative.dev/net-certmanager/cmd/controller@sha256:4906e7b85bdd1427c3e2281bd1344a6b1d7e717fdca62a40e8bd1a04ab8461b2
+        image: gcr.io/knative-nightly/knative.dev/net-certmanager/cmd/controller@sha256:db89c7334f1c5d3fec39178f2a9eb50ded8ff6eb39e8a579403d447e2a583efe
         resources:
           requests:
             cpu: 30m
@@ -149,7 +208,7 @@ kind: Service
 metadata:
   labels:
     app: networking-certmanager
-    serving.knative.dev/release: "v20200501-507f8f4"
+    serving.knative.dev/release: "v20200525-e037dfb"
     networking.knative.dev/certificate-provider: cert-manager
   name: networking-certmanager
   namespace: knative-serving
@@ -166,3 +225,114 @@ spec:
     app: networking-certmanager
 
 ---
+# Copyright 2020 The Knative Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: net-certmanager-webhook
+  namespace: knative-serving
+  labels:
+    serving.knative.dev/release: "v20200525-e037dfb"
+spec:
+  selector:
+    matchLabels:
+      app: net-certmanager-webhook
+      role: net-certmanager-webhook
+  template:
+    metadata:
+      annotations:
+        cluster-autoscaler.kubernetes.io/safe-to-evict: "false"
+      labels:
+        app: net-certmanager-webhook
+        role: net-certmanager-webhook
+        serving.knative.dev/release: "v20200525-e037dfb"
+    spec:
+      serviceAccountName: controller
+      containers:
+      - name: webhook
+        # This is the Go import path for the binary that is containerized
+        # and substituted here.
+        image: gcr.io/knative-nightly/knative.dev/net-certmanager/cmd/webhook@sha256:d7665647c960c714b07a53c6e1e91d0a3b2c76967aa0b6b0f292a13d11bd19d4
+        resources:
+          requests:
+            cpu: 20m
+            memory: 20Mi
+          limits:
+            cpu: 200m
+            memory: 200Mi
+        env:
+        - name: SYSTEM_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+        - name: CONFIG_LOGGING_NAME
+          value: config-logging
+        - name: CONFIG_OBSERVABILITY_NAME
+          value: config-observability
+        - # TODO(https://github.com/knative/pkg/pull/953): Remove stackdriver specific config
+          name: METRICS_DOMAIN
+          value: knative.dev/net-certmanager
+        - name: WEBHOOK_NAME
+          value: net-certmanager-webhook
+        securityContext:
+          allowPrivilegeEscalation: false
+        ports:
+        - name: metrics
+          containerPort: 9090
+        - name: profiling
+          containerPort: 8008
+        - name: https-webhook
+          containerPort: 8443
+
+---
+# Copyright 2020 The Knative Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: v1
+kind: Service
+metadata:
+  name: net-certmanager-webhook
+  namespace: knative-serving
+  labels:
+    role: net-certmanager-webhook
+    serving.knative.dev/release: "v20200525-e037dfb"
+spec:
+  ports:
+  - # Define metrics and profiling for them to be accessible within service meshes.
+    name: http-metrics
+    port: 9090
+    targetPort: 9090
+  - name: http-profiling
+    port: 8008
+    targetPort: 8008
+  - name: https-webhook
+    port: 443
+    targetPort: 8443
+  selector:
+    app: net-certmanager-webhook
+
+---
diff --git a/third_party/net-istio.yaml b/third_party/net-istio.yaml
index 90163c06b..3a85c59df 100644
--- a/third_party/net-istio.yaml
+++ b/third_party/net-istio.yaml
@@ -18,7 +18,7 @@ metadata:
   # These are the permissions needed by the Istio Ingress implementation.
   name: knative-serving-istio
   labels:
-    serving.knative.dev/release: "v20200514-0771529"
+    serving.knative.dev/release: "v20200522-dc16e1e"
     serving.knative.dev/controller: "true"
     networking.knative.dev/ingress-provider: istio
 rules:
@@ -48,7 +48,7 @@ metadata:
   name: knative-ingress-gateway
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20200514-0771529"
+    serving.knative.dev/release: "v20200522-dc16e1e"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -85,7 +85,7 @@ metadata:
   name: cluster-local-gateway
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20200514-0771529"
+    serving.knative.dev/release: "v20200522-dc16e1e"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -118,7 +118,7 @@ kind: MutatingWebhookConfiguration
 metadata:
   name: webhook.istio.networking.internal.knative.dev
   labels:
-    serving.knative.dev/release: "v20200514-0771529"
+    serving.knative.dev/release: "v20200522-dc16e1e"
     networking.knative.dev/ingress-provider: istio
 webhooks:
 - admissionReviewVersions:
@@ -154,7 +154,7 @@ kind: ValidatingWebhookConfiguration
 metadata:
   name: config.webhook.istio.networking.internal.knative.dev
   labels:
-    serving.knative.dev/release: "v20200514-0771529"
+    serving.knative.dev/release: "v20200522-dc16e1e"
     networking.knative.dev/ingress-provider: istio
 webhooks:
 - admissionReviewVersions:
@@ -192,7 +192,7 @@ metadata:
   name: istio-webhook-certs
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20200514-0771529"
+    serving.knative.dev/release: "v20200522-dc16e1e"
     networking.knative.dev/ingress-provider: istio
 
 ---
@@ -216,7 +216,7 @@ metadata:
   name: config-istio
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20200514-0771529"
+    serving.knative.dev/release: "v20200522-dc16e1e"
     networking.knative.dev/ingress-provider: istio
 data:
   _example: |
@@ -286,7 +286,7 @@ metadata:
   name: networking-istio
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20200514-0771529"
+    serving.knative.dev/release: "v20200522-dc16e1e"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -302,14 +302,14 @@ spec:
         sidecar.istio.io/inject: "false"
       labels:
         app: networking-istio
-        serving.knative.dev/release: "v20200514-0771529"
+        serving.knative.dev/release: "v20200522-dc16e1e"
     spec:
       serviceAccountName: controller
       containers:
       - name: networking-istio
         # This is the Go import path for the binary that is containerized
         # and substituted here.
-        image: gcr.io/knative-nightly/knative.dev/net-istio/cmd/controller@sha256:918468e505cffc9e861eb9897a18829c8c45f2ed3a945ea027d5085e6f5842aa
+        image: gcr.io/knative-nightly/knative.dev/net-istio/cmd/controller@sha256:250c68ac1458f524fb8aaf271747aa047b72d16b4e5a8800390c344266380fc9
         resources:
           requests:
             cpu: 30m
@@ -361,7 +361,7 @@ metadata:
   name: istio-webhook
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20200514-0771529"
+    serving.knative.dev/release: "v20200522-dc16e1e"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -375,14 +375,14 @@ spec:
       labels:
         app: istio-webhook
         role: istio-webhook
-        serving.knative.dev/release: "v20200514-0771529"
+        serving.knative.dev/release: "v20200522-dc16e1e"
     spec:
       serviceAccountName: controller
       containers:
       - name: webhook
         # This is the Go import path for the binary that is containerized
         # and substituted here.
-        image: gcr.io/knative-nightly/knative.dev/net-istio/cmd/webhook@sha256:2e62421e1c1f9bd1aef55eaf1664631b844c757ca958c3f3fd36c34db5ba8420
+        image: gcr.io/knative-nightly/knative.dev/net-istio/cmd/webhook@sha256:29978f12c8f25eeab4ecbcf1af73a9d65527f5a5331711197613d3b769089c6b
         resources:
           requests:
             cpu: 20m
@@ -436,7 +436,7 @@ metadata:
   namespace: knative-serving
   labels:
     role: istio-webhook
-    serving.knative.dev/release: "v20200514-0771529"
+    serving.knative.dev/release: "v20200522-dc16e1e"
     networking.knative.dev/ingress-provider: istio
 spec:
   ports:
diff --git a/vendor/github.com/prometheus/client_golang/api/client.go b/vendor/github.com/prometheus/client_golang/api/client.go
deleted file mode 100644
index 09af749ca..000000000
--- a/vendor/github.com/prometheus/client_golang/api/client.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2015 The Prometheus Authors
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build go1.7
-
-// Package api provides clients for the HTTP APIs.
-package api
-
-import (
-	"context"
-	"io/ioutil"
-	"net"
-	"net/http"
-	"net/url"
-	"path"
-	"strings"
-	"time"
-)
-
-// DefaultRoundTripper is used if no RoundTripper is set in Config.
-var DefaultRoundTripper http.RoundTripper = &http.Transport{
-	Proxy: http.ProxyFromEnvironment,
-	DialContext: (&net.Dialer{
-		Timeout:   30 * time.Second,
-		KeepAlive: 30 * time.Second,
-	}).DialContext,
-	TLSHandshakeTimeout: 10 * time.Second,
-}
-
-// Config defines configuration parameters for a new client.
-type Config struct {
-	// The address of the Prometheus to connect to.
-	Address string
-
-	// RoundTripper is used by the Client to drive HTTP requests. If not
-	// provided, DefaultRoundTripper will be used.
-	RoundTripper http.RoundTripper
-}
-
-func (cfg *Config) roundTripper() http.RoundTripper {
-	if cfg.RoundTripper == nil {
-		return DefaultRoundTripper
-	}
-	return cfg.RoundTripper
-}
-
-// Client is the interface for an API client.
-type Client interface {
-	URL(ep string, args map[string]string) *url.URL
-	Do(context.Context, *http.Request) (*http.Response, []byte, error)
-}
-
-// NewClient returns a new Client.
-//
-// It is safe to use the returned Client from multiple goroutines.
-func NewClient(cfg Config) (Client, error) {
-	u, err := url.Parse(cfg.Address)
-	if err != nil {
-		return nil, err
-	}
-	u.Path = strings.TrimRight(u.Path, "/")
-
-	return &httpClient{
-		endpoint: u,
-		client:   http.Client{Transport: cfg.roundTripper()},
-	}, nil
-}
-
-type httpClient struct {
-	endpoint *url.URL
-	client   http.Client
-}
-
-func (c *httpClient) URL(ep string, args map[string]string) *url.URL {
-	p := path.Join(c.endpoint.Path, ep)
-
-	for arg, val := range args {
-		arg = ":" + arg
-		p = strings.Replace(p, arg, val, -1)
-	}
-
-	u := *c.endpoint
-	u.Path = p
-
-	return &u
-}
-
-func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
-	if ctx != nil {
-		req = req.WithContext(ctx)
-	}
-	resp, err := c.client.Do(req)
-	defer func() {
-		if resp != nil {
-			resp.Body.Close()
-		}
-	}()
-
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var body []byte
-	done := make(chan struct{})
-	go func() {
-		body, err = ioutil.ReadAll(resp.Body)
-		close(done)
-	}()
-
-	select {
-	case <-ctx.Done():
-		err = resp.Body.Close()
-		<-done
-		if err == nil {
-			err = ctx.Err()
-		}
-	case <-done:
-	}
-
-	return resp, body, err
-}
diff --git a/vendor/github.com/prometheus/client_golang/api/prometheus/v1/api.go b/vendor/github.com/prometheus/client_golang/api/prometheus/v1/api.go
deleted file mode 100644
index 642418890..000000000
--- a/vendor/github.com/prometheus/client_golang/api/prometheus/v1/api.go
+++ /dev/null
@@ -1,519 +0,0 @@
-// Copyright 2017 The Prometheus Authors
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build go1.7
-
-// Package v1 provides bindings to the Prometheus HTTP API v1:
-// http://prometheus.io/docs/querying/api/
-package v1
-
-import (
-	"context"
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"strconv"
-	"time"
-
-	"github.com/prometheus/client_golang/api"
-	"github.com/prometheus/common/model"
-)
-
-const (
-	statusAPIError = 422
-
-	apiPrefix = "/api/v1"
-
-	epAlertManagers   = apiPrefix + "/alertmanagers"
-	epQuery           = apiPrefix + "/query"
-	epQueryRange      = apiPrefix + "/query_range"
-	epLabelValues     = apiPrefix + "/label/:name/values"
-	epSeries          = apiPrefix + "/series"
-	epTargets         = apiPrefix + "/targets"
-	epSnapshot        = apiPrefix + "/admin/tsdb/snapshot"
-	epDeleteSeries    = apiPrefix + "/admin/tsdb/delete_series"
-	epCleanTombstones = apiPrefix + "/admin/tsdb/clean_tombstones"
-	epConfig          = apiPrefix + "/status/config"
-	epFlags           = apiPrefix + "/status/flags"
-)
-
-// ErrorType models the different API error types.
-type ErrorType string
-
-// HealthStatus models the health status of a scrape target.
-type HealthStatus string
-
-const (
-	// Possible values for ErrorType.
-	ErrBadData     ErrorType = "bad_data"
-	ErrTimeout     ErrorType = "timeout"
-	ErrCanceled    ErrorType = "canceled"
-	ErrExec        ErrorType = "execution"
-	ErrBadResponse ErrorType = "bad_response"
-	ErrServer      ErrorType = "server_error"
-	ErrClient      ErrorType = "client_error"
-
-	// Possible values for HealthStatus.
-	HealthGood    HealthStatus = "up"
-	HealthUnknown HealthStatus = "unknown"
-	HealthBad     HealthStatus = "down"
-)
-
-// Error is an error returned by the API.
-type Error struct {
-	Type   ErrorType
-	Msg    string
-	Detail string
-}
-
-func (e *Error) Error() string {
-	return fmt.Sprintf("%s: %s", e.Type, e.Msg)
-}
-
-// Range represents a sliced time range.
-type Range struct {
-	// The boundaries of the time range.
-	Start, End time.Time
-	// The maximum time between two slices within the boundaries.
-	Step time.Duration
-}
-
-// API provides bindings for Prometheus's v1 API.
-type API interface {
-	// AlertManagers returns an overview of the current state of the Prometheus alert manager discovery.
-	AlertManagers(ctx context.Context) (AlertManagersResult, error)
-	// CleanTombstones removes the deleted data from disk and cleans up the existing tombstones.
-	CleanTombstones(ctx context.Context) error
-	// Config returns the current Prometheus configuration.
-	Config(ctx context.Context) (ConfigResult, error)
-	// DeleteSeries deletes data for a selection of series in a time range.
-	DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error
-	// Flags returns the flag values that Prometheus was launched with.
-	Flags(ctx context.Context) (FlagsResult, error)
-	// LabelValues performs a query for the values of the given label.
-	LabelValues(ctx context.Context, label string) (model.LabelValues, error)
-	// Query performs a query for the given time.
-	Query(ctx context.Context, query string, ts time.Time) (model.Value, error)
-	// QueryRange performs a query for the given range.
-	QueryRange(ctx context.Context, query string, r Range) (model.Value, error)
-	// Series finds series by label matchers.
-	Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error)
-	// Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand>
-	// under the TSDB's data directory and returns the directory as response.
-	Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error)
-	// Targets returns an overview of the current state of the Prometheus target discovery.
-	Targets(ctx context.Context) (TargetsResult, error)
-}
-
-// AlertManagersResult contains the result from querying the alertmanagers endpoint.
-type AlertManagersResult struct {
-	Active  []AlertManager `json:"activeAlertManagers"`
-	Dropped []AlertManager `json:"droppedAlertManagers"`
-}
-
-// AlertManager models a configured Alert Manager.
-type AlertManager struct {
-	URL string `json:"url"`
-}
-
-// ConfigResult contains the result from querying the config endpoint.
-type ConfigResult struct {
-	YAML string `json:"yaml"`
-}
-
-// FlagsResult contains the result from querying the flag endpoint.
-type FlagsResult map[string]string
-
-// SnapshotResult contains the result from querying the snapshot endpoint.
-type SnapshotResult struct {
-	Name string `json:"name"`
-}
-
-// TargetsResult contains the result from querying the targets endpoint.
-type TargetsResult struct {
-	Active  []ActiveTarget  `json:"activeTargets"`
-	Dropped []DroppedTarget `json:"droppedTargets"`
-}
-
-// ActiveTarget models an active Prometheus scrape target.
-type ActiveTarget struct {
-	DiscoveredLabels model.LabelSet `json:"discoveredLabels"`
-	Labels           model.LabelSet `json:"labels"`
-	ScrapeURL        string         `json:"scrapeUrl"`
-	LastError        string         `json:"lastError"`
-	LastScrape       time.Time      `json:"lastScrape"`
-	Health           HealthStatus   `json:"health"`
-}
-
-// DroppedTarget models a dropped Prometheus scrape target.
-type DroppedTarget struct {
-	DiscoveredLabels model.LabelSet `json:"discoveredLabels"`
-}
-
-// queryResult contains result data for a query.
-type queryResult struct {
-	Type   model.ValueType `json:"resultType"`
-	Result interface{}     `json:"result"`
-
-	// The decoded value.
-	v model.Value
-}
-
-func (qr *queryResult) UnmarshalJSON(b []byte) error {
-	v := struct {
-		Type   model.ValueType `json:"resultType"`
-		Result json.RawMessage `json:"result"`
-	}{}
-
-	err := json.Unmarshal(b, &v)
-	if err != nil {
-		return err
-	}
-
-	switch v.Type {
-	case model.ValScalar:
-		var sv model.Scalar
-		err = json.Unmarshal(v.Result, &sv)
-		qr.v = &sv
-
-	case model.ValVector:
-		var vv model.Vector
-		err = json.Unmarshal(v.Result, &vv)
-		qr.v = vv
-
-	case model.ValMatrix:
-		var mv model.Matrix
-		err = json.Unmarshal(v.Result, &mv)
-		qr.v = mv
-
-	default:
-		err = fmt.Errorf("unexpected value type %q", v.Type)
-	}
-	return err
-}
-
-// NewAPI returns a new API for the client.
-//
-// It is safe to use the returned API from multiple goroutines.
-func NewAPI(c api.Client) API {
-	return &httpAPI{client: apiClient{c}}
-}
-
-type httpAPI struct {
-	client api.Client
-}
-
-func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, error) {
-	u := h.client.URL(epAlertManagers, nil)
-
-	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
-	if err != nil {
-		return AlertManagersResult{}, err
-	}
-
-	_, body, err := h.client.Do(ctx, req)
-	if err != nil {
-		return AlertManagersResult{}, err
-	}
-
-	var res AlertManagersResult
-	err = json.Unmarshal(body, &res)
-	return res, err
-}
-
-func (h *httpAPI) CleanTombstones(ctx context.Context) error {
-	u := h.client.URL(epCleanTombstones, nil)
-
-	req, err := http.NewRequest(http.MethodPost, u.String(), nil)
-	if err != nil {
-		return err
-	}
-
-	_, _, err = h.client.Do(ctx, req)
-	return err
-}
-
-func (h *httpAPI) Config(ctx context.Context) (ConfigResult, error) {
-	u := h.client.URL(epConfig, nil)
-
-	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
-	if err != nil {
-		return ConfigResult{}, err
-	}
-
-	_, body, err := h.client.Do(ctx, req)
-	if err != nil {
-		return ConfigResult{}, err
-	}
-
-	var res ConfigResult
-	err = json.Unmarshal(body, &res)
-	return res, err
-}
-
-func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error {
-	u := h.client.URL(epDeleteSeries, nil)
-	q := u.Query()
-
-	for _, m := range matches {
-		q.Add("match[]", m)
-	}
-
-	q.Set("start", startTime.Format(time.RFC3339Nano))
-	q.Set("end", endTime.Format(time.RFC3339Nano))
-
-	u.RawQuery = q.Encode()
-
-	req, err := http.NewRequest(http.MethodPost, u.String(), nil)
-	if err != nil {
-		return err
-	}
-
-	_, _, err = h.client.Do(ctx, req)
-	return err
-}
-
-func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, error) {
-	u := h.client.URL(epFlags, nil)
-
-	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
-	if err != nil {
-		return FlagsResult{}, err
-	}
-
-	_, body, err := h.client.Do(ctx, req)
-	if err != nil {
-		return FlagsResult{}, err
-	}
-
-	var res FlagsResult
-	err = json.Unmarshal(body, &res)
-	return res, err
-}
-
-func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, error) {
-	u := h.client.URL(epLabelValues, map[string]string{"name": label})
-	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
-	if err != nil {
-		return nil, err
-	}
-	_, body, err := h.client.Do(ctx, req)
-	if err != nil {
-		return nil, err
-	}
-	var labelValues model.LabelValues
-	err = json.Unmarshal(body, &labelValues)
-	return labelValues, err
-}
-
-func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) {
-	u := h.client.URL(epQuery, nil)
-	q := u.Query()
-
-	q.Set("query", query)
-	if !ts.IsZero() {
-		q.Set("time", ts.Format(time.RFC3339Nano))
-	}
-
-	u.RawQuery = q.Encode()
-
-	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
-	if err != nil {
-		return nil, err
-	}
-
-	_, body, err := h.client.Do(ctx, req)
-	if err != nil {
-		return nil, err
-	}
-
-	var qres queryResult
-	err = json.Unmarshal(body, &qres)
-
-	return model.Value(qres.v), err
-}
-
-func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) {
-	u := h.client.URL(epQueryRange, nil)
-	q := u.Query()
-
-	var (
-		start = r.Start.Format(time.RFC3339Nano)
-		end   = r.End.Format(time.RFC3339Nano)
-		step  = strconv.FormatFloat(r.Step.Seconds(), 'f', 3, 64)
-	)
-
-	q.Set("query", query)
-	q.Set("start", start)
-	q.Set("end", end)
-	q.Set("step", step)
-
-	u.RawQuery = q.Encode()
-
-	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
-	if err != nil {
-		return nil, err
-	}
-
-	_, body, err := h.client.Do(ctx, req)
-	if err != nil {
-		return nil, err
-	}
-
-	var qres queryResult
-	err = json.Unmarshal(body, &qres)
-
-	return model.Value(qres.v), err
-}
-
-func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error) {
-	u := h.client.URL(epSeries, nil)
-	q := u.Query()
-
-	for _, m := range matches {
-		q.Add("match[]", m)
-	}
-
-	q.Set("start", startTime.Format(time.RFC3339Nano))
-	q.Set("end", endTime.Format(time.RFC3339Nano))
-
-	u.RawQuery = q.Encode()
-
-	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
-	if err != nil {
-		return nil, err
-	}
-
-	_, body, err := h.client.Do(ctx, req)
-	if err != nil {
-		return nil, err
-	}
-
-	var mset []model.LabelSet
-	err = json.Unmarshal(body, &mset)
-	return mset, err
-}
-
-func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) {
-	u := h.client.URL(epSnapshot, nil)
-	q := u.Query()
-
-	q.Set("skip_head", strconv.FormatBool(skipHead))
-
-	u.RawQuery = q.Encode()
-
-	req, err := http.NewRequest(http.MethodPost, u.String(), nil)
-	if err != nil {
-		return SnapshotResult{}, err
-	}
-
-	_, body, err := h.client.Do(ctx, req)
-	if err != nil {
-		return SnapshotResult{}, err
-	}
-
-	var res SnapshotResult
-	err = json.Unmarshal(body, &res)
-	return res, err
-}
-
-func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, error) {
-	u := h.client.URL(epTargets, nil)
-
-	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
-	if err != nil {
-		return TargetsResult{}, err
-	}
-
-	_, body, err := h.client.Do(ctx, req)
-	if err != nil {
-		return TargetsResult{}, err
-	}
-
-	var res TargetsResult
-	err = json.Unmarshal(body, &res)
-	return res, err
-}
-
-// apiClient wraps a regular client and processes successful API responses.
-// Successful also includes responses that errored at the API level.
-type apiClient struct {
-	api.Client
-}
-
-type apiResponse struct {
-	Status    string          `json:"status"`
-	Data      json.RawMessage `json:"data"`
-	ErrorType ErrorType       `json:"errorType"`
-	Error     string          `json:"error"`
-}
-
-func apiError(code int) bool {
-	// These are the codes that Prometheus sends when it returns an error.
-	return code == statusAPIError || code == http.StatusBadRequest
-}
-
-func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) {
-	switch resp.StatusCode / 100 {
-	case 4:
-		return ErrClient, fmt.Sprintf("client error: %d", resp.StatusCode)
-	case 5:
-		return ErrServer, fmt.Sprintf("server error: %d", resp.StatusCode)
-	}
-	return ErrBadResponse, fmt.Sprintf("bad response code %d", resp.StatusCode)
-}
-
-func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
-	resp, body, err := c.Client.Do(ctx, req)
-	if err != nil {
-		return resp, body, err
-	}
-
-	code := resp.StatusCode
-
-	if code/100 != 2 && !apiError(code) {
-		errorType, errorMsg := errorTypeAndMsgFor(resp)
-		return resp, body, &Error{
-			Type:   errorType,
-			Msg:    errorMsg,
-			Detail: string(body),
-		}
-	}
-
-	var result apiResponse
-
-	if http.StatusNoContent != code {
-		if err = json.Unmarshal(body, &result); err != nil {
-			return resp, body, &Error{
-				Type: ErrBadResponse,
-				Msg:  err.Error(),
-			}
-		}
-	}
-
-	if apiError(code) != (result.Status == "error") {
-		err = &Error{
-			Type: ErrBadResponse,
-			Msg:  "inconsistent body for response code",
-		}
-	}
-
-	if apiError(code) && result.Status == "error" {
-		err = &Error{
-			Type: result.ErrorType,
-			Msg:  result.Error,
-		}
-	}
-
-	return resp, []byte(result.Data), err
-}
diff --git a/vendor/knative.dev/pkg/configmap/parse.go b/vendor/knative.dev/pkg/configmap/parse.go
index 097b38f90..6479598b7 100644
--- a/vendor/knative.dev/pkg/configmap/parse.go
+++ b/vendor/knative.dev/pkg/configmap/parse.go
@@ -17,9 +17,12 @@ limitations under the License.
 package configmap
 
 import (
+	"fmt"
 	"strconv"
 	"strings"
 	"time"
+
+	"k8s.io/apimachinery/pkg/util/sets"
 )
 
 // ParseFunc is a function taking ConfigMap data and applying a parse operation to it.
@@ -51,7 +54,7 @@ func AsInt32(key string, target *int32) ParseFunc {
 		if raw, ok := data[key]; ok {
 			val, err := strconv.ParseInt(raw, 10, 32)
 			if err != nil {
-				return err
+				return fmt.Errorf("failed to parse %q: %w", key, err)
 			}
 			*target = int32(val)
 		}
@@ -65,7 +68,7 @@ func AsInt64(key string, target *int64) ParseFunc {
 		if raw, ok := data[key]; ok {
 			val, err := strconv.ParseInt(raw, 10, 64)
 			if err != nil {
-				return err
+				return fmt.Errorf("failed to parse %q: %w", key, err)
 			}
 			*target = val
 		}
@@ -79,7 +82,7 @@ func AsFloat64(key string, target *float64) ParseFunc {
 		if raw, ok := data[key]; ok {
 			val, err := strconv.ParseFloat(raw, 64)
 			if err != nil {
-				return err
+				return fmt.Errorf("failed to parse %q: %w", key, err)
 			}
 			*target = val
 		}
@@ -93,7 +96,7 @@ func AsDuration(key string, target *time.Duration) ParseFunc {
 		if raw, ok := data[key]; ok {
 			val, err := time.ParseDuration(raw)
 			if err != nil {
-				return err
+				return fmt.Errorf("failed to parse %q: %w", key, err)
 			}
 			*target = val
 		}
@@ -101,6 +104,16 @@ func AsDuration(key string, target *time.Duration) ParseFunc {
 	}
 }
 
+// AsStringSet parses the value at key as a sets.String (split by ',') into the target, if it exists.
+func AsStringSet(key string, target *sets.String) ParseFunc {
+	return func(data map[string]string) error {
+		if raw, ok := data[key]; ok {
+			*target = sets.NewString(strings.Split(raw, ",")...)
+		}
+		return nil
+	}
+}
+
 // Parse parses the given map using the parser functions passed in.
 func Parse(data map[string]string, parsers ...ParseFunc) error {
 	for _, parse := range parsers {
diff --git a/vendor/knative.dev/pkg/hack/update-deps.sh b/vendor/knative.dev/pkg/hack/update-deps.sh
index 8baa2558f..71b1bf26f 100644
--- a/vendor/knative.dev/pkg/hack/update-deps.sh
+++ b/vendor/knative.dev/pkg/hack/update-deps.sh
@@ -26,7 +26,7 @@ cd ${ROOT_DIR}
 # The list of dependencies that we track at HEAD and periodically
 # float forward in this repository.
 FLOATING_DEPS=(
-  "knative.dev/test-infra"
+  "knative.dev/test-infra@release-0.15"
 )
 
 # Parse flags to determine any we should pass to dep.
diff --git a/vendor/knative.dev/pkg/leaderelection/config.go b/vendor/knative.dev/pkg/leaderelection/config.go
index 0ec0377ed..59ab45905 100644
--- a/vendor/knative.dev/pkg/leaderelection/config.go
+++ b/vendor/knative.dev/pkg/leaderelection/config.go
@@ -19,12 +19,13 @@ package leaderelection
 import (
 	"fmt"
 	"os"
-	"strings"
 	"time"
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/apimachinery/pkg/util/uuid"
+
+	cm "knative.dev/pkg/configmap"
 )
 
 const ConfigMapNameEnv = "CONFIG_LEADERELECTION_NAME"
@@ -35,41 +36,23 @@ var validResourceLocks = sets.NewString("leases", "configmaps", "endpoints")
 func NewConfigFromMap(data map[string]string) (*Config, error) {
 	config := defaultConfig()
 
-	if resourceLock, ok := data["resourceLock"]; ok {
-		if !validResourceLocks.Has(resourceLock) {
-			return nil, fmt.Errorf(`resourceLock: invalid value %q: valid values are "leases","configmaps","endpoints"`, resourceLock)
-		}
-		config.ResourceLock = resourceLock
-	}
+	if err := cm.Parse(data,
+		cm.AsString("resourceLock", &config.ResourceLock),
 
-	for _, d := range []struct {
-		key string
-		val *time.Duration
-	}{{
-		"leaseDuration",
-		&config.LeaseDuration,
-	}, {
-		"renewDeadline",
-		&config.RenewDeadline,
-	}, {
-		"retryPeriod",
-		&config.RetryPeriod,
-	}} {
-		if v, ok := data[d.key]; ok {
-			dur, err := time.ParseDuration(v)
-			if err != nil {
-				return nil, fmt.Errorf("%s: invalid duration: %q", d.key, v)
-			}
-			*d.val = dur
-		}
+		cm.AsDuration("leaseDuration", &config.LeaseDuration),
+		cm.AsDuration("renewDeadline", &config.RenewDeadline),
+		cm.AsDuration("retryPeriod", &config.RetryPeriod),
+
+		// enabledComponents are not validated here, because they are dependent on
+		// the component. Components should provide additional validation for this
+		// field.
+		cm.AsStringSet("enabledComponents", &config.EnabledComponents),
+	); err != nil {
+		return nil, err
 	}
 
-	// enabledComponents are not validated here, because they are dependent on
-	// the component. Components should provide additional validation for this
-	// field.
-	if enabledComponents, ok := data["enabledComponents"]; ok {
-		tokens := strings.Split(enabledComponents, ",")
-		config.EnabledComponents = sets.NewString(tokens...)
+	if !validResourceLocks.Has(config.ResourceLock) {
+		return nil, fmt.Errorf(`resourceLock: invalid value %q: valid values are "leases","configmaps","endpoints"`, config.ResourceLock)
 	}
 
 	return config, nil
diff --git a/vendor/knative.dev/pkg/metrics/config_observability.go b/vendor/knative.dev/pkg/metrics/config_observability.go
index e40cb2fb2..e4560ede0 100644
--- a/vendor/knative.dev/pkg/metrics/config_observability.go
+++ b/vendor/knative.dev/pkg/metrics/config_observability.go
@@ -18,10 +18,10 @@ package metrics
 
 import (
 	"os"
-	"strings"
 	texttemplate "text/template"
 
 	corev1 "k8s.io/api/core/v1"
+	cm "knative.dev/pkg/configmap"
 )
 
 const (
@@ -71,32 +71,23 @@ func defaultConfig() *ObservabilityConfig {
 // NewObservabilityConfigFromConfigMap creates a ObservabilityConfig from the supplied ConfigMap
 func NewObservabilityConfigFromConfigMap(configMap *corev1.ConfigMap) (*ObservabilityConfig, error) {
 	oc := defaultConfig()
-	if evlc, ok := configMap.Data["logging.enable-var-log-collection"]; ok {
-		oc.EnableVarLogCollection = strings.EqualFold(evlc, "true")
-	}
 
-	if rut, ok := configMap.Data["logging.revision-url-template"]; ok {
-		oc.LoggingURLTemplate = rut
+	if err := cm.Parse(configMap.Data,
+		cm.AsBool("logging.enable-var-log-collection", &oc.EnableVarLogCollection),
+		cm.AsString("logging.revision-url-template", &oc.LoggingURLTemplate),
+		cm.AsString("logging.request-log-template", &oc.RequestLogTemplate),
+		cm.AsBool("logging.enable-probe-request-log", &oc.EnableProbeRequestLog),
+		cm.AsString("metrics.request-metrics-backend-destination", &oc.RequestMetricsBackend),
+		cm.AsBool("profiling.enable", &oc.EnableProfiling),
+	); err != nil {
+		return nil, err
 	}
 
-	if rlt, ok := configMap.Data["logging.request-log-template"]; ok {
+	if oc.RequestLogTemplate != "" {
 		// Verify that we get valid templates.
-		if _, err := texttemplate.New("requestLog").Parse(rlt); err != nil {
+		if _, err := texttemplate.New("requestLog").Parse(oc.RequestLogTemplate); err != nil {
 			return nil, err
 		}
-		oc.RequestLogTemplate = rlt
-	}
-
-	if eprl, ok := configMap.Data["logging.enable-probe-request-log"]; ok {
-		oc.EnableProbeRequestLog = strings.EqualFold(eprl, "true")
-	}
-
-	if mb, ok := configMap.Data["metrics.request-metrics-backend-destination"]; ok {
-		oc.RequestMetricsBackend = mb
-	}
-
-	if prof, ok := configMap.Data["profiling.enable"]; ok {
-		oc.EnableProfiling = strings.EqualFold(prof, "true")
 	}
 
 	return oc, nil
diff --git a/vendor/knative.dev/pkg/reconciler/reconcile_common.go b/vendor/knative.dev/pkg/reconciler/reconcile_common.go
index 8af08f61a..cc0e2d712 100644
--- a/vendor/knative.dev/pkg/reconciler/reconcile_common.go
+++ b/vendor/knative.dev/pkg/reconciler/reconcile_common.go
@@ -48,8 +48,9 @@ func PostProcessReconcile(ctx context.Context, resource duckv1.KRShaped) {
 	// generation regardless of success or failure.
 	newStatus.ObservedGeneration = resource.GetGeneration()
 
-	rc := mgr.GetTopLevelCondition()
-	if rc.Reason == failedGenerationBump {
+	if rc := mgr.GetTopLevelCondition(); rc == nil {
+		logger.Warn("A reconciliation included no top-level condition")
+	} else if rc.Reason == failedGenerationBump {
 		logger.Warn("A reconciler observed a new generation without updating the resource status")
 	}
 }
diff --git a/vendor/knative.dev/pkg/tracing/config/tracing.go b/vendor/knative.dev/pkg/tracing/config/tracing.go
index 67aec651c..b3aceee16 100644
--- a/vendor/knative.dev/pkg/tracing/config/tracing.go
+++ b/vendor/knative.dev/pkg/tracing/config/tracing.go
@@ -25,6 +25,7 @@ import (
 
 	"cloud.google.com/go/compute/metadata"
 	corev1 "k8s.io/api/core/v1"
+	cm "knative.dev/pkg/configmap"
 )
 
 const (
@@ -98,15 +99,20 @@ func NewTracingConfigFromMap(cfgMap map[string]string) (*Config, error) {
 		}
 	}
 
-	if endpoint, ok := cfgMap[zipkinEndpointKey]; !ok && tc.Backend == Zipkin {
+	if err := cm.Parse(cfgMap,
+		cm.AsString(zipkinEndpointKey, &tc.ZipkinEndpoint),
+		cm.AsString(stackdriverProjectIDKey, &tc.StackdriverProjectID),
+		cm.AsBool(debugKey, &tc.Debug),
+		cm.AsFloat64(sampleRateKey, &tc.SampleRate),
+	); err != nil {
+		return nil, err
+	}
+
+	if tc.Backend == Zipkin && tc.ZipkinEndpoint == "" {
 		return nil, errors.New("zipkin tracing enabled without a zipkin endpoint specified")
-	} else {
-		tc.ZipkinEndpoint = endpoint
 	}
 
-	if projectID, ok := cfgMap[stackdriverProjectIDKey]; ok {
-		tc.StackdriverProjectID = projectID
-	} else if tc.Backend == Stackdriver {
+	if tc.Backend == Stackdriver && tc.StackdriverProjectID == "" {
 		projectID, err := metadata.ProjectID()
 		if err != nil {
 			return nil, fmt.Errorf("stackdriver tracing enabled without a project-id specified: %w", err)
@@ -114,23 +120,8 @@ func NewTracingConfigFromMap(cfgMap map[string]string) (*Config, error) {
 		tc.StackdriverProjectID = projectID
 	}
 
-	if debug, ok := cfgMap[debugKey]; ok {
-		debugBool, err := strconv.ParseBool(debug)
-		if err != nil {
-			return nil, fmt.Errorf("failed parsing tracing config %q", debugKey)
-		}
-		tc.Debug = debugBool
-	}
-
-	if sampleRate, ok := cfgMap[sampleRateKey]; ok {
-		sampleRateFloat, err := strconv.ParseFloat(sampleRate, 64)
-		if err != nil {
-			return nil, fmt.Errorf("failed to parse sampleRate in tracing config: %w", err)
-		}
-		if sampleRateFloat < 0 || sampleRateFloat > 1 {
-			return nil, fmt.Errorf("sampleRate = %v must be in [0, 1] range", sampleRateFloat)
-		}
-		tc.SampleRate = sampleRateFloat
+	if tc.SampleRate < 0 || tc.SampleRate > 1 {
+		return nil, fmt.Errorf("sample-rate = %v must be in [0, 1] range", tc.SampleRate)
 	}
 
 	return tc, nil
diff --git a/vendor/modules.txt b/vendor/modules.txt
index aa4bc3fcc..b70eb18b9 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -308,8 +308,6 @@ github.com/pborman/uuid
 github.com/pkg/errors
 # github.com/prometheus/client_golang v1.5.0 => github.com/prometheus/client_golang v0.9.2
 ## explicit
-github.com/prometheus/client_golang/api
-github.com/prometheus/client_golang/api/prometheus/v1
 github.com/prometheus/client_golang/prometheus
 github.com/prometheus/client_golang/prometheus/internal
 github.com/prometheus/client_golang/prometheus/promhttp
@@ -1148,7 +1146,7 @@ k8s.io/utils/integer
 k8s.io/utils/path
 k8s.io/utils/pointer
 k8s.io/utils/trace
-# knative.dev/caching v0.0.0-20200515173804-a2fc9c5de2ef
+# knative.dev/caching v0.0.0-20200521155757-e78d17bc250e
 ## explicit
 knative.dev/caching/config
 knative.dev/caching/pkg/apis/caching
@@ -1169,7 +1167,7 @@ knative.dev/caching/pkg/client/injection/informers/caching/v1alpha1/image/fake
 knative.dev/caching/pkg/client/injection/informers/factory
 knative.dev/caching/pkg/client/injection/informers/factory/fake
 knative.dev/caching/pkg/client/listers/caching/v1alpha1
-# knative.dev/pkg v0.0.0-20200517014302-3c29a1ce8906
+# knative.dev/pkg v0.0.0-20200519155757-14eb3ae3a5a7
 ## explicit
 knative.dev/pkg/apiextensions/storageversion
 knative.dev/pkg/apiextensions/storageversion/cmd/migrate
@@ -1257,7 +1255,6 @@ knative.dev/pkg/test/mako/alerter/slack
 knative.dev/pkg/test/mako/config
 knative.dev/pkg/test/monitoring
 knative.dev/pkg/test/performance
-knative.dev/pkg/test/prometheus
 knative.dev/pkg/test/prow
 knative.dev/pkg/test/slackutil
 knative.dev/pkg/test/spoof
@@ -1285,7 +1282,7 @@ knative.dev/pkg/webhook/resourcesemantics/conversion
 knative.dev/pkg/webhook/resourcesemantics/defaulting
 knative.dev/pkg/webhook/resourcesemantics/validation
 knative.dev/pkg/websocket
-# knative.dev/test-infra v0.0.0-20200515184601-7a28f47cdbcb
+# knative.dev/test-infra v0.0.0-20200519161858-554a95a37986
 ## explicit
 knative.dev/test-infra/scripts
 # sigs.k8s.io/structured-merge-diff v1.0.1


Test patch:
diff --git a/cmd/queue/main_test.go b/cmd/queue/main_test.go
index 8e53a65bf..99282012c 100644
--- a/cmd/queue/main_test.go
+++ b/cmd/queue/main_test.go
@@ -517,7 +517,7 @@ func BenchmarkProxyHandler(b *testing.B) {
 	if err != nil {
 		b.Fatal("Failed to create stats reporter:", err)
 	}
-	queue.NewStats(time.Now(), reqChan, reportTicker.C, promStatReporter.Report)
+	go queue.ReportStats(time.Now(), reqChan, reportTicker.C, promStatReporter.Report)
 	req := httptest.NewRequest(http.MethodPost, "http://example.com", nil)
 	req.Header.Set(network.OriginalHostHeader, wantHost)
 
diff --git a/pkg/activator/net/throttler_test.go b/pkg/activator/net/throttler_test.go
index 4933568da..624d5ab09 100644
--- a/pkg/activator/net/throttler_test.go
+++ b/pkg/activator/net/throttler_test.go
@@ -439,8 +439,15 @@ func TestThrottlerSuccesses(t *testing.T) {
 			}
 
 			// Make sure our informer event has fired.
+			// We send multiple updates in some tests, so make sure the capacity is exact.
+			wantCapacity := 1
+			cc := tc.revision.Spec.ContainerConcurrency
+			if cc != nil && *cc != 0 {
+				dests := tc.initUpdates[len(tc.initUpdates)-1].Dests.Len()
+				wantCapacity = dests * int(*cc)
+			}
 			if err := wait.PollImmediate(10*time.Millisecond, 3*time.Second, func() (bool, error) {
-				return atomic.LoadInt32(&rt.activatorIndex) != -1 && rt.breaker.Capacity() > 0, nil
+				return atomic.LoadInt32(&rt.activatorIndex) != -1 && rt.breaker.Capacity() == wantCapacity, nil
 			}); err != nil {
 				t.Fatal("Timed out waiting for the capacity to be updated")
 			}
@@ -449,11 +456,15 @@ func TestThrottlerSuccesses(t *testing.T) {
 			tryContext, cancel2 := context.WithTimeout(context.Background(), 1*time.Second)
 			defer cancel2()
 
+			waitGrp := sync.WaitGroup{}
+			waitGrp.Add(tc.requests)
 			resultChan := throttler.try(tryContext, tc.requests, func(string) error {
-				// Simulate proxying.
-				time.Sleep(50 * time.Millisecond)
+				waitGrp.Done()
+				// Wait for all requests to reach the Try function before proceeding.
+				waitGrp.Wait()
 				return nil
 			})
+
 			gotDests := sets.NewString()
 			for i := 0; i < tc.requests; i++ {
 				result := <-resultChan
diff --git a/pkg/apis/autoscaling/annotation_validation_test.go b/pkg/apis/autoscaling/annotation_validation_test.go
index 1d3a536c6..dbb18ea27 100644
--- a/pkg/apis/autoscaling/annotation_validation_test.go
+++ b/pkg/apis/autoscaling/annotation_validation_test.go
@@ -45,23 +45,27 @@ func TestValidateScaleBoundAnnotations(t *testing.T) {
 	}, {
 		name:        "minScale is -1",
 		annotations: map[string]string{MinScaleAnnotationKey: "-1"},
-		expectErr:   "expected 1 <= -1 <= 2147483647: " + MinScaleAnnotationKey,
+		expectErr:   "expected 0 <= -1 <= 2147483647: " + MinScaleAnnotationKey,
+	}, {
+		name:        "maxScale is huuuuuuuge",
+		annotations: map[string]string{MaxScaleAnnotationKey: "2147483648"},
+		expectErr:   "expected 0 <= 2147483648 <= 2147483647: " + MaxScaleAnnotationKey,
 	}, {
 		name:        "maxScale is -1",
 		annotations: map[string]string{MaxScaleAnnotationKey: "-1"},
-		expectErr:   "expected 1 <= -1 <= 2147483647: " + MaxScaleAnnotationKey,
+		expectErr:   "expected 0 <= -1 <= 2147483647: " + MaxScaleAnnotationKey,
 	}, {
 		name:        "minScale is foo",
 		annotations: map[string]string{MinScaleAnnotationKey: "foo"},
-		expectErr:   "expected 1 <= foo <= 2147483647: " + MinScaleAnnotationKey,
+		expectErr:   "invalid value: foo: " + MinScaleAnnotationKey,
 	}, {
 		name:        "maxScale is bar",
 		annotations: map[string]string{MaxScaleAnnotationKey: "bar"},
-		expectErr:   "expected 1 <= bar <= 2147483647: " + MaxScaleAnnotationKey,
+		expectErr:   "invalid value: bar: " + MaxScaleAnnotationKey,
 	}, {
 		name:        "max/minScale is bar",
 		annotations: map[string]string{MaxScaleAnnotationKey: "bar", MinScaleAnnotationKey: "bar"},
-		expectErr:   "expected 1 <= bar <= 2147483647: " + MaxScaleAnnotationKey + ", " + MinScaleAnnotationKey,
+		expectErr:   "invalid value: bar: " + MaxScaleAnnotationKey + ", " + MinScaleAnnotationKey,
 	}, {
 		name:        "minScale is 5",
 		annotations: map[string]string{MinScaleAnnotationKey: "5"},
@@ -192,6 +196,24 @@ func TestValidateScaleBoundAnnotations(t *testing.T) {
 		name:        "invalid format and valid class for /window annotation",
 		annotations: map[string]string{WindowAnnotationKey: "jerry-was-a-racecar-driver", ClassAnnotationKey: KPA},
 		expectErr:   "invalid value: jerry-was-a-racecar-driver: " + WindowAnnotationKey,
+	}, {
+		name:        "valid 0 last pod scaledown timeout",
+		annotations: map[string]string{ScaleToZeroPodRetentionPeriodKey: "0"},
+	}, {
+		name:        "valid positive last pod scaledown timeout",
+		annotations: map[string]string{ScaleToZeroPodRetentionPeriodKey: "21m31s"},
+	}, {
+		name:        "invalid positive last pod scaledown timeout",
+		annotations: map[string]string{ScaleToZeroPodRetentionPeriodKey: "311m"},
+		expectErr:   "expected 0s <= 311m <= 1h0m0s: " + ScaleToZeroPodRetentionPeriodKey,
+	}, {
+		name:        "invalid negative last pod scaledown timeout",
+		annotations: map[string]string{ScaleToZeroPodRetentionPeriodKey: "-42s"},
+		expectErr:   "expected 0s <= -42s <= 1h0m0s: " + ScaleToZeroPodRetentionPeriodKey,
+	}, {
+		name:        "invalid last pod scaledown timeout",
+		annotations: map[string]string{ScaleToZeroPodRetentionPeriodKey: "twenty-two-minutes-and-five-seconds"},
+		expectErr:   "invalid value: twenty-two-minutes-and-five-seconds: " + ScaleToZeroPodRetentionPeriodKey,
 	}, {
 		name: "all together now fail",
 		annotations: map[string]string{
@@ -200,7 +222,7 @@ func TestValidateScaleBoundAnnotations(t *testing.T) {
 			MinScaleAnnotationKey:                 "-4",
 			MaxScaleAnnotationKey:                 "never",
 		},
-		expectErr: "expected 1 <= -11 <= 100: " + PanicWindowPercentageAnnotationKey + "\nexpected 1 <= -4 <= 2147483647: " + MinScaleAnnotationKey + "\nexpected 1 <= never <= 2147483647: " + MaxScaleAnnotationKey + "\ninvalid value: fifty: " + PanicThresholdPercentageAnnotationKey,
+		expectErr: "expected 0 <= -4 <= 2147483647: " + MinScaleAnnotationKey + "\nexpected 1 <= -11 <= 100: " + PanicWindowPercentageAnnotationKey + "\ninvalid value: fifty: " + PanicThresholdPercentageAnnotationKey + "\ninvalid value: never: " + MaxScaleAnnotationKey,
 	}, {
 		name: "all together now, succeed",
 		annotations: map[string]string{
@@ -258,7 +280,7 @@ func TestValidateScaleBoundAnnotations(t *testing.T) {
 	for _, c := range cases {
 		t.Run(c.name, func(t *testing.T) {
 			if got, want := ValidateAnnotations(c.allowInitScaleZero, c.annotations).Error(), c.expectErr; !reflect.DeepEqual(got, want) {
-				t.Errorf("Err = %q, want: %q, diff:\n%s", got, want, cmp.Diff(got, want))
+				t.Errorf("\nErr = %q,\nwant: %q, diff(-want,+got):\n%s", got, want, cmp.Diff(want, got))
 			}
 		})
 	}
diff --git a/pkg/apis/autoscaling/v1alpha1/metric_types_test.go b/pkg/apis/autoscaling/v1alpha1/metric_types_test.go
index 8c0363982..3f1de95fc 100644
--- a/pkg/apis/autoscaling/v1alpha1/metric_types_test.go
+++ b/pkg/apis/autoscaling/v1alpha1/metric_types_test.go
@@ -27,7 +27,7 @@ func TestMetricGetStatus(t *testing.T) {
 	}
 
 	if got, want := r.GetStatus(), &r.Status.Status; got != want {
-		t.Errorf("GotStatus=%v, want=%v", got, want)
+		t.Errorf("GetStatus=%v, want=%v", got, want)
 	}
 }
 
diff --git a/pkg/apis/autoscaling/v1alpha1/pa_defaults_test.go b/pkg/apis/autoscaling/v1alpha1/pa_defaults_test.go
index 52e60a508..253d42002 100644
--- a/pkg/apis/autoscaling/v1alpha1/pa_defaults_test.go
+++ b/pkg/apis/autoscaling/v1alpha1/pa_defaults_test.go
@@ -60,6 +60,12 @@ func TestPodAutoscalerDefaulting(t *testing.T) {
 				},
 				Data: map[string]string{},
 			})
+			s.OnConfigChanged(&v1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: config.FeaturesConfigName,
+				},
+				Data: map[string]string{},
+			})
 			s.OnConfigChanged(&v1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: autoscalerconfig.ConfigName,
diff --git a/pkg/apis/autoscaling/v1alpha1/pa_lifecycle_test.go b/pkg/apis/autoscaling/v1alpha1/pa_lifecycle_test.go
index 9272dac8c..24635dd3c 100644
--- a/pkg/apis/autoscaling/v1alpha1/pa_lifecycle_test.go
+++ b/pkg/apis/autoscaling/v1alpha1/pa_lifecycle_test.go
@@ -245,7 +245,7 @@ func TestInactiveFor(t *testing.T) {
 
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
-			if got, want := tc.status.InactiveFor(now), tc.result; absDiff(got, want) > 10*time.Millisecond {
+			if got, want := tc.status.InactiveFor(now), tc.result; got != want {
 				t.Errorf("InactiveFor = %v, want: %v", got, want)
 			}
 		})
@@ -329,21 +329,13 @@ func TestActiveFor(t *testing.T) {
 
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
-			if got, want := tc.status.ActiveFor(now), tc.result; absDiff(got, want) > 10*time.Millisecond {
+			if got, want := tc.status.ActiveFor(now), tc.result; got != want {
 				t.Errorf("ActiveFor = %v, want: %v", got, want)
 			}
 		})
 	}
 }
 
-func absDiff(a, b time.Duration) time.Duration {
-	a -= b
-	if a < 0 {
-		a *= -1
-	}
-	return a
-}
-
 func TestCanFailActivation(t *testing.T) {
 	now := time.Now()
 	cases := []struct {
@@ -417,8 +409,8 @@ func TestCanFailActivation(t *testing.T) {
 
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
-			if e, a := tc.result, tc.status.CanFailActivation(now, tc.grace); e != a {
-				t.Errorf("%q expected: %v got: %v", tc.name, e, a)
+			if got, want := tc.status.CanFailActivation(now, tc.grace), tc.result; got != want {
+				t.Errorf("CanFailActivation = %v, want: %v", got, want)
 			}
 		})
 	}
@@ -469,9 +461,11 @@ func TestIsActivating(t *testing.T) {
 	}}
 
 	for _, tc := range cases {
-		if e, a := tc.isActivating, tc.status.IsActivating(); e != a {
-			t.Errorf("%q expected: %v got: %v", tc.name, e, a)
-		}
+		t.Run(tc.name, func(t *testing.T) {
+			if got, want := tc.status.IsActivating(), tc.isActivating; got != want {
+				t.Errorf("IsActivating = %v, want: %v", got, want)
+			}
+		})
 	}
 }
 
@@ -569,9 +563,11 @@ func TestIsReady(t *testing.T) {
 	}}
 
 	for _, tc := range cases {
-		if e, a := tc.isReady, tc.status.IsReady(); e != a {
-			t.Errorf("%q expected: %v got: %v", tc.name, e, a)
-		}
+		t.Run(tc.name, func(t *testing.T) {
+			if got, want := tc.status.IsReady(), tc.isReady; got != want {
+				t.Errorf("IsReady = %v, want: %v", got, want)
+			}
+		})
 	}
 }
 
@@ -613,10 +609,10 @@ func TestTargetAnnotation(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			gotTarget, gotOK := tc.pa.Target()
 			if gotTarget != tc.wantTarget {
-				t.Errorf("got target: %v wanted: %v", gotTarget, tc.wantTarget)
+				t.Errorf("Target = %v; want: %v", gotTarget, tc.wantTarget)
 			}
 			if gotOK != tc.wantOK {
-				t.Errorf("got ok: %v wanted %v", gotOK, tc.wantOK)
+				t.Errorf("OK = %v; want: %v", gotOK, tc.wantOK)
 			}
 		})
 	}
@@ -823,7 +819,14 @@ func TestWindowAnnotation(t *testing.T) {
 		pa: pa(map[string]string{
 			autoscaling.WindowAnnotationKey: "120s",
 		}),
-		wantWindow: time.Second * 120,
+		wantWindow: 120 * time.Second,
+		wantOK:     true,
+	}, {
+		name: "complex",
+		pa: pa(map[string]string{
+			autoscaling.WindowAnnotationKey: "2m33s",
+		}),
+		wantWindow: 153 * time.Second,
 		wantOK:     true,
 	}, {
 		name: "invalid",
@@ -838,10 +841,10 @@ func TestWindowAnnotation(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			gotWindow, gotOK := tc.pa.Window()
 			if gotWindow != tc.wantWindow {
-				t.Errorf("%q expected target: %v got: %v", tc.name, tc.wantWindow, gotWindow)
+				t.Errorf("Window = %v, want: %v", gotWindow, tc.wantWindow)
 			}
 			if gotOK != tc.wantOK {
-				t.Errorf("%q expected ok: %v got %v", tc.name, tc.wantOK, gotOK)
+				t.Errorf("OK = %v, want: %v", gotOK, tc.wantOK)
 			}
 		})
 	}
@@ -878,10 +881,10 @@ func TestPanicWindowPercentageAnnotation(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			gotPercentage, gotOK := tc.pa.PanicWindowPercentage()
 			if gotPercentage != tc.wantPercentage {
-				t.Errorf("%q expected target: %v got: %v", tc.name, tc.wantPercentage, gotPercentage)
+				t.Errorf("PanicWindowPercentage = %v, want: %v", gotPercentage, tc.wantPercentage)
 			}
 			if gotOK != tc.wantOK {
-				t.Errorf("%q expected ok: %v got %v", tc.name, tc.wantOK, gotOK)
+				t.Errorf("OK = %v, want: %v", gotOK, tc.wantOK)
 			}
 		})
 	}
@@ -918,10 +921,10 @@ func TestTargetUtilization(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			got, gotOK := tc.pa.TargetUtilization()
 			if got, want := got, tc.want; got != want {
-				t.Errorf("%q target utilization: %v want: %v", tc.name, got, want)
+				t.Errorf("TU = %v, want: %v", got, want)
 			}
 			if gotOK != tc.wantOK {
-				t.Errorf("%q expected ok: %v got %v", tc.name, tc.wantOK, gotOK)
+				t.Errorf("OK = %v, want: %v", gotOK, tc.wantOK)
 			}
 		})
 	}
@@ -951,17 +954,17 @@ func TestPanicThresholdPercentage(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			gotPercentage, gotOK := tc.pa.PanicThresholdPercentage()
 			if gotPercentage != tc.wantPercentage {
-				t.Errorf("%q expected target: %v got: %v", tc.name, tc.wantPercentage, gotPercentage)
+				t.Errorf("PanicThresholdPercentage = %v, want: %v", gotPercentage, tc.wantPercentage)
 			}
 			if gotOK != tc.wantOK {
-				t.Errorf("%q expected ok: %v got %v", tc.name, tc.wantOK, gotOK)
+				t.Errorf("OK = %v, want: %v", gotOK, tc.wantOK)
 			}
 		})
 	}
 }
 
 func pa(annotations map[string]string) *PodAutoscaler {
-	p := &PodAutoscaler{
+	return &PodAutoscaler{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace:   "test-namespace",
 			Name:        "test-name",
@@ -972,7 +975,6 @@ func pa(annotations map[string]string) *PodAutoscaler {
 		},
 		Status: PodAutoscalerStatus{},
 	}
-	return p
 }
 
 func TestTypicalFlow(t *testing.T) {
@@ -1021,7 +1023,6 @@ func TestTargetBC(t *testing.T) {
 	}{{
 		name: "not present",
 		pa:   pa(map[string]string{}),
-		want: 0.0,
 	}, {
 		name: "present",
 		pa: pa(map[string]string{
@@ -1048,17 +1049,16 @@ func TestTargetBC(t *testing.T) {
 		pa: pa(map[string]string{
 			autoscaling.TargetBurstCapacityKey: "NPH",
 		}),
-		want: 0.0,
 	}}
 
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			got, gotOK := tc.pa.TargetBC()
 			if got, want := got, tc.want; got != want {
-				t.Errorf("%q target burst capacity: %v want: %v", tc.name, got, want)
+				t.Errorf("TBC = %v, want: %v", got, want)
 			}
 			if gotOK != tc.wantOK {
-				t.Errorf("%q expected ok: %v got %v", tc.name, tc.wantOK, gotOK)
+				t.Errorf("OK = %v, want: %v", gotOK, tc.wantOK)
 			}
 		})
 	}
@@ -1094,3 +1094,49 @@ func TestPodAutoscalerGetGroupVersionKind(t *testing.T) {
 		t.Errorf("got: %v, want: %v", got, want)
 	}
 }
+
+func TestScaleToZeroPodRetention(t *testing.T) {
+	cases := []struct {
+		name   string
+		pa     *PodAutoscaler
+		want   time.Duration
+		wantOK bool
+	}{{
+		name: "nil",
+		pa:   pa(nil),
+	}, {
+		name: "not present",
+		pa:   pa(map[string]string{}),
+	}, {
+		name: "present",
+		pa: pa(map[string]string{
+			autoscaling.ScaleToZeroPodRetentionPeriodKey: "311s",
+		}),
+		want:   311 * time.Second,
+		wantOK: true,
+	}, {
+		name: "complex",
+		pa: pa(map[string]string{
+			autoscaling.ScaleToZeroPodRetentionPeriodKey: "4m21s",
+		}),
+		want:   261 * time.Second,
+		wantOK: true,
+	}, {
+		name: "invalid",
+		pa: pa(map[string]string{
+			autoscaling.ScaleToZeroPodRetentionPeriodKey: "365d",
+		}),
+	}}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			got, gotOK := tc.pa.ScaleToZeroPodRetention()
+			if got != tc.want {
+				t.Errorf("ScaleToZeroPodRetention = %v, want: %v", got, tc.want)
+			}
+			if gotOK != tc.wantOK {
+				t.Errorf("OK = %v, want: %v", gotOK, tc.wantOK)
+			}
+		})
+	}
+}
diff --git a/pkg/apis/autoscaling/v1alpha1/pa_types_test.go b/pkg/apis/autoscaling/v1alpha1/pa_types_test.go
index 865fb297b..c2b80debd 100644
--- a/pkg/apis/autoscaling/v1alpha1/pa_types_test.go
+++ b/pkg/apis/autoscaling/v1alpha1/pa_types_test.go
@@ -27,7 +27,7 @@ func TestPodAutoscalerGetStatus(t *testing.T) {
 	}
 
 	if got, want := r.GetStatus(), &r.Status.Status; got != want {
-		t.Errorf("GotStatus=%v, want=%v", got, want)
+		t.Errorf("GetStatus=%v, want=%v", got, want)
 	}
 }
 
diff --git a/pkg/apis/autoscaling/v1alpha1/pa_validation_test.go b/pkg/apis/autoscaling/v1alpha1/pa_validation_test.go
index 68574c2e8..b3defc968 100644
--- a/pkg/apis/autoscaling/v1alpha1/pa_validation_test.go
+++ b/pkg/apis/autoscaling/v1alpha1/pa_validation_test.go
@@ -18,7 +18,6 @@ package v1alpha1
 
 import (
 	"context"
-	"math"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -208,7 +207,7 @@ func TestPodAutoscalerValidation(t *testing.T) {
 				ProtocolType: net.ProtocolHTTP1,
 			},
 		},
-		want: apis.ErrOutOfBoundsValue("FOO", 1, math.MaxInt32, autoscaling.MinScaleAnnotationKey).ViaField("metadata", "annotations"),
+		want: apis.ErrInvalidValue("FOO", autoscaling.MinScaleAnnotationKey).ViaField("metadata", "annotations"),
 	}, {
 		name: "empty spec",
 		r: &PodAutoscaler{
diff --git a/pkg/apis/config/defaults_test.go b/pkg/apis/config/defaults_test.go
index b534f4eb4..e5c99a163 100644
--- a/pkg/apis/config/defaults_test.go
+++ b/pkg/apis/config/defaults_test.go
@@ -35,21 +35,22 @@ func TestDefaultsConfigurationFromFile(t *testing.T) {
 	cm, example := ConfigMapsFromTestFile(t, DefaultsConfigName)
 
 	if _, err := NewDefaultsConfigFromConfigMap(cm); err != nil {
-		t.Errorf("NewDefaultsConfigFromConfigMap(actual) = %v", err)
+		t.Error("NewDefaultsConfigFromConfigMap(actual) =", err)
 	}
 
-	if got, err := NewDefaultsConfigFromConfigMap(example); err != nil {
-		t.Errorf("NewDefaultsConfigFromConfigMap(example) = %v", err)
-	} else {
-		// Those are in example, to show usage,
-		// but default is nil, i.e. inheriting k8s.
-		// So for this test we ignore those, but verify the other fields.
-		got.RevisionCPULimit, got.RevisionCPURequest = nil, nil
-		got.RevisionMemoryLimit, got.RevisionMemoryRequest = nil, nil
-		if want := defaultConfig(); !cmp.Equal(got, want) {
-			t.Errorf("Example does not represent default config: diff(-want,+got)\n%s",
-				cmp.Diff(want, got))
-		}
+	got, err := NewDefaultsConfigFromConfigMap(example)
+	if err != nil {
+		t.Fatal("NewDefaultsConfigFromConfigMap(example) =", err)
+	}
+
+	// Those are in example, to show usage,
+	// but default is nil, i.e. inheriting k8s.
+	// So for this test we ignore those, but verify the other fields.
+	got.RevisionCPULimit, got.RevisionCPURequest = nil, nil
+	got.RevisionMemoryLimit, got.RevisionMemoryRequest = nil, nil
+	want := defaultDefaultsConfig()
+	if diff := cmp.Diff(want, got); diff != "" {
+		t.Error("Example does not represent default config: diff(-want,+got)\n", diff)
 	}
 }
 
@@ -64,22 +65,19 @@ func TestDefaultsConfiguration(t *testing.T) {
 	}{{
 		name:         "default configuration",
 		wantErr:      false,
-		wantDefaults: defaultConfig(),
+		wantDefaults: defaultDefaultsConfig(),
 		data:         map[string]string{},
 	}, {
 		name:    "specified values",
 		wantErr: false,
 		wantDefaults: &Defaults{
-			EnableMultiContainer:          true,
-			RevisionTimeoutSeconds:        123,
-			MaxRevisionTimeoutSeconds:     456,
-			ContainerConcurrencyMaxLimit:  1984,
-			RevisionCPURequest:            &oneTwoThree,
-			UserContainerNameTemplate:     "{{.Name}}",
-			AllowContainerConcurrencyZero: false,
+			RevisionTimeoutSeconds:       123,
+			MaxRevisionTimeoutSeconds:    456,
+			ContainerConcurrencyMaxLimit: 1984,
+			RevisionCPURequest:           &oneTwoThree,
+			UserContainerNameTemplate:    "{{.Name}}",
 		},
 		data: map[string]string{
-			"enable-multi-container":           "true",
 			"revision-timeout-seconds":         "123",
 			"max-revision-timeout-seconds":     "456",
 			"revision-cpu-request":             "123m",
@@ -91,7 +89,6 @@ func TestDefaultsConfiguration(t *testing.T) {
 		name:    "invalid multi container flag value",
 		wantErr: false,
 		wantDefaults: &Defaults{
-			EnableMultiContainer:          false,
 			RevisionTimeoutSeconds:        DefaultRevisionTimeoutSeconds,
 			MaxRevisionTimeoutSeconds:     DefaultMaxRevisionTimeoutSeconds,
 			UserContainerNameTemplate:     DefaultUserContainerName,
@@ -105,7 +102,6 @@ func TestDefaultsConfiguration(t *testing.T) {
 		name:    "invalid allow container concurrency zero flag value",
 		wantErr: false,
 		wantDefaults: &Defaults{
-			EnableMultiContainer:          false,
 			RevisionTimeoutSeconds:        DefaultRevisionTimeoutSeconds,
 			MaxRevisionTimeoutSeconds:     DefaultMaxRevisionTimeoutSeconds,
 			UserContainerNameTemplate:     DefaultUserContainerName,
@@ -212,7 +208,7 @@ func TestTemplating(t *testing.T) {
 				},
 			})
 			if err != nil {
-				t.Errorf("Error parsing defaults: %v", err)
+				t.Fatal("Error parsing defaults:", err)
 			}
 
 			ctx := apis.WithinParent(context.Background(), metav1.ObjectMeta{
diff --git a/pkg/apis/config/features_test.go b/pkg/apis/config/features_test.go
new file mode 100644
index 000000000..d0972ca84
--- /dev/null
+++ b/pkg/apis/config/features_test.go
@@ -0,0 +1,93 @@
+/*
+Copyright 2020 The Knative Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package config
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	corev1 "k8s.io/api/core/v1"
+	. "knative.dev/pkg/configmap/testing"
+	_ "knative.dev/pkg/system/testing"
+)
+
+func TestFeaturesConfigurationFromFile(t *testing.T) {
+	cm, example := ConfigMapsFromTestFile(t, FeaturesConfigName)
+
+	if _, err := NewFeaturesConfigFromConfigMap(cm); err != nil {
+		t.Error("NewFeaturesConfigFromConfigMap(actual) =", err)
+	}
+
+	got, err := NewFeaturesConfigFromConfigMap(example)
+	if err != nil {
+		t.Fatal("NewFeaturesConfigFromConfigMap(example) =", err)
+	}
+
+	want := defaultFeaturesConfig()
+	if diff := cmp.Diff(want, got); diff != "" {
+		t.Error("Example does not represent default config: diff(-want,+got)\n", diff)
+	}
+}
+
+func TestFeaturesConfiguration(t *testing.T) {
+	configTests := []struct {
+		name         string
+		wantErr      bool
+		wantFeatures *Features
+		data         map[string]string
+	}{{
+		name:         "default configuration",
+		wantErr:      false,
+		wantFeatures: defaultFeaturesConfig(),
+		data:         map[string]string{},
+	}, {
+		name:    "multi-container Allowed",
+		wantErr: false,
+		wantFeatures: &Features{
+			MultiContainer: Allowed,
+		},
+		data: map[string]string{
+			"multi-container": "Allowed",
+		},
+	}, {
+		name:    "multi-container Enabled",
+		wantErr: false,
+		wantFeatures: &Features{
+			MultiContainer: Enabled,
+		},
+		data: map[string]string{
+			"multi-container": "Enabled",
+		},
+	}}
+
+	for _, tt := range configTests {
+		t.Run(tt.name, func(t *testing.T) {
+			actualFeatures, err := NewFeaturesConfigFromConfigMap(&corev1.ConfigMap{
+				Data: tt.data,
+			})
+
+			if (err != nil) != tt.wantErr {
+				t.Fatalf("NewFeaturesConfigFromConfigMap() error = %v, WantErr %v", err, tt.wantErr)
+			}
+
+			got, want := actualFeatures, tt.wantFeatures
+			if diff := cmp.Diff(want, got); diff != "" {
+				t.Error("Config mismatch: diff(-want,+got):\n", diff)
+			}
+		})
+	}
+}
diff --git a/pkg/apis/config/store_test.go b/pkg/apis/config/store_test.go
index 11ec92d35..2debf2d59 100644
--- a/pkg/apis/config/store_test.go
+++ b/pkg/apis/config/store_test.go
@@ -37,9 +37,11 @@ func TestStoreLoadWithContext(t *testing.T) {
 	store := NewStore(logtesting.TestLogger(t))
 
 	defaultsConfig := ConfigMapFromTestFile(t, DefaultsConfigName)
+	featuresConfig := ConfigMapFromTestFile(t, FeaturesConfigName)
 	autoscalerConfig := ConfigMapFromTestFile(t, autoscalerconfig.ConfigName)
 
 	store.OnConfigChanged(defaultsConfig)
+	store.OnConfigChanged(featuresConfig)
 	store.OnConfigChanged(autoscalerConfig)
 
 	config := FromContextOrDefaults(store.ToContext(context.Background()))
@@ -47,34 +49,49 @@ func TestStoreLoadWithContext(t *testing.T) {
 	t.Run("defaults", func(t *testing.T) {
 		expected, _ := NewDefaultsConfigFromConfigMap(defaultsConfig)
 		if diff := cmp.Diff(expected, config.Defaults, ignoreStuff...); diff != "" {
-			t.Errorf("Unexpected defaults config (-want, +got): %v", diff)
+			t.Errorf("Unexpected defaults config (-want, +got):\n%v", diff)
+		}
+	})
+
+	t.Run("features", func(t *testing.T) {
+		expected, _ := NewFeaturesConfigFromConfigMap(featuresConfig)
+		if diff := cmp.Diff(expected, config.Features, ignoreStuff...); diff != "" {
+			t.Errorf("Unexpected features config (-want, +got):\n%v", diff)
 		}
 	})
 
 	t.Run("autoscaler", func(t *testing.T) {
 		expected, _ := autoscalerconfig.NewConfigFromConfigMap(autoscalerConfig)
 		if diff := cmp.Diff(expected, config.Autoscaler, ignoreStuff...); diff != "" {
-			t.Errorf("Unexpected autoscaler config (-want, +got): %v", diff)
+			t.Errorf("Unexpected autoscaler config (-want, +got):\n%v", diff)
 		}
 	})
 }
 
 func TestStoreLoadWithContextOrDefaults(t *testing.T) {
 	defaultsConfig := ConfigMapFromTestFile(t, DefaultsConfigName)
+	featuresConfig := ConfigMapFromTestFile(t, FeaturesConfigName)
 	autoscalerConfig := ConfigMapFromTestFile(t, autoscalerconfig.ConfigName)
 	config := FromContextOrDefaults(context.Background())
 
 	t.Run("defaults", func(t *testing.T) {
 		expected, _ := NewDefaultsConfigFromConfigMap(defaultsConfig)
 		if diff := cmp.Diff(expected, config.Defaults, ignoreStuff...); diff != "" {
-			t.Errorf("Unexpected defaults config (-want, +got): %v", diff)
+			t.Errorf("Unexpected defaults config (-want, +got):\n%v", diff)
+		}
+	})
+
+	t.Run("features", func(t *testing.T) {
+		expected, _ := NewFeaturesConfigFromConfigMap(featuresConfig)
+		if diff := cmp.Diff(expected, config.Features, ignoreStuff...); diff != "" {
+			t.Errorf("Unexpected features config (-want, +got):\n%v", diff)
 		}
 	})
 
 	t.Run("autoscaler", func(t *testing.T) {
 		expected, _ := autoscalerconfig.NewConfigFromConfigMap(autoscalerConfig)
 		if diff := cmp.Diff(expected, config.Autoscaler, ignoreStuff...); diff != "" {
-			t.Errorf("Unexpected autoscaler config (-want, +got): %v", diff)
+			t.Errorf("Unexpected autoscaler config (-want, +got):\n%v", diff)
 		}
 	})
 }
@@ -83,11 +100,13 @@ func TestStoreImmutableConfig(t *testing.T) {
 	store := NewStore(logtesting.TestLogger(t))
 
 	store.OnConfigChanged(ConfigMapFromTestFile(t, DefaultsConfigName))
+	store.OnConfigChanged(ConfigMapFromTestFile(t, FeaturesConfigName))
 	store.OnConfigChanged(ConfigMapFromTestFile(t, autoscalerconfig.ConfigName))
 
 	config := store.Load()
 
 	config.Defaults.RevisionTimeoutSeconds = 1234
+	config.Features.MultiContainer = Enabled
 	config.Autoscaler.TargetBurstCapacity = 99
 
 	newConfig := store.Load()
@@ -96,6 +115,10 @@ func TestStoreImmutableConfig(t *testing.T) {
 		t.Error("Defaults config is not immutable")
 	}
 
+	if newConfig.Features.MultiContainer == Enabled {
+		t.Error("Features config is not immutable")
+	}
+
 	if newConfig.Autoscaler.TargetBurstCapacity == 99 {
 		t.Error("Autoscaler config is not immutable")
 	}
diff --git a/pkg/apis/config/testdata/config-features.yaml b/pkg/apis/config/testdata/config-features.yaml
new file mode 120000
index 000000000..e9cda5cbe
--- /dev/null
+++ b/pkg/apis/config/testdata/config-features.yaml
@@ -0,0 +1,1 @@
+../../../../config/core/configmaps/features.yaml
\ No newline at end of file
diff --git a/pkg/apis/networking/v1alpha1/certificate_lifecycle_test.go b/pkg/apis/networking/v1alpha1/certificate_lifecycle_test.go
index eed31127a..066b1a563 100644
--- a/pkg/apis/networking/v1alpha1/certificate_lifecycle_test.go
+++ b/pkg/apis/networking/v1alpha1/certificate_lifecycle_test.go
@@ -49,7 +49,7 @@ func TestCertificateGetConditionSet(t *testing.T) {
 	r := &Certificate{}
 
 	if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want {
-		t.Errorf("GotConditionSet=%v, want=%v", got, want)
+		t.Errorf("GetConditionSet=%v, want=%v", got, want)
 	}
 }
 
diff --git a/pkg/apis/networking/v1alpha1/certificate_types_test.go b/pkg/apis/networking/v1alpha1/certificate_types_test.go
index f14385455..8764b0dc8 100644
--- a/pkg/apis/networking/v1alpha1/certificate_types_test.go
+++ b/pkg/apis/networking/v1alpha1/certificate_types_test.go
@@ -17,8 +17,6 @@ package v1alpha1
 
 import (
 	"testing"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 func TestCertificateGetStatus(t *testing.T) {
@@ -27,16 +25,6 @@ func TestCertificateGetStatus(t *testing.T) {
 	}
 
 	if got, want := r.GetStatus(), &r.Status.Status; got != want {
-		t.Errorf("GotStatus=%v, want=%v", got, want)
-	}
-}
-
-func TestCertificateGetObjectMeta(t *testing.T) {
-	r := &Certificate{
-		TypeMeta: metav1.TypeMeta{},
-	}
-
-	if got, want := r.GetTypeMeta(), &r.TypeMeta; got != want {
-		t.Errorf("GotTypeMeta=%v, want=%v", got, want)
+		t.Errorf("GetStatus=%v, want=%v", got, want)
 	}
 }
diff --git a/pkg/apis/networking/v1alpha1/ingress_lifecycle_test.go b/pkg/apis/networking/v1alpha1/ingress_lifecycle_test.go
index c4bf343da..af2d8fa11 100644
--- a/pkg/apis/networking/v1alpha1/ingress_lifecycle_test.go
+++ b/pkg/apis/networking/v1alpha1/ingress_lifecycle_test.go
@@ -49,7 +49,7 @@ func TestIngressGetConditionSet(t *testing.T) {
 	r := &Ingress{}
 
 	if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want {
-		t.Errorf("GotConditionSet=%v, want=%v", got, want)
+		t.Errorf("GetConditionSet=%v, want=%v", got, want)
 	}
 }
 
diff --git a/pkg/apis/networking/v1alpha1/ingress_types_test.go b/pkg/apis/networking/v1alpha1/ingress_types_test.go
index 9ee4bc096..1f9e91ec5 100644
--- a/pkg/apis/networking/v1alpha1/ingress_types_test.go
+++ b/pkg/apis/networking/v1alpha1/ingress_types_test.go
@@ -17,8 +17,6 @@ package v1alpha1
 
 import (
 	"testing"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 func TestIngressGetStatus(t *testing.T) {
@@ -27,16 +25,6 @@ func TestIngressGetStatus(t *testing.T) {
 	}
 
 	if got, want := r.GetStatus(), &r.Status.Status; got != want {
-		t.Errorf("GotStatus=%v, want=%v", got, want)
-	}
-}
-
-func TestIngressGetObjectMeta(t *testing.T) {
-	r := &Ingress{
-		TypeMeta: metav1.TypeMeta{},
-	}
-
-	if got, want := r.GetTypeMeta(), &r.TypeMeta; got != want {
-		t.Errorf("GotTypeMeta=%v, want=%v", got, want)
+		t.Errorf("GetStatus=%v, want=%v", got, want)
 	}
 }
diff --git a/pkg/apis/networking/v1alpha1/serverlessservice_lifecycle_test.go b/pkg/apis/networking/v1alpha1/serverlessservice_lifecycle_test.go
index 56408e07a..88205aaef 100644
--- a/pkg/apis/networking/v1alpha1/serverlessservice_lifecycle_test.go
+++ b/pkg/apis/networking/v1alpha1/serverlessservice_lifecycle_test.go
@@ -48,7 +48,7 @@ func TestServerlessServiceDuckTypes(t *testing.T) {
 func TestServerlessServiceGetConditionSet(t *testing.T) {
 	r := &ServerlessService{}
 	if got, want := r.GetConditionSet().GetTopLevelConditionType(), apis.ConditionReady; got != want {
-		t.Errorf("GotConditionSet=%v, want=%v", got, want)
+		t.Errorf("GetConditionSet=%v, want=%v", got, want)
 	}
 }
 
diff --git a/pkg/apis/networking/v1alpha1/serverlessservice_types_test.go b/pkg/apis/networking/v1alpha1/serverlessservice_types_test.go
index b862630a2..662025263 100644
--- a/pkg/apis/networking/v1alpha1/serverlessservice_types_test.go
+++ b/pkg/apis/networking/v1alpha1/serverlessservice_types_test.go
@@ -17,8 +17,6 @@ package v1alpha1
 
 import (
 	"testing"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 func TestServerlessServiceGetStatus(t *testing.T) {
@@ -27,16 +25,6 @@ func TestServerlessServiceGetStatus(t *testing.T) {
 	}
 
 	if got, want := r.GetStatus(), &r.Status.Status; got != want {
-		t.Errorf("GotStatus=%v, want=%v", got, want)
-	}
-}
-
-func TestServerlessServiceGetObjectMeta(t *testing.T) {
-	r := &ServerlessService{
-		TypeMeta: metav1.TypeMeta{},
-	}
-
-	if got, want := r.GetTypeMeta(), &r.TypeMeta; got != want {
-		t.Errorf("GotTypeMeta=%v, want=%v", got, want)
+		t.Errorf("GetStatus=%v, want=%v", got, want)
 	}
 }
diff --git a/pkg/apis/serving/k8s_validation_test.go b/pkg/apis/serving/k8s_validation_test.go
index a644e8380..e78a74a2d 100644
--- a/pkg/apis/serving/k8s_validation_test.go
+++ b/pkg/apis/serving/k8s_validation_test.go
@@ -34,8 +34,8 @@ import (
 
 func enableMultiContainer(ctx context.Context, t *testing.T) context.Context {
 	return config.ToContext(ctx, &config.Config{
-		Defaults: &config.Defaults{
-			EnableMultiContainer: true,
+		Features: &config.Features{
+			MultiContainer: config.Enabled,
 		},
 	})
 }
@@ -168,7 +168,7 @@ func TestPodSpecValidation(t *testing.T) {
 				Image: "helloworld",
 			}},
 		},
-		want: &apis.FieldError{Message: "enable-multi-container is off, but found 2 containers"},
+		want: &apis.FieldError{Message: "multi-container is off, but found 2 containers"},
 	}, {
 		name: "extra field",
 		ps: corev1.PodSpec{
@@ -219,7 +219,7 @@ func TestPodSpecMultiContainerValidation(t *testing.T) {
 				Image: "helloworld",
 			}},
 		},
-		want: &apis.FieldError{Message: "enable-multi-container is off, but found 2 containers"},
+		want: &apis.FieldError{Message: "multi-container is off, but found 2 containers"},
 	}, {
 		name: "flag enabled: more than one container with one container port",
 		ps: corev1.PodSpec{
diff --git a/pkg/apis/serving/metadata_validation_test.go b/pkg/apis/serving/metadata_validation_test.go
index 5ca9a4c6d..2c862ced8 100644
--- a/pkg/apis/serving/metadata_validation_test.go
+++ b/pkg/apis/serving/metadata_validation_test.go
@@ -209,13 +209,22 @@ func TestValidateQueueSidecarAnnotation(t *testing.T) {
 		annotation map[string]string
 		expectErr  *apis.FieldError
 	}{{
-		name: "Queue sidecar resource percentage annotation more than 100",
+		name: "too small",
 		annotation: map[string]string{
-			QueueSideCarResourcePercentageAnnotation: "200",
+			QueueSideCarResourcePercentageAnnotation: "0.01982",
 		},
 		expectErr: &apis.FieldError{
-			Message: "expected 0.1 <= 200 <= 100",
-			Paths:   []string{QueueSideCarResourcePercentageAnnotation},
+			Message: "expected 0.1 <= 0.01982 <= 100",
+			Paths:   []string{fmt.Sprintf("[%s]", QueueSideCarResourcePercentageAnnotation)},
+		},
+	}, {
+		name: "too big for Queue sidecar resource percentage annotation",
+		annotation: map[string]string{
+			QueueSideCarResourcePercentageAnnotation: "100.0001",
+		},
+		expectErr: &apis.FieldError{
+			Message: "expected 0.1 <= 100.0001 <= 100",
+			Paths:   []string{fmt.Sprintf("[%s]", QueueSideCarResourcePercentageAnnotation)},
 		},
 	}, {
 		name: "Invalid queue sidecar resource percentage annotation",
@@ -232,7 +241,12 @@ func TestValidateQueueSidecarAnnotation(t *testing.T) {
 	}, {
 		name: "different annotation other than QueueSideCarResourcePercentageAnnotation",
 		annotation: map[string]string{
-			CreatorAnnotation: "",
+			CreatorAnnotation: "umph",
+		},
+	}, {
+		name: "valid value for Queue sidecar resource percentage annotation",
+		annotation: map[string]string{
+			QueueSideCarResourcePercentageAnnotation: "0.1",
 		},
 	}, {
 		name: "valid value for Queue sidecar resource percentage annotation",
diff --git a/pkg/apis/serving/v1/revision_defaults_test.go b/pkg/apis/serving/v1/revision_defaults_test.go
index 59583b8cb..f327de9ee 100644
--- a/pkg/apis/serving/v1/revision_defaults_test.go
+++ b/pkg/apis/serving/v1/revision_defaults_test.go
@@ -66,6 +66,7 @@ func TestRevisionDefaulting(t *testing.T) {
 		wc: func(ctx context.Context) context.Context {
 			s := config.NewStore(logger)
 			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
+			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName}})
 			s.OnConfigChanged(&corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultsConfigName,
@@ -130,6 +131,7 @@ func TestRevisionDefaulting(t *testing.T) {
 		wc: func(ctx context.Context) context.Context {
 			s := config.NewStore(logger)
 			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
+			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName}})
 			s.OnConfigChanged(&corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultsConfigName,
@@ -261,6 +263,7 @@ func TestRevisionDefaulting(t *testing.T) {
 		wc: func(ctx context.Context) context.Context {
 			s := config.NewStore(logger)
 			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
+			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName}})
 			s.OnConfigChanged(&corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultsConfigName,
diff --git a/pkg/apis/serving/v1/revision_validation_test.go b/pkg/apis/serving/v1/revision_validation_test.go
index b5f313066..20dc82e9e 100644
--- a/pkg/apis/serving/v1/revision_validation_test.go
+++ b/pkg/apis/serving/v1/revision_validation_test.go
@@ -383,7 +383,7 @@ func TestRevisionSpecValidation(t *testing.T) {
 				}},
 			},
 		},
-		want: &apis.FieldError{Message: "enable-multi-container is off, but found 2 containers"},
+		want: &apis.FieldError{Message: "multi-container is off, but found 2 containers"},
 	}, {
 		name: "exceed max timeout",
 		rs: &RevisionSpec{
@@ -410,6 +410,7 @@ func TestRevisionSpecValidation(t *testing.T) {
 		wc: func(ctx context.Context) context.Context {
 			s := config.NewStore(logtesting.TestLogger(t))
 			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
+			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName}})
 			s.OnConfigChanged(&corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultsConfigName,
@@ -518,6 +519,7 @@ func TestImmutableFields(t *testing.T) {
 		wc: func(ctx context.Context) context.Context {
 			s := config.NewStore(logtesting.TestLogger(t))
 			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
+			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName}})
 			s.OnConfigChanged(&corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultsConfigName,
@@ -838,7 +840,7 @@ func TestRevisionTemplateSpecValidation(t *testing.T) {
 			ObjectMeta: metav1.ObjectMeta{
 				Annotations: map[string]string{
 					autoscaling.MinScaleAnnotationKey: "5",
-					autoscaling.MaxScaleAnnotationKey: "",
+					autoscaling.MaxScaleAnnotationKey: "covid-19",
 				},
 			},
 			Spec: RevisionSpec{
@@ -849,10 +851,8 @@ func TestRevisionTemplateSpecValidation(t *testing.T) {
 				},
 			},
 		},
-		want: (&apis.FieldError{
-			Message: "expected 1 <=  <= 2147483647",
-			Paths:   []string{autoscaling.MaxScaleAnnotationKey},
-		}).ViaField("annotations").ViaField("metadata"),
+		want: apis.ErrInvalidValue("covid-19", autoscaling.MaxScaleAnnotationKey).
+			ViaField("annotations").ViaField("metadata"),
 	}, {
 		name: "Queue sidecar resource percentage annotation more than 100",
 		rts: &RevisionTemplateSpec{
@@ -871,7 +871,7 @@ func TestRevisionTemplateSpecValidation(t *testing.T) {
 		},
 		want: (&apis.FieldError{
 			Message: "expected 0.1 <= 200 <= 100",
-			Paths:   []string{serving.QueueSideCarResourcePercentageAnnotation},
+			Paths:   []string{"[" + serving.QueueSideCarResourcePercentageAnnotation + "]"},
 		}).ViaField("metadata.annotations"),
 	}, {
 		name: "Invalid queue sidecar resource percentage annotation",
@@ -946,7 +946,7 @@ func TestRevisionTemplateSpecValidation(t *testing.T) {
 
 			got := test.rts.Validate(ctx)
 			if got, want := got.Error(), test.want.Error(); !cmp.Equal(got, want) {
-				t.Errorf("Validate (-want, +got): \n%s", cmp.Diff(want, got))
+				t.Errorf("Validate (-want, +got):\n%s", cmp.Diff(want, got))
 			}
 		})
 	}
diff --git a/pkg/apis/serving/v1/route_lifecycle_test.go b/pkg/apis/serving/v1/route_lifecycle_test.go
index aea593dfa..7f35cb6ab 100644
--- a/pkg/apis/serving/v1/route_lifecycle_test.go
+++ b/pkg/apis/serving/v1/route_lifecycle_test.go
@@ -177,6 +177,7 @@ func TestTypicalRouteFlow(t *testing.T) {
 	apistest.CheckConditionOngoing(r, RouteConditionReady, t)
 
 	r.MarkTrafficAssigned()
+	r.MarkAutoTLSNotEnabled()
 	apistest.CheckConditionSucceeded(r, RouteConditionAllTrafficAssigned, t)
 	apistest.CheckConditionOngoing(r, RouteConditionIngressReady, t)
 	apistest.CheckConditionOngoing(r, RouteConditionReady, t)
@@ -290,6 +291,7 @@ func TestIngressFailureRecovery(t *testing.T) {
 	apistest.CheckConditionOngoing(r, RouteConditionReady, t)
 
 	r.MarkTrafficAssigned()
+	r.MarkAutoTLSNotEnabled()
 	r.PropagateIngressStatus(netv1alpha1.IngressStatus{
 		Status: duckv1.Status{
 			Conditions: duckv1.Conditions{{
@@ -381,6 +383,22 @@ func TestRouteNotOwnCertificate(t *testing.T) {
 	apistest.CheckConditionFailed(r, RouteConditionCertificateProvisioned, t)
 }
 
+func TestRouteAutoTLSNotEnabled(t *testing.T) {
+	r := &RouteStatus{}
+	r.InitializeConditions()
+	r.MarkAutoTLSNotEnabled()
+
+	apistest.CheckConditionSucceeded(r, RouteConditionCertificateProvisioned, t)
+}
+
+func TestRouteHTTPDowngrade(t *testing.T) {
+	r := &RouteStatus{}
+	r.InitializeConditions()
+	r.MarkHTTPDowngrade("cert")
+
+	apistest.CheckConditionSucceeded(r, RouteConditionCertificateProvisioned, t)
+}
+
 func TestIngressNotConfigured(t *testing.T) {
 	r := &RouteStatus{}
 	r.InitializeConditions()
diff --git a/pkg/apis/serving/v1alpha1/revision_defaults_test.go b/pkg/apis/serving/v1alpha1/revision_defaults_test.go
index b26d4b9a4..a577cd83d 100644
--- a/pkg/apis/serving/v1alpha1/revision_defaults_test.go
+++ b/pkg/apis/serving/v1alpha1/revision_defaults_test.go
@@ -89,6 +89,7 @@ func TestRevisionDefaulting(t *testing.T) {
 		wc: func(ctx context.Context) context.Context {
 			s := config.NewStore(logtesting.TestLogger(t))
 			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
+			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName}})
 			s.OnConfigChanged(&corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultsConfigName,
diff --git a/pkg/apis/serving/v1alpha1/revision_validation_test.go b/pkg/apis/serving/v1alpha1/revision_validation_test.go
index ed6c35a70..2c81f043c 100644
--- a/pkg/apis/serving/v1alpha1/revision_validation_test.go
+++ b/pkg/apis/serving/v1alpha1/revision_validation_test.go
@@ -244,6 +244,7 @@ func TestRevisionSpecValidation(t *testing.T) {
 		wc: func(ctx context.Context) context.Context {
 			s := config.NewStore(logtesting.TestLogger(t))
 			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
+			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName}})
 			s.OnConfigChanged(&corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultsConfigName,
@@ -401,7 +402,7 @@ func TestRevisionTemplateSpecValidation(t *testing.T) {
 		},
 		want: (&apis.FieldError{
 			Message: "expected 0.1 <= 200 <= 100",
-			Paths:   []string{serving.QueueSideCarResourcePercentageAnnotation},
+			Paths:   []string{"[" + serving.QueueSideCarResourcePercentageAnnotation + "]"},
 		}).ViaField("metadata.annotations"),
 	}, {
 		name: "Invalid queue sidecar resource percentage annotation",
@@ -427,7 +428,7 @@ func TestRevisionTemplateSpecValidation(t *testing.T) {
 			ObjectMeta: metav1.ObjectMeta{
 				Annotations: map[string]string{
 					autoscaling.MinScaleAnnotationKey: "5",
-					autoscaling.MaxScaleAnnotationKey: "",
+					autoscaling.MaxScaleAnnotationKey: "covid-19",
 				},
 			},
 			Spec: RevisionSpec{
@@ -436,10 +437,8 @@ func TestRevisionTemplateSpecValidation(t *testing.T) {
 				},
 			},
 		},
-		want: (&apis.FieldError{
-			Message: "expected 1 <=  <= 2147483647",
-			Paths:   []string{autoscaling.MaxScaleAnnotationKey},
-		}).ViaField("annotations").ViaField("metadata"),
+		want: apis.ErrInvalidValue("covid-19", autoscaling.MaxScaleAnnotationKey).
+			ViaField("annotations").ViaField("metadata"),
 	}, {
 		name: "Invalid initial scale when cluster doesn't allow zero",
 		ctx:  autoscalerConfigCtx(false, 1),
@@ -647,6 +646,7 @@ func TestImmutableFields(t *testing.T) {
 		wc: func(ctx context.Context) context.Context {
 			s := config.NewStore(logtesting.TestLogger(t))
 			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
+			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName}})
 			s.OnConfigChanged(&corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultsConfigName,
diff --git a/pkg/apis/serving/v1alpha1/route_lifecycle_test.go b/pkg/apis/serving/v1alpha1/route_lifecycle_test.go
index 04dec0bf6..b1685869e 100644
--- a/pkg/apis/serving/v1alpha1/route_lifecycle_test.go
+++ b/pkg/apis/serving/v1alpha1/route_lifecycle_test.go
@@ -160,6 +160,7 @@ func TestTypicalRouteFlow(t *testing.T) {
 	apistest.CheckConditionOngoing(r, RouteConditionReady, t)
 
 	r.MarkTrafficAssigned()
+	r.MarkAutoTLSNotEnabled()
 	apistest.CheckConditionSucceeded(r, RouteConditionAllTrafficAssigned, t)
 	apistest.CheckConditionOngoing(r, RouteConditionIngressReady, t)
 	apistest.CheckConditionOngoing(r, RouteConditionReady, t)
@@ -273,6 +274,7 @@ func TestIngressFailureRecovery(t *testing.T) {
 	apistest.CheckConditionOngoing(r, RouteConditionReady, t)
 
 	r.MarkTrafficAssigned()
+	r.MarkAutoTLSNotEnabled()
 	r.PropagateIngressStatus(netv1alpha1.IngressStatus{
 		Status: duckv1.Status{
 			Conditions: duckv1.Conditions{{
@@ -376,6 +378,22 @@ func TestRouteNotOwnCertificate(t *testing.T) {
 	apistest.CheckConditionFailed(r, RouteConditionCertificateProvisioned, t)
 }
 
+func TestRouteAutoTLSNotEnabled(t *testing.T) {
+	r := &RouteStatus{}
+	r.InitializeConditions()
+	r.MarkAutoTLSNotEnabled()
+
+	apistest.CheckConditionSucceeded(r, RouteConditionCertificateProvisioned, t)
+}
+
+func TestRouteHTTPDowngrade(t *testing.T) {
+	r := &RouteStatus{}
+	r.InitializeConditions()
+	r.MarkHTTPDowngrade("cert")
+
+	apistest.CheckConditionSucceeded(r, RouteConditionCertificateProvisioned, t)
+}
+
 func TestIngressNotConfigured(t *testing.T) {
 	r := &RouteStatus{}
 	r.InitializeConditions()
diff --git a/pkg/apis/serving/v1beta1/revision_defaults_test.go b/pkg/apis/serving/v1beta1/revision_defaults_test.go
index 2b131d917..e88286b62 100644
--- a/pkg/apis/serving/v1beta1/revision_defaults_test.go
+++ b/pkg/apis/serving/v1beta1/revision_defaults_test.go
@@ -67,6 +67,7 @@ func TestRevisionDefaulting(t *testing.T) {
 		wc: func(ctx context.Context) context.Context {
 			s := config.NewStore(logger)
 			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
+			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName}})
 			s.OnConfigChanged(&corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultsConfigName,
@@ -131,6 +132,7 @@ func TestRevisionDefaulting(t *testing.T) {
 		wc: func(ctx context.Context) context.Context {
 			s := config.NewStore(logger)
 			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
+			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName}})
 			s.OnConfigChanged(&corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultsConfigName,
diff --git a/pkg/apis/serving/v1beta1/revision_validation_test.go b/pkg/apis/serving/v1beta1/revision_validation_test.go
index c7c56dff9..3ee08493b 100644
--- a/pkg/apis/serving/v1beta1/revision_validation_test.go
+++ b/pkg/apis/serving/v1beta1/revision_validation_test.go
@@ -384,7 +384,7 @@ func TestRevisionSpecValidation(t *testing.T) {
 				}},
 			},
 		},
-		want: &apis.FieldError{Message: "enable-multi-container is off, but found 2 containers"},
+		want: &apis.FieldError{Message: "multi-container is off, but found 2 containers"},
 	}, {
 		name: "exceed max timeout",
 		rs: &v1.RevisionSpec{
@@ -411,6 +411,7 @@ func TestRevisionSpecValidation(t *testing.T) {
 		wc: func(ctx context.Context) context.Context {
 			s := config.NewStore(logtesting.TestLogger(t))
 			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
+			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName}})
 			s.OnConfigChanged(&corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultsConfigName,
@@ -519,6 +520,7 @@ func TestImmutableFields(t *testing.T) {
 		wc: func(ctx context.Context) context.Context {
 			s := config.NewStore(logtesting.TestLogger(t))
 			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: autoscalerconfig.ConfigName}})
+			s.OnConfigChanged(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: config.FeaturesConfigName}})
 			s.OnConfigChanged(&corev1.ConfigMap{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: config.DefaultsConfigName,
@@ -788,7 +790,7 @@ func TestRevisionTemplateSpecValidation(t *testing.T) {
 			ObjectMeta: metav1.ObjectMeta{
 				Annotations: map[string]string{
 					autoscaling.MinScaleAnnotationKey: "5",
-					autoscaling.MaxScaleAnnotationKey: "",
+					autoscaling.MaxScaleAnnotationKey: "covid-19",
 				},
 			},
 			Spec: v1.RevisionSpec{
@@ -799,10 +801,8 @@ func TestRevisionTemplateSpecValidation(t *testing.T) {
 				},
 			},
 		},
-		want: (&apis.FieldError{
-			Message: "expected 1 <=  <= 2147483647",
-			Paths:   []string{autoscaling.MaxScaleAnnotationKey},
-		}).ViaField("annotations").ViaField("metadata"),
+		want: apis.ErrInvalidValue("covid-19", autoscaling.MaxScaleAnnotationKey).
+			ViaField("annotations").ViaField("metadata"),
 	}, {
 		name: "Queue sidecar resource percentage annotation more than 100",
 		rts: &v1.RevisionTemplateSpec{
@@ -821,7 +821,7 @@ func TestRevisionTemplateSpecValidation(t *testing.T) {
 		},
 		want: (&apis.FieldError{
 			Message: "expected 0.1 <= 200 <= 100",
-			Paths:   []string{serving.QueueSideCarResourcePercentageAnnotation},
+			Paths:   []string{"[" + serving.QueueSideCarResourcePercentageAnnotation + "]"},
 		}).ViaField("metadata.annotations"),
 	}, {
 		name: "Invalid queue sidecar resource percentage annotation",
diff --git a/pkg/autoscaler/config/config_test.go b/pkg/autoscaler/config/config_test.go
index d03aaa40d..21133d3e6 100644
--- a/pkg/autoscaler/config/config_test.go
+++ b/pkg/autoscaler/config/config_test.go
@@ -203,6 +203,12 @@ func TestNewConfig(t *testing.T) {
 			"max-scale-down-rate": "1",
 		},
 		wantErr: true,
+	}, {
+		name: "stable window too big",
+		input: map[string]string{
+			"stable-window": "1h1s",
+		},
+		wantErr: true,
 	}, {
 		name: "stable window too small",
 		input: map[string]string{
diff --git a/pkg/autoscaler/metrics/collector_test.go b/pkg/autoscaler/metrics/collector_test.go
index 620d56d2d..90da18374 100644
--- a/pkg/autoscaler/metrics/collector_test.go
+++ b/pkg/autoscaler/metrics/collector_test.go
@@ -24,9 +24,11 @@ import (
 
 	"github.com/google/go-cmp/cmp"
 	"go.uber.org/zap"
+
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
+
 	. "knative.dev/pkg/logging/testing"
 	av1alpha1 "knative.dev/serving/pkg/apis/autoscaling/v1alpha1"
 	"knative.dev/serving/pkg/apis/serving"
@@ -224,12 +226,7 @@ func TestMetricCollectorNoScraper(t *testing.T) {
 		AverageConcurrentRequests: wantStat,
 		RequestCount:              wantStat,
 	}
-	scraper := &testScraper{
-		s: func() (Stat, error) {
-			return stat, nil
-		},
-	}
-	factory := scraperFactory(scraper, nil)
+	factory := scraperFactory(nil, nil)
 
 	coll := NewMetricCollector(factory, logger)
 	coll.tickProvider = mtp.NewTicker // custom ticker.
@@ -387,6 +384,19 @@ func TestMetricCollectorRecord(t *testing.T) {
 	}
 }
 
+func TestDoubleWatch(t *testing.T) {
+	defer func() {
+		if x := recover(); x == nil {
+			t.Error("Expected panic")
+		}
+	}()
+	logger := TestLogger(t)
+	factory := scraperFactory(nil, nil)
+	coll := NewMetricCollector(factory, logger)
+	coll.Watch(func(types.NamespacedName) {})
+	coll.Watch(func(types.NamespacedName) {})
+}
+
 func TestMetricCollectorError(t *testing.T) {
 	testMetric := &av1alpha1.Metric{
 		ObjectMeta: metav1.ObjectMeta{
diff --git a/pkg/deployment/config_test.go b/pkg/deployment/config_test.go
index d9ce7a737..4d97d08d8 100644
--- a/pkg/deployment/config_test.go
+++ b/pkg/deployment/config_test.go
@@ -31,7 +31,7 @@ import (
 	_ "knative.dev/pkg/system/testing"
 )
 
-const noSidecarImage = ""
+const defaultSidecarImage = "defaultImage"
 
 func TestControllerConfigurationFromFile(t *testing.T) {
 	cm, example := ConfigMapsFromTestFile(t, ConfigName, QueueSidecarImageKey)
@@ -62,33 +62,33 @@ func TestControllerConfiguration(t *testing.T) {
 		name: "controller configuration with bad registries",
 		wantConfig: &Config{
 			RegistriesSkippingTagResolving: sets.NewString("ko.local", ""),
-			QueueSidecarImage:              noSidecarImage,
+			QueueSidecarImage:              defaultSidecarImage,
 			ProgressDeadline:               ProgressDeadlineDefault,
 		},
 		data: map[string]string{
-			QueueSidecarImageKey:              noSidecarImage,
+			QueueSidecarImageKey:              defaultSidecarImage,
 			registriesSkippingTagResolvingKey: "ko.local,,",
 		},
 	}, {
 		name: "controller configuration good progress deadline",
 		wantConfig: &Config{
 			RegistriesSkippingTagResolving: sets.NewString("ko.local", "dev.local"),
-			QueueSidecarImage:              noSidecarImage,
+			QueueSidecarImage:              defaultSidecarImage,
 			ProgressDeadline:               444 * time.Second,
 		},
 		data: map[string]string{
-			QueueSidecarImageKey: noSidecarImage,
+			QueueSidecarImageKey: defaultSidecarImage,
 			ProgressDeadlineKey:  "444s",
 		},
 	}, {
 		name: "controller configuration with registries",
 		wantConfig: &Config{
 			RegistriesSkippingTagResolving: sets.NewString("ko.local", "ko.dev"),
-			QueueSidecarImage:              noSidecarImage,
+			QueueSidecarImage:              defaultSidecarImage,
 			ProgressDeadline:               ProgressDeadlineDefault,
 		},
 		data: map[string]string{
-			QueueSidecarImageKey:              noSidecarImage,
+			QueueSidecarImageKey:              defaultSidecarImage,
 			registriesSkippingTagResolvingKey: "ko.local,ko.dev",
 		},
 	}, {
@@ -99,21 +99,21 @@ func TestControllerConfiguration(t *testing.T) {
 		name:    "controller configuration invalid progress deadline",
 		wantErr: true,
 		data: map[string]string{
-			QueueSidecarImageKey: noSidecarImage,
+			QueueSidecarImageKey: defaultSidecarImage,
 			ProgressDeadlineKey:  "not-a-duration",
 		},
 	}, {
 		name:    "controller configuration invalid progress deadline II",
 		wantErr: true,
 		data: map[string]string{
-			QueueSidecarImageKey: noSidecarImage,
+			QueueSidecarImageKey: defaultSidecarImage,
 			ProgressDeadlineKey:  "-21s",
 		},
 	}, {
 		name:    "controller configuration invalid progress deadline III",
 		wantErr: true,
 		data: map[string]string{
-			QueueSidecarImageKey: noSidecarImage,
+			QueueSidecarImageKey: defaultSidecarImage,
 			ProgressDeadlineKey:  "0ms",
 		},
 	}}
diff --git a/pkg/leaderelection/config_test.go b/pkg/leaderelection/config_test.go
index e45ba435c..48f69077a 100644
--- a/pkg/leaderelection/config_test.go
+++ b/pkg/leaderelection/config_test.go
@@ -18,7 +18,6 @@ package leaderelection
 
 import (
 	"errors"
-	"reflect"
 	"testing"
 	"time"
 
@@ -71,7 +70,7 @@ func TestValidateConfig(t *testing.T) {
 			data["renewDeadline"] = "not a duration"
 			return data
 		}(),
-		err: errors.New(`renewDeadline: invalid duration: "not a duration"`),
+		err: errors.New(`failed to parse "renewDeadline": time: invalid duration not a duration`),
 	}, {
 		name: "invalid component",
 		data: func() map[string]string {
@@ -85,7 +84,7 @@ func TestValidateConfig(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			actualConfig, actualErr := ValidateConfig(&corev1.ConfigMap{Data: tc.data})
-			if !reflect.DeepEqual(tc.err, actualErr) {
+			if tc.err != nil && tc.err.Error() != actualErr.Error() {
 				t.Fatalf("%v: expected error %v, got %v", tc.name, tc.err, actualErr)
 			}
 
diff --git a/pkg/queue/stats_test.go b/pkg/queue/stats_test.go
index ffadf15f5..2a2296ef5 100644
--- a/pkg/queue/stats_test.go
+++ b/pkg/queue/stats_test.go
@@ -33,7 +33,7 @@ type reportedStat struct {
 
 func TestNoData(t *testing.T) {
 	now := time.Now()
-	s := newTestStats(now)
+	s := newTestStats(t, now)
 
 	got := s.report(now)
 	want := reportedStat{
@@ -47,7 +47,7 @@ func TestNoData(t *testing.T) {
 
 func TestSingleRequestWholeTime(t *testing.T) {
 	now := time.Now()
-	s := newTestStats(now)
+	s := newTestStats(t, now)
 
 	s.requestStart(now)
 	now = now.Add(1 * time.Second)
@@ -66,7 +66,7 @@ func TestSingleRequestWholeTime(t *testing.T) {
 
 func TestSingleRequestHalfTime(t *testing.T) {
 	now := time.Now()
-	s := newTestStats(now)
+	s := newTestStats(t, now)
 
 	s.requestStart(now)
 	now = now.Add(1 * time.Second)
@@ -85,7 +85,7 @@ func TestSingleRequestHalfTime(t *testing.T) {
 
 func TestVeryShortLivedRequest(t *testing.T) {
 	now := time.Now()
-	s := newTestStats(now)
+	s := newTestStats(t, now)
 
 	s.requestStart(now)
 	now = now.Add(10 * time.Millisecond)
@@ -105,7 +105,7 @@ func TestVeryShortLivedRequest(t *testing.T) {
 
 func TestMultipleRequestsWholeTime(t *testing.T) {
 	now := time.Now()
-	s := newTestStats(now)
+	s := newTestStats(t, now)
 
 	s.requestStart(now)
 	now = now.Add(300 * time.Millisecond)
@@ -132,7 +132,7 @@ func TestMultipleRequestsWholeTime(t *testing.T) {
 
 func TestMultipleRequestsInterleaved(t *testing.T) {
 	now := time.Now()
-	s := newTestStats(now)
+	s := newTestStats(t, now)
 
 	s.requestStart(now)
 	now = now.Add(100 * time.Millisecond)
@@ -155,7 +155,7 @@ func TestMultipleRequestsInterleaved(t *testing.T) {
 
 func TestOneRequestAcrossReportings(t *testing.T) {
 	now := time.Now()
-	s := newTestStats(now)
+	s := newTestStats(t, now)
 
 	s.requestStart(now)
 	now = now.Add(1 * time.Second)
@@ -184,7 +184,7 @@ func TestOneRequestAcrossReportings(t *testing.T) {
 
 func TestOneProxiedRequest(t *testing.T) {
 	now := time.Now()
-	s := newTestStats(now)
+	s := newTestStats(t, now)
 	s.proxiedStart(now)
 	now = now.Add(1 * time.Second)
 	got := s.report(now)
@@ -201,7 +201,7 @@ func TestOneProxiedRequest(t *testing.T) {
 
 func TestOneEndedProxiedRequest(t *testing.T) {
 	now := time.Now()
-	s := newTestStats(now)
+	s := newTestStats(t, now)
 	s.proxiedStart(now)
 	now = now.Add(500 * time.Millisecond)
 	s.proxiedEnd(now)
@@ -220,7 +220,7 @@ func TestOneEndedProxiedRequest(t *testing.T) {
 
 func TestTwoRequestsOneProxied(t *testing.T) {
 	now := time.Now()
-	s := newTestStats(now)
+	s := newTestStats(t, now)
 	s.proxiedStart(now)
 	now = now.Add(500 * time.Millisecond)
 	s.proxiedEnd(now)
@@ -245,9 +245,11 @@ type testStats struct {
 	statChan     chan reportedStat
 }
 
-func newTestStats(now time.Time) *testStats {
-	reportBiChan := make(chan time.Time)
+func newTestStats(t *testing.T, now time.Time) *testStats {
 	reqChan := make(chan network.ReqEvent)
+	t.Cleanup(func() { close(reqChan) })
+
+	reportBiChan := make(chan time.Time)
 	statChan := make(chan reportedStat)
 	report := func(acr float64, apcr float64, rc float64, prc float64) {
 		statChan <- reportedStat{
@@ -257,7 +259,7 @@ func newTestStats(now time.Time) *testStats {
 			ProxiedRequestCount: prc,
 		}
 	}
-	NewStats(now, reqChan, reportBiChan, report)
+	go ReportStats(now, reqChan, reportBiChan, report)
 	return &testStats{
 		reqChan:      reqChan,
 		reportBiChan: reportBiChan,
diff --git a/pkg/reconciler/autoscaling/kpa/scaler_test.go b/pkg/reconciler/autoscaling/kpa/scaler_test.go
index 6001583f4..e9599f3cb 100644
--- a/pkg/reconciler/autoscaling/kpa/scaler_test.go
+++ b/pkg/reconciler/autoscaling/kpa/scaler_test.go
@@ -153,8 +153,18 @@ func TestScaler(t *testing.T) {
 			WithWindowAnnotation(paStableWindow.String())(k)
 			paMarkActive(k, time.Now().Add(-paStableWindow))
 		},
-		wantReplicas: 0,
-		wantScaling:  false,
+	}, {
+		label:         "can scale to zero after grace period, but 0 PA retention",
+		startReplicas: 1,
+		scaleTo:       0,
+		paMutation: func(k *pav1alpha1.PodAutoscaler) {
+			paMarkInactive(k, time.Now().Add(-gracePeriod))
+			k.Annotations[autoscaling.ScaleToZeroPodRetentionPeriodKey] = "0"
+		},
+		configMutator: func(c *config.Config) {
+			c.Autoscaler.ScaleToZeroPodRetentionPeriod = 2 * gracePeriod
+		},
+		wantScaling: true,
 	}, {
 		label:         "can't scale to zero after grace period, but before last pod retention",
 		startReplicas: 1,
@@ -168,6 +178,20 @@ func TestScaler(t *testing.T) {
 		wantReplicas: 0,
 		wantScaling:  false,
 		wantCBCount:  1,
+	}, {
+		label:         "can't scale to zero after grace period, but before last pod retention, pa defined",
+		startReplicas: 1,
+		scaleTo:       0,
+		paMutation: func(k *pav1alpha1.PodAutoscaler) {
+			paMarkInactive(k, time.Now().Add(-gracePeriod))
+			k.Annotations[autoscaling.ScaleToZeroPodRetentionPeriodKey] = (2 * gracePeriod).String()
+		},
+		configMutator: func(c *config.Config) {
+			c.Autoscaler.ScaleToZeroPodRetentionPeriod = 0 // Disabled in CM.
+		},
+		wantReplicas: 0,
+		wantScaling:  false,
+		wantCBCount:  1,
 	}, {
 		label:         "scale to zero after grace period, and after last pod retention",
 		startReplicas: 1,
diff --git a/pkg/reconciler/route/resources/service_test.go b/pkg/reconciler/route/resources/service_test.go
index 81ccb3fc8..df6d8f5e5 100644
--- a/pkg/reconciler/route/resources/service_test.go
+++ b/pkg/reconciler/route/resources/service_test.go
@@ -53,193 +53,192 @@ var (
 )
 
 func TestNewMakeK8SService(t *testing.T) {
-	scenarios := map[string]struct {
-		// Inputs
+	tests := []struct {
+		name         string
 		route        *v1.Route
 		ingress      *netv1alpha1.Ingress
 		targetName   string
 		expectedSpec corev1.ServiceSpec
 		expectedMeta metav1.ObjectMeta
 		shouldFail   bool
-	}{
-		"no-loadbalancer": {
-			route: r,
-			ingress: &netv1alpha1.Ingress{
-				Status: netv1alpha1.IngressStatus{},
-			},
-			expectedMeta: expectedMeta,
-			shouldFail:   true,
+	}{{
+		name:  "no-loadbalancer",
+		route: r,
+		ingress: &netv1alpha1.Ingress{
+			Status: netv1alpha1.IngressStatus{},
 		},
-		"empty-loadbalancer": {
-			route: r,
-			ingress: &netv1alpha1.Ingress{
-				Status: netv1alpha1.IngressStatus{
-					LoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{}},
-					},
-					PublicLoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{}},
-					},
-					PrivateLoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{}},
-					},
+		expectedMeta: expectedMeta,
+		shouldFail:   true,
+	}, {
+		name:  "empty-loadbalancer",
+		route: r,
+		ingress: &netv1alpha1.Ingress{
+			Status: netv1alpha1.IngressStatus{
+				LoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{}},
+				},
+				PublicLoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{}},
+				},
+				PrivateLoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{}},
 				},
 			},
-			expectedMeta: expectedMeta,
-			shouldFail:   true,
 		},
-		"multi-loadbalancer": {
-			route: r,
-			ingress: &netv1alpha1.Ingress{
-				Status: netv1alpha1.IngressStatus{
-					LoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{
-							Domain: "domain.com",
-						}, {
-							DomainInternal: "domain.com",
-						}},
-					},
-					PublicLoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{
-							Domain: "domain.com",
-						}, {
-							DomainInternal: "domain.com",
-						}},
-					},
-					PrivateLoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{
-							Domain: "domain.com",
-						}, {
-							DomainInternal: "domain.com",
-						}},
-					},
+		expectedMeta: expectedMeta,
+		shouldFail:   true,
+	}, {
+		name:  "multi-loadbalancer",
+		route: r,
+		ingress: &netv1alpha1.Ingress{
+			Status: netv1alpha1.IngressStatus{
+				LoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{
+						Domain: "domain.com",
+					}, {
+						DomainInternal: "domain.com",
+					}},
+				},
+				PublicLoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{
+						Domain: "domain.com",
+					}, {
+						DomainInternal: "domain.com",
+					}},
+				},
+				PrivateLoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{
+						Domain: "domain.com",
+					}, {
+						DomainInternal: "domain.com",
+					}},
 				},
 			},
-			expectedMeta: expectedMeta,
-			shouldFail:   true,
 		},
-		"ingress-with-domain": {
-			route: r,
-			ingress: &netv1alpha1.Ingress{
-				Status: netv1alpha1.IngressStatus{
-					LoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{Domain: "domain.com"}},
-					},
-					PublicLoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{Domain: "domain.com"}},
-					},
-					PrivateLoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{Domain: "domain.com"}},
-					},
+		expectedMeta: expectedMeta,
+		shouldFail:   true,
+	}, {
+		name:  "ingress-with-domain",
+		route: r,
+		ingress: &netv1alpha1.Ingress{
+			Status: netv1alpha1.IngressStatus{
+				LoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{Domain: "domain.com"}},
+				},
+				PublicLoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{Domain: "domain.com"}},
+				},
+				PrivateLoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{Domain: "domain.com"}},
 				},
 			},
-			expectedMeta: expectedMeta,
-			expectedSpec: corev1.ServiceSpec{
-				Type:         corev1.ServiceTypeExternalName,
-				ExternalName: "domain.com",
-			},
 		},
-		"ingress-with-domaininternal": {
-			route: r,
-			ingress: &netv1alpha1.Ingress{
-				Status: netv1alpha1.IngressStatus{
-					LoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{DomainInternal: "istio-ingressgateway.istio-system.svc.cluster.local"}},
-					},
-					PublicLoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{DomainInternal: "istio-ingressgateway.istio-system.svc.cluster.local"}},
-					},
-					PrivateLoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{DomainInternal: "private-istio-ingressgateway.istio-system.svc.cluster.local"}},
-					},
+		expectedMeta: expectedMeta,
+		expectedSpec: corev1.ServiceSpec{
+			Type:         corev1.ServiceTypeExternalName,
+			ExternalName: "domain.com",
+		},
+	}, {
+		name:  "ingress-with-domaininternal",
+		route: r,
+		ingress: &netv1alpha1.Ingress{
+			Status: netv1alpha1.IngressStatus{
+				LoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{DomainInternal: "istio-ingressgateway.istio-system.svc.cluster.local"}},
+				},
+				PublicLoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{DomainInternal: "istio-ingressgateway.istio-system.svc.cluster.local"}},
+				},
+				PrivateLoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{DomainInternal: "private-istio-ingressgateway.istio-system.svc.cluster.local"}},
 				},
-			},
-			expectedMeta: expectedMeta,
-			expectedSpec: corev1.ServiceSpec{
-				Type:            corev1.ServiceTypeExternalName,
-				ExternalName:    "private-istio-ingressgateway.istio-system.svc.cluster.local",
-				SessionAffinity: corev1.ServiceAffinityNone,
 			},
 		},
-		"ingress-with-only-mesh": {
-			route: r,
-			ingress: &netv1alpha1.Ingress{
-				Status: netv1alpha1.IngressStatus{
-					LoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{MeshOnly: true}},
-					},
-					PublicLoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{MeshOnly: true}},
-					},
-					PrivateLoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{MeshOnly: true}},
-					},
+		expectedMeta: expectedMeta,
+		expectedSpec: corev1.ServiceSpec{
+			Type:            corev1.ServiceTypeExternalName,
+			ExternalName:    "private-istio-ingressgateway.istio-system.svc.cluster.local",
+			SessionAffinity: corev1.ServiceAffinityNone,
+		},
+	}, {
+		name:  "ingress-with-only-mesh",
+		route: r,
+		ingress: &netv1alpha1.Ingress{
+			Status: netv1alpha1.IngressStatus{
+				LoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{MeshOnly: true}},
+				},
+				PublicLoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{MeshOnly: true}},
+				},
+				PrivateLoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{MeshOnly: true}},
 				},
 			},
-			expectedMeta: expectedMeta,
-			expectedSpec: corev1.ServiceSpec{
-				Type: corev1.ServiceTypeClusterIP,
-				Ports: []corev1.ServicePort{{
-					Name: "http",
-					Port: 80,
-				}},
-			},
 		},
-		"with-target-name-specified": {
-			route:      r,
-			targetName: "my-target-name",
-			ingress: &netv1alpha1.Ingress{
-				Status: netv1alpha1.IngressStatus{
-					LoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{MeshOnly: true}},
-					},
-					PublicLoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{MeshOnly: true}},
-					},
-					PrivateLoadBalancer: &netv1alpha1.LoadBalancerStatus{
-						Ingress: []netv1alpha1.LoadBalancerIngressStatus{{MeshOnly: true}},
-					},
+		expectedMeta: expectedMeta,
+		expectedSpec: corev1.ServiceSpec{
+			Type: corev1.ServiceTypeClusterIP,
+			Ports: []corev1.ServicePort{{
+				Name: "http",
+				Port: 80,
+			}},
+		},
+	}, {
+		name:       "with-target-name-specified",
+		route:      r,
+		targetName: "my-target-name",
+		ingress: &netv1alpha1.Ingress{
+			Status: netv1alpha1.IngressStatus{
+				LoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{MeshOnly: true}},
 				},
-			},
-			expectedMeta: metav1.ObjectMeta{
-				Name:      "my-target-name-test-route",
-				Namespace: r.Namespace,
-				OwnerReferences: []metav1.OwnerReference{
-					*kmeta.NewControllerRef(r),
+				PublicLoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{MeshOnly: true}},
 				},
-				Labels: map[string]string{
-					serving.RouteLabelKey: r.Name,
+				PrivateLoadBalancer: &netv1alpha1.LoadBalancerStatus{
+					Ingress: []netv1alpha1.LoadBalancerIngressStatus{{MeshOnly: true}},
 				},
 			},
-			expectedSpec: corev1.ServiceSpec{
-				Type: corev1.ServiceTypeClusterIP,
-				Ports: []corev1.ServicePort{{
-					Name: "http",
-					Port: 80,
-				}},
+		},
+		expectedMeta: metav1.ObjectMeta{
+			Name:      "my-target-name-test-route",
+			Namespace: r.Namespace,
+			OwnerReferences: []metav1.OwnerReference{
+				*kmeta.NewControllerRef(r),
+			},
+			Labels: map[string]string{
+				serving.RouteLabelKey: r.Name,
 			},
 		},
-	}
+		expectedSpec: corev1.ServiceSpec{
+			Type: corev1.ServiceTypeClusterIP,
+			Ports: []corev1.ServicePort{{
+				Name: "http",
+				Port: 80,
+			}},
+		},
+	}}
 
-	for name, scenario := range scenarios {
-		t.Run(name, func(t *testing.T) {
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
 			cfg := testConfig()
 			ctx := config.ToContext(context.Background(), cfg)
-			service, err := MakeK8sService(ctx, scenario.route, scenario.targetName, scenario.ingress, false)
+			service, err := MakeK8sService(ctx, tc.route, tc.targetName, tc.ingress, false)
 			// Validate
-			if scenario.shouldFail && err == nil {
-				t.Errorf("Test %q failed: returned success but expected error", name)
+			if tc.shouldFail && err == nil {
+				t.Fatal("MakeK8sService returned success but expected error")
 			}
-			if !scenario.shouldFail {
+			if !tc.shouldFail {
 				if err != nil {
-					t.Errorf("Test %q failed: returned error: %v", name, err)
+					t.Fatal("MakeK8sService:", err)
 				}
 
-				if !cmp.Equal(scenario.expectedMeta, service.ObjectMeta) {
-					t.Errorf("Unexpected Metadata (-want +got): %s", cmp.Diff(scenario.expectedMeta, service.ObjectMeta))
+				if !cmp.Equal(tc.expectedMeta, service.ObjectMeta) {
+					t.Error("Unexpected Metadata (-want +got):", cmp.Diff(tc.expectedMeta, service.ObjectMeta))
 				}
-				if !cmp.Equal(scenario.expectedSpec, service.Spec) {
-					t.Errorf("Unexpected ServiceSpec (-want +got): %s", cmp.Diff(scenario.expectedSpec, service.Spec))
+				if !cmp.Equal(tc.expectedSpec, service.Spec) {
+					t.Error("Unexpected ServiceSpec (-want +got):", cmp.Diff(tc.expectedSpec, service.Spec))
 				}
 			}
 		})
diff --git a/pkg/reconciler/route/table_test.go b/pkg/reconciler/route/table_test.go
index 1ed2c6844..f8694c521 100644
--- a/pkg/reconciler/route/table_test.go
+++ b/pkg/reconciler/route/table_test.go
@@ -157,7 +157,7 @@ func TestReconcile(t *testing.T) {
 			Object: Route("default", "becomes-ready", WithConfigTarget("config"),
 				WithRouteUID("12-34"),
 				// Populated by reconciliation when all traffic has been assigned.
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressNotConfigured, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -204,7 +204,7 @@ func TestReconcile(t *testing.T) {
 			Object: Route("default", "ingress-failed", WithConfigTarget("config"),
 				WithRouteUID("12-34"),
 				// Populated by reconciliation when all traffic has been assigned.
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled, WithInitRouteConditions,
 				MarkTrafficAssigned,
 				WithStatusTraffic(
 					v1.TrafficTarget{
@@ -255,7 +255,7 @@ func TestReconcile(t *testing.T) {
 			Object: Route("default", "becomes-ready", WithConfigTarget("config"),
 				WithRouteUID("12-34"), WithIngressClass("custom-ingress-class"),
 				// Populated by reconciliation when all traffic has been assigned.
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressNotConfigured, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -312,7 +312,7 @@ func TestReconcile(t *testing.T) {
 			Object: Route("default", "becomes-ready", WithConfigTarget("config"),
 				WithRouteUID("65-23"),
 				// Populated by reconciliation when all traffic has been assigned.
-				WithLocalDomain, WithAddress, WithInitRouteConditions,
+				WithLocalDomain, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				WithRouteLabel(map[string]string{"serving.knative.dev/visibility": "cluster-local"}),
 				MarkTrafficAssigned, MarkIngressNotConfigured, WithStatusTraffic(
 					v1.TrafficTarget{
@@ -358,7 +358,7 @@ func TestReconcile(t *testing.T) {
 		WantStatusUpdates: []clientgotesting.UpdateActionImpl{{
 			Object: Route("default", "becomes-ready", WithConfigTarget("config"),
 				// Populated by reconciliation when the route becomes ready.
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -448,7 +448,7 @@ func TestReconcile(t *testing.T) {
 			Object: Route("default", "ingress-create-failure", WithConfigTarget("config"),
 				WithRouteFinalizer,
 				// Populated by reconciliation when we fail to create the ingress.
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -466,7 +466,7 @@ func TestReconcile(t *testing.T) {
 		Name: "steady state",
 		Objects: []runtime.Object{
 			Route("default", "steady-state", WithConfigTarget("config"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady,
 				WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
@@ -503,7 +503,7 @@ func TestReconcile(t *testing.T) {
 		WantErr: true,
 		Objects: []runtime.Object{
 			Route("default", "unhappy-owner", WithConfigTarget("config"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName: "config-00001",
@@ -520,7 +520,7 @@ func TestReconcile(t *testing.T) {
 		},
 		WantStatusUpdates: []clientgotesting.UpdateActionImpl{{
 			Object: Route("default", "unhappy-owner", WithConfigTarget("config"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -542,7 +542,7 @@ func TestReconcile(t *testing.T) {
 		Objects: []runtime.Object{
 			Route("default", "different-domain", WithConfigTarget("config"),
 				WithAnotherDomain, WithAddress,
-				WithInitRouteConditions, MarkTrafficAssigned, MarkIngressReady,
+				WithRouteConditionsAutoTLSDisabled, MarkTrafficAssigned, MarkIngressReady,
 				WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -604,7 +604,7 @@ func TestReconcile(t *testing.T) {
 		Name: "new latest created revision",
 		Objects: []runtime.Object{
 			Route("default", "new-latest-created", WithConfigTarget("config"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -642,7 +642,7 @@ func TestReconcile(t *testing.T) {
 		Name: "new latest ready revision",
 		Objects: []runtime.Object{
 			Route("default", "new-latest-ready", WithConfigTarget("config"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName: "config-00001",
@@ -695,7 +695,7 @@ func TestReconcile(t *testing.T) {
 		}},
 		WantStatusUpdates: []clientgotesting.UpdateActionImpl{{
 			Object: Route("default", "new-latest-ready", WithConfigTarget("config"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00002",
@@ -760,7 +760,7 @@ func TestReconcile(t *testing.T) {
 			Object: Route("default", "becomes-local", WithConfigTarget("config"),
 				WithRouteUID("65-23"),
 				MarkTrafficAssigned, MarkIngressNotConfigured,
-				WithLocalDomain, WithAddress, WithInitRouteConditions,
+				WithLocalDomain, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				WithRouteLabel(map[string]string{"serving.knative.dev/visibility": "cluster-local"}),
 				WithStatusTraffic(
 					v1.TrafficTarget{
@@ -824,7 +824,7 @@ func TestReconcile(t *testing.T) {
 			Object: Route("default", "becomes-public", WithConfigTarget("config"),
 				WithRouteUID("65-23"),
 				MarkTrafficAssigned, MarkIngressNotConfigured,
-				WithAddress, WithInitRouteConditions, WithURL,
+				WithAddress, WithRouteConditionsAutoTLSDisabled, WithURL,
 				WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -842,7 +842,7 @@ func TestReconcile(t *testing.T) {
 		},
 		Objects: []runtime.Object{
 			Route("default", "update-ci-failure", WithConfigTarget("config"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName: "config-00001",
@@ -894,7 +894,7 @@ func TestReconcile(t *testing.T) {
 		}},
 		WantStatusUpdates: []clientgotesting.UpdateActionImpl{{
 			Object: Route("default", "update-ci-failure", WithConfigTarget("config"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00002",
@@ -910,7 +910,7 @@ func TestReconcile(t *testing.T) {
 		Name: "reconcile service mutation",
 		Objects: []runtime.Object{
 			Route("default", "svc-mutation", WithConfigTarget("config"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -954,7 +954,7 @@ func TestReconcile(t *testing.T) {
 		},
 		Objects: []runtime.Object{
 			Route("default", "svc-mutation", WithConfigTarget("config"), WithRouteFinalizer,
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -999,7 +999,7 @@ func TestReconcile(t *testing.T) {
 		Name: "drop cluster ip",
 		Objects: []runtime.Object{
 			Route("default", "cluster-ip", WithConfigTarget("config"), WithRouteFinalizer,
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -1039,7 +1039,7 @@ func TestReconcile(t *testing.T) {
 		Name: "fix external name",
 		Objects: []runtime.Object{
 			Route("default", "external-name", WithConfigTarget("config"), WithRouteFinalizer,
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -1078,7 +1078,7 @@ func TestReconcile(t *testing.T) {
 		Name: "reconcile ingress mutation",
 		Objects: []runtime.Object{
 			Route("default", "ingress-mutation", WithConfigTarget("config"), WithRouteFinalizer,
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -1133,7 +1133,7 @@ func TestReconcile(t *testing.T) {
 		Objects: []runtime.Object{
 			// The status reflects "oldconfig", but the spec "newconfig".
 			Route("default", "change-configs", WithConfigTarget("newconfig"), WithRouteFinalizer,
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName: "oldconfig-00001",
@@ -1189,7 +1189,7 @@ func TestReconcile(t *testing.T) {
 		WantStatusUpdates: []clientgotesting.UpdateActionImpl{{
 			// Status updated to "newconfig"
 			Object: Route("default", "change-configs", WithConfigTarget("newconfig"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "newconfig-00001",
@@ -1270,7 +1270,7 @@ func TestReconcile(t *testing.T) {
 			Object: Route("default", "pinned-becomes-ready",
 				// Use the Revision name from the config
 				WithRevTarget("config-00001"), WithRouteFinalizer,
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -1352,7 +1352,7 @@ func TestReconcile(t *testing.T) {
 					ConfigurationName: "green",
 					Percent:           ptr.Int64(50),
 				}), WithRouteUID("34-78"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressNotConfigured, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "blue-00001",
@@ -1489,7 +1489,7 @@ func TestReconcile(t *testing.T) {
 						RevisionName: "gray-00001",
 						Percent:      ptr.Int64(50),
 					}), WithRouteUID("1-2"), WithRouteFinalizer,
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressNotConfigured, WithStatusTraffic(
 					v1.TrafficTarget{
 						Tag:            "gray",
@@ -1524,7 +1524,7 @@ func TestReconcile(t *testing.T) {
 		// Start from a steady state referencing "blue", and modify the route spec to point to "green" instead.
 		Objects: []runtime.Object{
 			Route("default", "switch-configs", WithConfigTarget("green"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithStatusTraffic(
 					v1.TrafficTarget{
 						Tag:          "blue",
@@ -1579,7 +1579,7 @@ func TestReconcile(t *testing.T) {
 		}},
 		WantStatusUpdates: []clientgotesting.UpdateActionImpl{{
 			Object: Route("default", "switch-configs", WithConfigTarget("green"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "green-00001",
@@ -1642,7 +1642,7 @@ func TestReconcile(t *testing.T) {
 		Name: "Update stale lastPinned",
 		Objects: []runtime.Object{
 			Route("default", "stale-lastpinned", WithConfigTarget("config"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -1682,7 +1682,7 @@ func TestReconcile(t *testing.T) {
 		Name: "check that we can find the ingress with old naming",
 		Objects: []runtime.Object{
 			Route("default", "old-naming", WithConfigTarget("config"), WithRouteFinalizer,
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
@@ -1718,7 +1718,7 @@ func TestReconcile(t *testing.T) {
 		Name: "deletes service when route no longer references service",
 		Objects: []runtime.Object{
 			Route("default", "my-route", WithConfigTarget("config"),
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithRouteConditionsAutoTLSDisabled,
 				MarkTrafficAssigned, MarkIngressReady,
 				WithRouteFinalizer, WithStatusTraffic(
 					v1.TrafficTarget{
@@ -1923,13 +1923,13 @@ func TestReconcile_EnableAutoTLS(t *testing.T) {
 			Object: Route("default", "becomes-ready", WithConfigTarget("config"),
 				WithRouteUID("12-34"),
 				// Populated by reconciliation when all traffic has been assigned.
-				WithURL, WithAddress, WithInitRouteConditions,
+				WithURL, WithAddress, WithInitRouteConditions, WithRouteConditionsHTTPDowngrade,
 				MarkTrafficAssigned, MarkIngressNotConfigured, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
 						Percent:        ptr.Int64(100),
 						LatestRevision: ptr.Bool(true),
-					}), MarkCertificateNotReady),
+					})),
 		}},
 		WantEvents: []string{
 			Eventf(corev1.EventTypeNormal, "Created", "Created placeholder service %q", "becomes-ready"),
@@ -2157,17 +2157,16 @@ func TestReconcile_EnableAutoTLS(t *testing.T) {
 				WithRouteUID("12-34"),
 				// Populated by reconciliation when all traffic has been assigned.
 				WithAddress, WithInitRouteConditions,
+				// The certificate has to be created in the not ready state for the ACME challenge
+				// ingress rules to be added.
 				MarkTrafficAssigned, MarkIngressNotConfigured, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
 						Percent:        ptr.Int64(100),
 						LatestRevision: ptr.Bool(true),
 					}),
-				// The certificate has to be created in the not ready state for the ACME challenge
-				// ingress rules to be added.
-				MarkCertificateNotReady,
 				// Which also means no HTTPS URL
-				WithURL,
+				WithURL, WithRouteConditionsHTTPDowngrade,
 			),
 		}},
 		Key: "default/becomes-ready",
@@ -2279,13 +2278,13 @@ func TestReconcile_EnableAutoTLS(t *testing.T) {
 			Object: Route("default", "becomes-ready", WithConfigTarget("config"),
 				WithRouteUID("12-34"),
 				// Populated by reconciliation when all traffic has been assigned.
-				WithAddress, WithInitRouteConditions,
+				WithAddress, WithRouteConditionsHTTPDowngrade,
 				MarkTrafficAssigned, MarkIngressNotConfigured, WithStatusTraffic(
 					v1.TrafficTarget{
 						RevisionName:   "config-00001",
 						Percent:        ptr.Int64(100),
 						LatestRevision: ptr.Bool(true),
-					}), MarkCertificateNotReady, MarkIngressNotConfigured,
+					}), MarkIngressNotConfigured,
 				// The certificate is not ready. So we want to have HTTP URL.
 				WithURL),
 		}},
diff --git a/pkg/testing/v1/route.go b/pkg/testing/v1/route.go
index 3cd22a476..54394f379 100644
--- a/pkg/testing/v1/route.go
+++ b/pkg/testing/v1/route.go
@@ -159,6 +159,18 @@ func WithInitRouteConditions(rt *v1.Route) {
 	rt.Status.InitializeConditions()
 }
 
+// WithRouteConditionsAutoTLSDisabled calls MarkAutoTLSNotEnabled after initialized the Service's conditions.
+func WithRouteConditionsAutoTLSDisabled(rt *v1.Route) {
+	rt.Status.InitializeConditions()
+	rt.Status.MarkAutoTLSNotEnabled()
+}
+
+// WithRouteConditionsHTTPDowngrade calls MarkHTTPDowngrade after initialized the Service's conditions.
+func WithRouteConditionsHTTPDowngrade(rt *v1.Route) {
+	rt.Status.InitializeConditions()
+	rt.Status.MarkHTTPDowngrade(routenames.Certificate(rt))
+}
+
 // MarkTrafficAssigned calls the method of the same name on .Status
 func MarkTrafficAssigned(r *v1.Route) {
 	r.Status.MarkTrafficAssigned()
diff --git a/test/e2e-tests.sh b/test/e2e-tests.sh
index d21fbc66c..a9a2dce34 100755
--- a/test/e2e-tests.sh
+++ b/test/e2e-tests.sh
@@ -85,11 +85,17 @@ add_trap "kubectl -n ${SYSTEM_NAMESPACE} patch configmap/config-autoscaler --typ
 # Run conformance and e2e tests.
 
 go_test_e2e -timeout=30m \
-  $(go list ./test/conformance/... | grep -v certificate) \
+  $(go list ./test/conformance/... | grep -v 'certificate\|ingress' ) \
   ./test/e2e ./test/e2e/hpa \
   ${parallelism} \
   "--resolvabledomain=$(use_resolvable_domain)" "${use_https}" "$(ingress_class)" || failed=1
 
+# We run KIngress conformance ingress separately, to make it easier to skip some tests.
+go_test_e2e -timeout=20m ./test/conformance/ingress ${parallelism}  \
+  `# Skip TestUpdate due to excessive flaking https://github.com/knative/serving/issues/8032` \
+  -run="Test[^U]" \
+  "--resolvabledomain=$(use_resolvable_domain)" "${use_https}" "$(ingress_class)" || failed=1
+
 if (( HTTPS )); then
   kubectl delete -f ${TMP_DIR}/test/config/autotls/certmanager/caissuer/ --ignore-not-found
   turn_off_auto_tls
diff --git a/test/ha/activator_test.go b/test/ha/activator_test.go
index cd8025ed4..80282dd08 100644
--- a/test/ha/activator_test.go
+++ b/test/ha/activator_test.go
@@ -42,6 +42,7 @@ const (
 )
 
 func TestActivatorHAGraceful(t *testing.T) {
+	t.Skip("TODO(8066): This was added too optimistically. Needs debugging and triaging.")
 	testActivatorHA(t, nil, 1)
 }
 
diff --git a/test/performance/latency_test.go b/test/performance/latency_test.go
index 7af54f07b..47081f451 100644
--- a/test/performance/latency_test.go
+++ b/test/performance/latency_test.go
@@ -49,19 +49,18 @@ const (
 func timeToServe(t *testing.T, img, query string, reqTimeout time.Duration) {
 	t.Helper()
 	tName := t.Name()
-	perfClients, err := Setup(t)
+	clients, err := Setup()
 	if err != nil {
-		t.Fatal("Cannot initialize performance client:", err)
+		t.Fatal("Cannot initialize performance clients:", err)
 	}
 
-	clients := perfClients.E2EClients
 	names := test.ResourceNames{
 		Service: test.ObjectNameForTest(t),
 		Image:   img,
 	}
 
-	defer TearDown(perfClients, names, t.Logf)
-	test.CleanupOnInterrupt(func() { TearDown(perfClients, names, t.Logf) })
+	defer TearDown(clients, names, t.Logf)
+	test.CleanupOnInterrupt(func() { TearDown(clients, names, t.Logf) })
 
 	t.Log("Creating a new Service")
 	objs, err := v1a1test.CreateRunLatestServiceReady(t, clients, &names)
diff --git a/test/performance/observed_concurency_test.go b/test/performance/observed_concurency_test.go
index 1efd838ef..7f6f4eba1 100644
--- a/test/performance/observed_concurency_test.go
+++ b/test/performance/observed_concurency_test.go
@@ -137,7 +137,7 @@ func TestObservedConcurrency(t *testing.T) {
 }
 
 func testConcurrencyN(t *testing.T, concurrency int) []junit.TestCase {
-	perfClients, err := Setup(t)
+	clients, err := Setup()
 	if err != nil {
 		t.Fatal("Cannot initialize performance client:", err)
 	}
@@ -146,10 +146,9 @@ func testConcurrencyN(t *testing.T, concurrency int) []junit.TestCase {
 		Service: test.ObjectNameForTest(t),
 		Image:   "observed-concurrency",
 	}
-	clients := perfClients.E2EClients
 
-	defer TearDown(perfClients, names, t.Logf)
-	test.CleanupOnInterrupt(func() { TearDown(perfClients, names, t.Logf) })
+	defer TearDown(clients, names, t.Logf)
+	test.CleanupOnInterrupt(func() { TearDown(clients, names, t.Logf) })
 
 	t.Log("Creating a new Service")
 	objs, err := v1test.CreateServiceReady(t, clients, &names,
diff --git a/test/performance/performance.go b/test/performance/performance.go
index 0984b4b91..763ced9d3 100644
--- a/test/performance/performance.go
+++ b/test/performance/performance.go
@@ -21,7 +21,6 @@ import (
 	"fmt"
 	"log"
 	"net/http"
-	"testing"
 	"time"
 
 	v1 "k8s.io/api/core/v1"
@@ -30,7 +29,6 @@ import (
 	podinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/pod"
 	pkgTest "knative.dev/pkg/test"
 	"knative.dev/pkg/test/logging"
-	"knative.dev/pkg/test/prometheus"
 	"knative.dev/serving/test"
 
 	"knative.dev/pkg/test/spoof"
@@ -40,50 +38,18 @@ import (
 	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
 )
 
-const (
-	monitoringNS = "knative-monitoring"
-	duration     = 1 * time.Minute
-
-	EnablePrometheus = iota
-)
-
-// Client is the client used in the performance tests.
-type Client struct {
-	E2EClients *test.Clients
-	PromClient *prometheus.PromProxy
-}
+const duration = 1 * time.Minute
 
 // Setup creates all the clients that we need to interact with in our tests
-func Setup(t *testing.T, monitoring ...int) (*Client, error) {
+func Setup() (*test.Clients, error) {
 	pkgTest.SetupLoggingFlags()
 	clients, err := test.NewClients(pkgTest.Flags.Kubeconfig, pkgTest.Flags.Cluster, test.ServingNamespace)
-	if err != nil {
-		return nil, err
-	}
-
-	var p *prometheus.PromProxy
-	for _, m := range monitoring {
-		switch m {
-		case EnablePrometheus:
-			t.Log("Creating prometheus proxy client")
-			p = &prometheus.PromProxy{Namespace: monitoringNS}
-			p.Setup(clients.KubeClient.Kube, t.Logf)
-		default:
-			t.Log("No monitoring components enabled")
-		}
-	}
-
-	return &Client{E2EClients: clients, PromClient: p}, nil
+	return clients, err
 }
 
 // TearDown cleans up resources used
-func TearDown(client *Client, names test.ResourceNames, logf logging.FormatLogger) {
-	test.TearDown(client.E2EClients, names)
-
-	// Teardown prometheus client
-	if client.PromClient != nil {
-		client.PromClient.Teardown(logf)
-	}
+func TearDown(clients *test.Clients, names test.ResourceNames, logf logging.FormatLogger) {
+	test.TearDown(clients, names)
 }
 
 // ProbeTargetTillReady will probe the target once per second for the given duration, until it's ready or error happens
@@ -113,7 +79,7 @@ func ProbeTargetTillReady(target string, duration time.Duration) error {
 func WaitForScaleToZero(ctx context.Context, namespace string, selector labels.Selector, duration time.Duration) error {
 	pl := podinformer.Get(ctx).Lister()
 	begin := time.Now()
-	return wait.PollImmediate(1*time.Second, duration, func() (bool, error) {
+	return wait.PollImmediate(time.Second, duration, func() (bool, error) {
 		pods, err := pl.Pods(namespace).List(selector)
 		if err != nil {
 			return false, err
diff --git a/third_party/contour-latest/contour.yaml b/third_party/contour-latest/contour.yaml
index ddd207a20..150871ea2 100644
--- a/third_party/contour-latest/contour.yaml
+++ b/third_party/contour-latest/contour.yaml
@@ -2,6 +2,8 @@ apiVersion: rbac.authorization.k8s.io/v1beta1
 kind: ClusterRoleBinding
 metadata:
   name: contour-external
+  labels:
+    networking.knative.dev/ingress-provider: contour
 roleRef:
   apiGroup: rbac.authorization.k8s.io
   kind: ClusterRole
@@ -15,24 +17,32 @@ apiVersion: v1
 kind: Namespace
 metadata:
   name: contour-external
+  labels:
+    networking.knative.dev/ingress-provider: contour
 ---
 apiVersion: v1
 kind: ServiceAccount
 metadata:
   name: contour
   namespace: contour-external
+  labels:
+    networking.knative.dev/ingress-provider: contour
 ---
 apiVersion: v1
 kind: ServiceAccount
 metadata:
   name: envoy
   namespace: contour-external
+  labels:
+    networking.knative.dev/ingress-provider: contour
 ---
 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: contour
   namespace: contour-external
+  labels:
+    networking.knative.dev/ingress-provider: contour
 data:
   contour.yaml: |
     leaderelection:
@@ -98,6 +108,8 @@ metadata:
     controller-gen.kubebuilder.io/version: v0.2.4
   creationTimestamp: null
   name: ingressroutes.contour.heptio.com
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   additionalPrinterColumns:
   - JSONPath: .spec.virtualhost.fqdn
@@ -467,6 +479,8 @@ metadata:
     controller-gen.kubebuilder.io/version: v0.2.4
   creationTimestamp: null
   name: tlscertificatedelegations.contour.heptio.com
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   group: contour.heptio.com
   names:
@@ -543,6 +557,8 @@ metadata:
     controller-gen.kubebuilder.io/version: v0.2.4
   creationTimestamp: null
   name: httpproxies.projectcontour.io
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   additionalPrinterColumns:
   - JSONPath: .spec.virtualhost.fqdn
@@ -1278,6 +1294,8 @@ metadata:
     controller-gen.kubebuilder.io/version: v0.2.4
   creationTimestamp: null
   name: tlscertificatedelegations.projectcontour.io
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   group: projectcontour.io
   names:
@@ -1354,12 +1372,16 @@ kind: ServiceAccount
 metadata:
   name: contour-certgen
   namespace: contour-external
+  labels:
+    networking.knative.dev/ingress-provider: contour
 ---
 apiVersion: rbac.authorization.k8s.io/v1beta1
 kind: RoleBinding
 metadata:
   name: contour
   namespace: contour-external
+  labels:
+    networking.knative.dev/ingress-provider: contour
 roleRef:
   apiGroup: rbac.authorization.k8s.io
   kind: Role
@@ -1374,6 +1396,8 @@ kind: Role
 metadata:
   name: contour-certgen
   namespace: contour-external
+  labels:
+    networking.knative.dev/ingress-provider: contour
 rules:
 - apiGroups:
   - ""
@@ -1393,6 +1417,8 @@ kind: Job
 metadata:
   name: contour-certgen
   namespace: contour-external
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   ttlSecondsAfterFinished: 0
   template:
@@ -1406,7 +1432,7 @@ spec:
         # if we change this on each version, you can no longer upgrade
         # just by applying the deployment YAML.
         # See #2423, #2395, #2150, and #2030 for earlier questions about this.
-        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:8d3d3f935e36974dfe5ec7f7499862c7a621325536b8c01df68bcfaaea98de33
+        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:68fa5b91335cca8c5d3dfc1bf611194eb1e0741d2f7a33f89839f1b496388f6c
         imagePullPolicy: Always
         command:
         - contour
@@ -1433,6 +1459,8 @@ apiVersion: rbac.authorization.k8s.io/v1beta1
 kind: ClusterRole
 metadata:
   name: contour
+  labels:
+    networking.knative.dev/ingress-provider: contour
 rules:
 - apiGroups:
   - ""
@@ -1511,6 +1539,8 @@ kind: Role
 metadata:
   name: contour-leaderelection
   namespace: contour-external
+  labels:
+    networking.knative.dev/ingress-provider: contour
 rules:
 - apiGroups:
   - ""
@@ -1536,6 +1566,8 @@ kind: RoleBinding
 metadata:
   name: contour-leaderelection
   namespace: contour-external
+  labels:
+    networking.knative.dev/ingress-provider: contour
 roleRef:
   apiGroup: rbac.authorization.k8s.io
   kind: Role
@@ -1550,6 +1582,8 @@ kind: Service
 metadata:
   name: contour
   namespace: contour-external
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   ports:
   - port: 8001
@@ -1573,6 +1607,8 @@ metadata:
     # for information about enabling the PROXY protocol on the ELB to recover
     # the original remote IP address.
     service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   externalTrafficPolicy: Local
   ports:
@@ -1591,6 +1627,7 @@ kind: Deployment
 metadata:
   labels:
     app: contour
+    networking.knative.dev/ingress-provider: contour
   name: contour
   namespace: contour-external
 spec:
@@ -1635,7 +1672,7 @@ spec:
         - --contour-key-file=/certs/tls.key
         - --config-path=/config/contour.yaml
         command: ["contour"]
-        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:8d3d3f935e36974dfe5ec7f7499862c7a621325536b8c01df68bcfaaea98de33
+        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:68fa5b91335cca8c5d3dfc1bf611194eb1e0741d2f7a33f89839f1b496388f6c
         imagePullPolicy: Always
         name: contour
         ports:
@@ -1701,6 +1738,7 @@ kind: DaemonSet
 metadata:
   labels:
     app: envoy
+    networking.knative.dev/ingress-provider: contour
   name: envoy
   namespace: contour-external
 spec:
@@ -1726,7 +1764,7 @@ spec:
         args:
         - envoy
         - shutdown-manager
-        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:8d3d3f935e36974dfe5ec7f7499862c7a621325536b8c01df68bcfaaea98de33
+        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:68fa5b91335cca8c5d3dfc1bf611194eb1e0741d2f7a33f89839f1b496388f6c
         imagePullPolicy: Always
         lifecycle:
           preStop:
@@ -1802,7 +1840,7 @@ spec:
         - --envoy-key-file=/certs/tls.key
         command:
         - contour
-        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:8d3d3f935e36974dfe5ec7f7499862c7a621325536b8c01df68bcfaaea98de33
+        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:68fa5b91335cca8c5d3dfc1bf611194eb1e0741d2f7a33f89839f1b496388f6c
         imagePullPolicy: Always
         name: envoy-initconfig
         volumeMounts:
@@ -1831,14 +1869,14 @@ spec:
         secret:
           secretName: cacert
       restartPolicy: Always
----
-
 
 ---
 apiVersion: rbac.authorization.k8s.io/v1beta1
 kind: ClusterRoleBinding
 metadata:
   name: contour-internal
+  labels:
+    networking.knative.dev/ingress-provider: contour
 roleRef:
   apiGroup: rbac.authorization.k8s.io
   kind: ClusterRole
@@ -1852,24 +1890,32 @@ apiVersion: v1
 kind: Namespace
 metadata:
   name: contour-internal
+  labels:
+    networking.knative.dev/ingress-provider: contour
 ---
 apiVersion: v1
 kind: ServiceAccount
 metadata:
   name: contour
   namespace: contour-internal
+  labels:
+    networking.knative.dev/ingress-provider: contour
 ---
 apiVersion: v1
 kind: ServiceAccount
 metadata:
   name: envoy
   namespace: contour-internal
+  labels:
+    networking.knative.dev/ingress-provider: contour
 ---
 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: contour
   namespace: contour-internal
+  labels:
+    networking.knative.dev/ingress-provider: contour
 data:
   contour.yaml: |
     leaderelection:
@@ -1935,6 +1981,8 @@ metadata:
     controller-gen.kubebuilder.io/version: v0.2.4
   creationTimestamp: null
   name: ingressroutes.contour.heptio.com
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   additionalPrinterColumns:
   - JSONPath: .spec.virtualhost.fqdn
@@ -2304,6 +2352,8 @@ metadata:
     controller-gen.kubebuilder.io/version: v0.2.4
   creationTimestamp: null
   name: tlscertificatedelegations.contour.heptio.com
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   group: contour.heptio.com
   names:
@@ -2380,6 +2430,8 @@ metadata:
     controller-gen.kubebuilder.io/version: v0.2.4
   creationTimestamp: null
   name: httpproxies.projectcontour.io
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   additionalPrinterColumns:
   - JSONPath: .spec.virtualhost.fqdn
@@ -3115,6 +3167,8 @@ metadata:
     controller-gen.kubebuilder.io/version: v0.2.4
   creationTimestamp: null
   name: tlscertificatedelegations.projectcontour.io
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   group: projectcontour.io
   names:
@@ -3191,12 +3245,16 @@ kind: ServiceAccount
 metadata:
   name: contour-certgen
   namespace: contour-internal
+  labels:
+    networking.knative.dev/ingress-provider: contour
 ---
 apiVersion: rbac.authorization.k8s.io/v1beta1
 kind: RoleBinding
 metadata:
   name: contour
   namespace: contour-internal
+  labels:
+    networking.knative.dev/ingress-provider: contour
 roleRef:
   apiGroup: rbac.authorization.k8s.io
   kind: Role
@@ -3211,6 +3269,8 @@ kind: Role
 metadata:
   name: contour-certgen
   namespace: contour-internal
+  labels:
+    networking.knative.dev/ingress-provider: contour
 rules:
 - apiGroups:
   - ""
@@ -3230,6 +3290,8 @@ kind: Job
 metadata:
   name: contour-certgen
   namespace: contour-internal
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   ttlSecondsAfterFinished: 0
   template:
@@ -3243,7 +3305,7 @@ spec:
         # if we change this on each version, you can no longer upgrade
         # just by applying the deployment YAML.
         # See #2423, #2395, #2150, and #2030 for earlier questions about this.
-        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:8d3d3f935e36974dfe5ec7f7499862c7a621325536b8c01df68bcfaaea98de33
+        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:68fa5b91335cca8c5d3dfc1bf611194eb1e0741d2f7a33f89839f1b496388f6c
         imagePullPolicy: Always
         command:
         - contour
@@ -3270,6 +3332,8 @@ apiVersion: rbac.authorization.k8s.io/v1beta1
 kind: ClusterRole
 metadata:
   name: contour
+  labels:
+    networking.knative.dev/ingress-provider: contour
 rules:
 - apiGroups:
   - ""
@@ -3348,6 +3412,8 @@ kind: Role
 metadata:
   name: contour-leaderelection
   namespace: contour-internal
+  labels:
+    networking.knative.dev/ingress-provider: contour
 rules:
 - apiGroups:
   - ""
@@ -3373,6 +3439,8 @@ kind: RoleBinding
 metadata:
   name: contour-leaderelection
   namespace: contour-internal
+  labels:
+    networking.knative.dev/ingress-provider: contour
 roleRef:
   apiGroup: rbac.authorization.k8s.io
   kind: Role
@@ -3387,6 +3455,8 @@ kind: Service
 metadata:
   name: contour
   namespace: contour-internal
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   ports:
   - port: 8001
@@ -3410,6 +3480,8 @@ metadata:
     # for information about enabling the PROXY protocol on the ELB to recover
     # the original remote IP address.
     service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   # externalTrafficPolicy: Local
   ports:
@@ -3428,6 +3500,7 @@ kind: Deployment
 metadata:
   labels:
     app: contour
+    networking.knative.dev/ingress-provider: contour
   name: contour
   namespace: contour-internal
 spec:
@@ -3472,7 +3545,7 @@ spec:
         - --contour-key-file=/certs/tls.key
         - --config-path=/config/contour.yaml
         command: ["contour"]
-        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:8d3d3f935e36974dfe5ec7f7499862c7a621325536b8c01df68bcfaaea98de33
+        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:68fa5b91335cca8c5d3dfc1bf611194eb1e0741d2f7a33f89839f1b496388f6c
         imagePullPolicy: Always
         name: contour
         ports:
@@ -3538,6 +3611,7 @@ kind: DaemonSet
 metadata:
   labels:
     app: envoy
+    networking.knative.dev/ingress-provider: contour
   name: envoy
   namespace: contour-internal
 spec:
@@ -3563,7 +3637,7 @@ spec:
         args:
         - envoy
         - shutdown-manager
-        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:8d3d3f935e36974dfe5ec7f7499862c7a621325536b8c01df68bcfaaea98de33
+        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:68fa5b91335cca8c5d3dfc1bf611194eb1e0741d2f7a33f89839f1b496388f6c
         imagePullPolicy: Always
         lifecycle:
           preStop:
@@ -3639,7 +3713,7 @@ spec:
         - --envoy-key-file=/certs/tls.key
         command:
         - contour
-        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:8d3d3f935e36974dfe5ec7f7499862c7a621325536b8c01df68bcfaaea98de33
+        image: gcr.io/knative-nightly/github.com/projectcontour/contour/cmd/contour@sha256:68fa5b91335cca8c5d3dfc1bf611194eb1e0741d2f7a33f89839f1b496388f6c
         imagePullPolicy: Always
         name: envoy-initconfig
         volumeMounts:
@@ -3668,7 +3742,5 @@ spec:
         secret:
           secretName: cacert
       restartPolicy: Always
----
-
 
 ---
diff --git a/third_party/contour-latest/net-contour.yaml b/third_party/contour-latest/net-contour.yaml
index 78a55da15..0a5e9c3d9 100644
--- a/third_party/contour-latest/net-contour.yaml
+++ b/third_party/contour-latest/net-contour.yaml
@@ -5,6 +5,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 metadata:
   name: knative-contour-core
   labels:
+    networking.knative.dev/ingress-provider: contour
     serving.knative.dev/controller: "true"
 rules:
 - apiGroups: ["projectcontour.io"]
@@ -33,7 +34,8 @@ metadata:
   name: config-contour
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20200518-f3b733a"
+    networking.knative.dev/ingress-provider: contour
+    serving.knative.dev/release: "v20200525-d02a1c4"
 data:
   _example: |
     ################################
@@ -75,6 +77,8 @@ kind: Deployment
 metadata:
   name: contour-ingress-controller
   namespace: knative-serving
+  labels:
+    networking.knative.dev/ingress-provider: contour
 spec:
   replicas: 1
   selector:
@@ -90,7 +94,7 @@ spec:
       - name: controller
         # This is the Go import path for the binary that is containerized
         # and substituted here.
-        image: gcr.io/knative-nightly/knative.dev/net-contour/cmd/controller@sha256:adf935d27fdfc204e862abff0ced57471ae7b6fbacc6e9c9522b4c43daca614e
+        image: gcr.io/knative-nightly/knative.dev/net-contour/cmd/controller@sha256:b6528a96949370697e2b7a96ad2ab847cf0c5a1ba0b5d5884241021f73cf3700
         resources:
           requests:
             cpu: 40m
diff --git a/third_party/kourier-latest/kourier.yaml b/third_party/kourier-latest/kourier.yaml
index 6c0031d76..c016164c8 100644
--- a/third_party/kourier-latest/kourier.yaml
+++ b/third_party/kourier-latest/kourier.yaml
@@ -2,24 +2,32 @@ apiVersion: v1
 kind: Namespace
 metadata:
   name: kourier-system
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 ---
 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: config-logging
   namespace: kourier-system
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 ---
 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: config-observability
   namespace: kourier-system
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 ---
 apiVersion: v1
 kind: ConfigMap
 metadata:
   name: config-leader-election
   namespace: kourier-system
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 data:
   _example: |
     ################################
@@ -69,6 +77,8 @@ kind: Service
 metadata:
   name: kourier
   namespace: kourier-system
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 spec:
   ports:
   - name: http2
@@ -88,6 +98,8 @@ kind: Deployment
 metadata:
   name: 3scale-kourier-gateway
   namespace: kourier-system
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 spec:
   selector:
     matchLabels:
@@ -103,7 +115,7 @@ spec:
         - /tmp/config/envoy-bootstrap.yaml
         command:
         - /usr/local/bin/envoy
-        image: docker.io/maistra/proxyv2-ubi8:1.0.8
+        image: docker.io/maistra/proxyv2-ubi8:1.1.0
         imagePullPolicy: Always
         name: kourier-gateway
         ports:
@@ -116,16 +128,20 @@ spec:
         volumeMounts:
         - name: config-volume
           mountPath: /tmp/config
+        lifecycle:
+          preStop:
+            exec:
+              command: ["curl", "-X", "POST", "--unix", "/tmp/envoy.admin", "http://localhost/healthcheck/fail"]
         readinessProbe:
           httpGet:
             httpHeaders:
             - name: Host
               value: internalkourier
-            path: /__internalkouriersnapshot
+            path: /ready
             port: 8081
             scheme: HTTP
-          initialDelaySeconds: 5
-          periodSeconds: 2
+          initialDelaySeconds: 10
+          periodSeconds: 5
       volumes:
       - name: config-volume
         configMap:
@@ -137,6 +153,8 @@ kind: Deployment
 metadata:
   name: 3scale-kourier-control
   namespace: kourier-system
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 spec:
   replicas: 1
   selector:
@@ -148,7 +166,7 @@ spec:
         app: 3scale-kourier-control
     spec:
       containers:
-      - image: gcr.io/knative-nightly/knative.dev/net-kourier/cmd/kourier@sha256:4c1bba191b7751a31d4bf7679a0e7cf5ce47abadb1fa8983b7064218f7a2fadb
+      - image: gcr.io/knative-nightly/knative.dev/net-kourier/cmd/kourier@sha256:f1ec90d11071ec5fbe9e83fa8fa340bf1b338a395e52e4dd3de2f5ce89ad5679
         imagePullPolicy: Always
         name: kourier-control
         env:
@@ -168,6 +186,8 @@ kind: ClusterRole
 metadata:
   name: 3scale-kourier
   namespace: kourier-system
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 rules:
 - apiGroups: [""]
   resources: ["pods", "endpoints", "namespaces", "services", "secrets", "configmaps"]
@@ -190,11 +210,15 @@ kind: ServiceAccount
 metadata:
   name: 3scale-kourier
   namespace: kourier-system
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 ---
 apiVersion: rbac.authorization.k8s.io/v1beta1
 kind: ClusterRoleBinding
 metadata:
   name: 3scale-kourier
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 roleRef:
   apiGroup: rbac.authorization.k8s.io
   kind: ClusterRole
@@ -209,6 +233,8 @@ kind: Service
 metadata:
   name: kourier-internal
   namespace: kourier-system
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 spec:
   ports:
   - name: http2
@@ -224,6 +250,8 @@ kind: Service
 metadata:
   name: kourier-control
   namespace: kourier-system
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 spec:
   ports:
   - port: 18000
@@ -238,6 +266,8 @@ kind: ConfigMap
 metadata:
   name: kourier-bootstrap
   namespace: kourier-system
+  labels:
+    networking.knative.dev/ingress-provider: kourier
 data:
   envoy-bootstrap.yaml: |
     dynamic_resources:
diff --git a/vendor/knative.dev/pkg/test/prometheus/prometheus.go b/vendor/knative.dev/pkg/test/prometheus/prometheus.go
deleted file mode 100644
index 270927f23..000000000
--- a/vendor/knative.dev/pkg/test/prometheus/prometheus.go
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
-Copyright 2018 The Knative Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-	https://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package prometheus
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"sync"
-	"time"
-
-	"github.com/prometheus/client_golang/api"
-	v1 "github.com/prometheus/client_golang/api/prometheus/v1"
-	"github.com/prometheus/common/model"
-	"k8s.io/client-go/kubernetes"
-	"knative.dev/pkg/test/logging"
-	"knative.dev/pkg/test/monitoring"
-)
-
-const (
-	prometheusPort = 9090
-	appLabel       = "prometheus"
-)
-
-var (
-	// sync.Once variable to ensure we execute zipkin setup only once.
-	setupOnce sync.Once
-
-	// sync.Once variable to ensure we execute zipkin cleanup only if zipkin is setup and it is executed only once.
-	teardownOnce sync.Once
-)
-
-// PromProxy defines a proxy to the prometheus server
-type PromProxy struct {
-	Namespace string
-	processID int
-}
-
-// Setup performs a port forwarding for app prometheus-test in given namespace
-func (p *PromProxy) Setup(kubeClientset *kubernetes.Clientset, logf logging.FormatLogger) {
-	setupOnce.Do(func() {
-		if err := monitoring.CheckPortAvailability(prometheusPort); err != nil {
-			logf("Prometheus port not available: %v", err)
-			return
-		}
-
-		promPods, err := monitoring.GetPods(kubeClientset, appLabel, p.Namespace)
-		if err != nil {
-			logf("Error retrieving prometheus pod details: %v", err)
-			return
-		}
-
-		p.processID, err = monitoring.PortForward(logf, promPods, prometheusPort, prometheusPort, p.Namespace)
-		if err != nil {
-			logf("Error starting kubectl port-forward command: %v", err)
-			return
-		}
-	})
-}
-
-// Teardown will kill the port forwarding process if running.
-func (p *PromProxy) Teardown(logf logging.FormatLogger) {
-	teardownOnce.Do(func() {
-		if err := monitoring.Cleanup(p.processID); err != nil {
-			logf("Encountered error killing port-forward process: %v", err)
-			return
-		}
-	})
-}
-
-// PromAPI gets a handle to the prometheus API
-func PromAPI() (v1.API, error) {
-	client, err := api.NewClient(api.Config{Address: fmt.Sprintf("http://localhost:%d", prometheusPort)})
-	if err != nil {
-		return nil, err
-	}
-	return v1.NewAPI(client), nil
-}
-
-// AllowPrometheusSync sleeps for sometime to allow prometheus time to scrape the metrics.
-func AllowPrometheusSync(logf logging.FormatLogger) {
-	logf("Sleeping to allow prometheus to record metrics...")
-	time.Sleep(30 * time.Second)
-}
-
-// RunQuery runs a prometheus query and returns the metric value
-func RunQuery(ctx context.Context, logf logging.FormatLogger, promAPI v1.API, query string) (float64, error) {
-	logf("Running prometheus query: %s", query)
-
-	value, err := promAPI.Query(ctx, query, time.Now())
-	if err != nil {
-		return 0, err
-	}
-
-	return VectorValue(value)
-}
-
-// RunQueryRange runs a prometheus query over the given range
-func RunQueryRange(ctx context.Context, logf logging.FormatLogger, promAPI v1.API, query string, r v1.Range) (float64, error) {
-	logf("Running prometheus query: %s", query)
-
-	value, err := promAPI.QueryRange(ctx, query, r)
-	if err != nil {
-		return 0, err
-	}
-
-	return VectorValue(value)
-}
-
-// VectorValue gets the vector value from the value type
-func VectorValue(val model.Value) (float64, error) {
-	if val.Type() != model.ValVector {
-		return 0, fmt.Errorf("Value type is %s. Expected: Valvector", val.String())
-	}
-	value := val.(model.Vector)
-	if len(value) == 0 {
-		return 0, errors.New("Query returned no results")
-	}
-
-	return float64(value[0].Value), nil
-}
diff --git a/vendor/knative.dev/pkg/test/prow/prow.go b/vendor/knative.dev/pkg/test/prow/prow.go
index a6e7f3a50..2af8a0095 100644
--- a/vendor/knative.dev/pkg/test/prow/prow.go
+++ b/vendor/knative.dev/pkg/test/prow/prow.go
@@ -34,9 +34,6 @@ import (
 )
 
 const (
-	// OrgName is the name of knative org
-	OrgName = "knative"
-
 	// BucketName is the gcs bucket for all knative builds
 	BucketName = "knative-prow"
 	// Latest is the filename storing latest build number
@@ -71,6 +68,7 @@ type Job struct {
 	Name        string
 	Type        string
 	Bucket      string  // optional
+	Org         string  // optional
 	Repo        string  // optional
 	StoragePath string  // optional
 	PullID      int     // only for Presubmit jobs
@@ -135,11 +133,13 @@ func Initialize(serviceAccount string) error {
 
 // NewJob creates new job struct
 // pullID is only saved by Presubmit job for determining StoragePath
-func NewJob(jobName, jobType, repoName string, pullID int) *Job {
+func NewJob(jobName, jobType, orgName, repoName string, pullID int) *Job {
 	job := Job{
 		Name:   jobName,
 		Type:   jobType,
 		Bucket: BucketName,
+		Org:    orgName,
+		Repo:   repoName,
 	}
 
 	switch jobType {
@@ -147,7 +147,7 @@ func NewJob(jobName, jobType, repoName string, pullID int) *Job {
 		job.StoragePath = path.Join("logs", jobName)
 	case PresubmitJob:
 		job.PullID = pullID
-		job.StoragePath = path.Join("pr-logs", "pull", OrgName+"_"+repoName, strconv.Itoa(pullID), jobName)
+		job.StoragePath = path.Join("pr-logs", "pull", orgName+"_"+repoName, strconv.Itoa(pullID), jobName)
 	case BatchJob:
 		job.StoragePath = path.Join("pr-logs", "pull", "batch", jobName)
 	default: