Repository: knative/serving
Pull request #10061 (2020-11-07 02:49:27)

Update serving module to Go 1.15
Fixes #

<!-- Please include the 'why' behind your changes if no issue exists -->
## Proposed Changes
* Update serving module to Go 1.15

**Release Note**

<!-- Enter your extended release note in the below block. If the PR requires
additional action from users switching to the new release, include the string
"action required". If no release note is required, write "NONE". -->

```release-note
NONE
```


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

This pull-request has been approved by: *<a href="https://github.com/knative/serving/pull/10061#" title="Author self-approved">chizhg</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **vagababov** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @vagababov` 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:

- **[OWNERS](https://github.com/knative/serving/blob/master/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":["vagababov"]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/knative/serving/pull/10061?src=pr&el=h1) Report
> Merging [#10061](https://codecov.io/gh/knative/serving/pull/10061?src=pr&el=desc) (529e89f) into [master](https://codecov.io/gh/knative/serving/commit/861357753161f70a68615fcf1c40e485a8cdedec?el=desc) (8613577) will **increase** coverage by `0.05%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/knative/serving/pull/10061/graphs/tree.svg?width=650&height=150&src=pr&token=MPpDfAj4Ui)](https://codecov.io/gh/knative/serving/pull/10061?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master   #10061      +/-   ##
==========================================
+ Coverage   87.96%   88.01%   +0.05%     
==========================================
  Files         185      185              
  Lines        8683     8686       +3     
==========================================
+ Hits         7638     7645       +7     
+ Misses        795      793       -2     
+ Partials      250      248       -2     
```


| [Impacted Files](https://codecov.io/gh/knative/serving/pull/10061?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/reconciler/route/traffic/rollout.go](https://codecov.io/gh/knative/serving/pull/10061/diff?src=pr&el=tree#diff-cGtnL3JlY29uY2lsZXIvcm91dGUvdHJhZmZpYy9yb2xsb3V0Lmdv) | `100.00% <0.00%> (ø)` | |
| [pkg/autoscaler/statforwarder/forwarder.go](https://codecov.io/gh/knative/serving/pull/10061/diff?src=pr&el=tree#diff-cGtnL2F1dG9zY2FsZXIvc3RhdGZvcndhcmRlci9mb3J3YXJkZXIuZ28=) | `82.44% <0.00%> (+1.26%)` | :arrow_up: |
| [pkg/reconciler/configuration/configuration.go](https://codecov.io/gh/knative/serving/pull/10061/diff?src=pr&el=tree#diff-cGtnL3JlY29uY2lsZXIvY29uZmlndXJhdGlvbi9jb25maWd1cmF0aW9uLmdv) | `88.28% <0.00%> (+1.56%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/knative/serving/pull/10061?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/knative/serving/pull/10061?src=pr&el=footer). Last update [8613577...529e89f](https://codecov.io/gh/knative/serving/pull/10061?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/runtime-contract.md b/docs/runtime-contract.md
index 87a587794..7cc7b0ea7 100644
--- a/docs/runtime-contract.md
+++ b/docs/runtime-contract.md
@@ -504,7 +504,8 @@ changes.
 From the OCI spec:
 
 > `rootfsPropagation` (string, OPTIONAL) sets the rootfs's mount propagation.
-> Its value is either slave, private, shared or unbindable. The
+> Its value is either slave,<!-- wokeignore:rule=slave --> private, shared or
+> unbindable. The
 > [Shared Subtrees](https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt)
 > article in the kernel documentation has more information about mount
 > propagation.
diff --git a/go.mod b/go.mod
index 8907930c9..413bd08e1 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module knative.dev/serving
 
-go 1.14
+go 1.15
 
 require (
 	github.com/davecgh/go-spew v1.1.1
diff --git a/pkg/activator/net/helpers.go b/pkg/activator/net/helpers.go
index 5f46d3546..ba51a5377 100644
--- a/pkg/activator/net/helpers.go
+++ b/pkg/activator/net/helpers.go
@@ -29,7 +29,17 @@ import (
 // healthyAddresses takes an endpoints object and a port name and return the set
 // of addresses that implement this port.
 func healthyAddresses(endpoints *corev1.Endpoints, portName string) sets.String {
-	ready := sets.NewString()
+	var addresses int
+	for _, es := range endpoints.Subsets {
+		for _, port := range es.Ports {
+			if port.Name == portName {
+				addresses += len(es.Addresses)
+				break
+			}
+		}
+	}
+
+	ready := make(sets.String, addresses)
 
 	for _, es := range endpoints.Subsets {
 		for _, port := range es.Ports {
@@ -48,8 +58,19 @@ func healthyAddresses(endpoints *corev1.Endpoints, portName string) sets.String
 // endpointsToDests takes an endpoints object and a port name and returns two sets of
 // ready and non-ready l4 dests in the endpoints object which have that port.
 func endpointsToDests(endpoints *corev1.Endpoints, portName string) (ready, notReady sets.String) {
-	ready = sets.NewString()
-	notReady = sets.NewString()
+	var readyAddresses, nonReadyAddresses int
+	for _, es := range endpoints.Subsets {
+		for _, port := range es.Ports {
+			if port.Name == portName {
+				readyAddresses += len(es.Addresses)
+				nonReadyAddresses += len(es.NotReadyAddresses)
+				break
+			}
+		}
+	}
+
+	ready = make(sets.String, readyAddresses)
+	notReady = make(sets.String, nonReadyAddresses)
 
 	for _, es := range endpoints.Subsets {
 		for _, port := range es.Ports {
diff --git a/pkg/apis/serving/v1/revision_lifecycle.go b/pkg/apis/serving/v1/revision_lifecycle.go
index 85089e9ae..fac3cd359 100644
--- a/pkg/apis/serving/v1/revision_lifecycle.go
+++ b/pkg/apis/serving/v1/revision_lifecycle.go
@@ -123,12 +123,16 @@ func (rs *RevisionStatus) MarkContainerHealthyTrue() {
 
 // MarkContainerHealthyFalse marks ContainerHealthy status on revision as False
 func (rs *RevisionStatus) MarkContainerHealthyFalse(reason, message string) {
-	revisionCondSet.Manage(rs).MarkFalse(RevisionConditionContainerHealthy, reason, message)
+	// We escape here, because errors sometimes contain `%` and that makes the error message
+	// quite poor.
+	revisionCondSet.Manage(rs).MarkFalse(RevisionConditionContainerHealthy, reason, "%s", message)
 }
 
 // MarkContainerHealthyUnknown marks ContainerHealthy status on revision as Unknown
 func (rs *RevisionStatus) MarkContainerHealthyUnknown(reason, message string) {
-	revisionCondSet.Manage(rs).MarkUnknown(RevisionConditionContainerHealthy, reason, message)
+	// We escape here, because errors sometimes contain `%` and that makes the error message
+	// quite poor.
+	revisionCondSet.Manage(rs).MarkUnknown(RevisionConditionContainerHealthy, reason, "%s", message)
 }
 
 // MarkResourcesAvailableTrue marks ResourcesAvailable status on revision as True
diff --git a/pkg/autoscaler/metrics/collector.go b/pkg/autoscaler/metrics/collector.go
index ce57d6810..32492c2a9 100644
--- a/pkg/autoscaler/metrics/collector.go
+++ b/pkg/autoscaler/metrics/collector.go
@@ -64,7 +64,7 @@ type Collector interface {
 	// Record allows stats to be captured that came from outside the Collector.
 	Record(key types.NamespacedName, now time.Time, stat Stat)
 	// Delete deletes a Metric and halts collection.
-	Delete(string, string) error
+	Delete(string, string)
 	// Watch registers a singleton function to call when a specific collector's status changes.
 	// The passed name is the namespace/name of the metric owned by the respective collector.
 	Watch(func(types.NamespacedName))
@@ -136,7 +136,7 @@ func (c *MetricCollector) CreateOrUpdate(metric *av1alpha1.Metric) error {
 }
 
 // Delete deletes a Metric and halts collection.
-func (c *MetricCollector) Delete(namespace, name string) error {
+func (c *MetricCollector) Delete(namespace, name string) {
 	c.collectionsMutex.Lock()
 	defer c.collectionsMutex.Unlock()
 
@@ -145,7 +145,6 @@ func (c *MetricCollector) Delete(namespace, name string) error {
 		collection.close()
 		delete(c.collections, key)
 	}
-	return nil
 }
 
 // Record records a stat that's been generated outside of the metric collector.
diff --git a/pkg/autoscaler/scaling/autoscaler.go b/pkg/autoscaler/scaling/autoscaler.go
index fe9d2324b..be2de8bb1 100644
--- a/pkg/autoscaler/scaling/autoscaler.go
+++ b/pkg/autoscaler/scaling/autoscaler.go
@@ -127,12 +127,11 @@ func newAutoscaler(
 }
 
 // Update reconfigures the UniScaler according to the DeciderSpec.
-func (a *autoscaler) Update(deciderSpec *DeciderSpec) error {
+func (a *autoscaler) Update(deciderSpec *DeciderSpec) {
 	a.specMux.Lock()
 	defer a.specMux.Unlock()
 
 	a.deciderSpec = deciderSpec
-	return nil
 }
 
 // Scale calculates the desired scale based on current statistics given the current time.
diff --git a/pkg/autoscaler/scaling/multiscaler.go b/pkg/autoscaler/scaling/multiscaler.go
index 58d63e466..e5aeca353 100644
--- a/pkg/autoscaler/scaling/multiscaler.go
+++ b/pkg/autoscaler/scaling/multiscaler.go
@@ -122,7 +122,7 @@ type UniScaler interface {
 	Scale(context.Context, time.Time) ScaleResult
 
 	// Update reconfigures the UniScaler according to the DeciderSpec.
-	Update(*DeciderSpec) error
+	Update(*DeciderSpec)
 }
 
 // UniScalerFactory creates a UniScaler for a given PA using the given dynamic configuration.
@@ -256,7 +256,7 @@ func (m *MultiScaler) Update(_ context.Context, decider *Decider) (*Decider, err
 }
 
 // Delete stops and removes a Decider.
-func (m *MultiScaler) Delete(_ context.Context, namespace, name string) error {
+func (m *MultiScaler) Delete(_ context.Context, namespace, name string) {
 	key := types.NamespacedName{Namespace: namespace, Name: name}
 	m.scalersMutex.Lock()
 	defer m.scalersMutex.Unlock()
@@ -264,7 +264,6 @@ func (m *MultiScaler) Delete(_ context.Context, namespace, name string) error {
 		close(scaler.stopCh)
 		delete(m.scalers, key)
 	}
-	return nil
 }
 
 // Watch registers a singleton function to call when DeciderStatus is updated.
diff --git a/pkg/queue/readiness/probe.go b/pkg/queue/readiness/probe.go
index 6ce77fde2..ba7463877 100644
--- a/pkg/queue/readiness/probe.go
+++ b/pkg/queue/readiness/probe.go
@@ -143,24 +143,36 @@ func (p *Probe) probeContainerImpl() bool {
 }
 
 func (p *Probe) doProbe(probe func(time.Duration) error) error {
-	if p.IsAggressive() {
-		return wait.PollImmediate(retryInterval, p.pollTimeout, func() (bool, error) {
-			if err := probe(aggressiveProbeTimeout); err != nil {
-				fmt.Fprintln(p.out, "aggressive probe error: ", err)
-				// Reset count of consecutive successes to zero.
-				p.count = 0
-				return false, nil
-			}
-
-			p.count++
-
-			// Return success if count of consecutive successes is equal to or greater
-			// than the probe's SuccessThreshold.
-			return p.count >= p.SuccessThreshold, nil
-		})
+	if !p.IsAggressive() {
+		return probe(time.Duration(p.TimeoutSeconds) * time.Second)
 	}
 
-	return probe(time.Duration(p.TimeoutSeconds) * time.Second)
+	var failCount int
+	var lastProbeErr error
+	pollErr := wait.PollImmediate(retryInterval, p.pollTimeout, func() (bool, error) {
+		if err := probe(aggressiveProbeTimeout); err != nil {
+			// Reset count of consecutive successes to zero.
+			p.count = 0
+			// Don't log this now since we probe every 50ms and some failures are
+			// expected if the user container takes longer than that to start up.
+			// We'll log the lastProbeErr if we don't eventually succeed.
+			lastProbeErr = err
+			failCount++
+			return false, nil
+		}
+
+		p.count++
+
+		// Return success if count of consecutive successes is equal to or greater
+		// than the probe's SuccessThreshold.
+		return p.count >= p.SuccessThreshold, nil
+	})
+
+	if lastProbeErr != nil {
+		fmt.Fprintf(p.out, "aggressive probe error (failed %d times): %v\n", failCount, lastProbeErr)
+	}
+
+	return pollErr
 }
 
 // tcpProbe function executes TCP probe once if its standard probe
diff --git a/pkg/reconciler/autoscaling/kpa/resources/decider.go b/pkg/reconciler/autoscaling/kpa/resources/decider.go
index a2cb42ab2..52e7979b4 100644
--- a/pkg/reconciler/autoscaling/kpa/resources/decider.go
+++ b/pkg/reconciler/autoscaling/kpa/resources/decider.go
@@ -35,7 +35,7 @@ type Deciders interface {
 	Create(ctx context.Context, decider *scaling.Decider) (*scaling.Decider, error)
 
 	// Delete removes the Decider resource for a given key, returning any errors.
-	Delete(ctx context.Context, namespace, name string) error
+	Delete(ctx context.Context, namespace, name string)
 
 	// Watch registers a function to call when Decider change.
 	Watch(watcher func(types.NamespacedName))
diff --git a/pkg/reconciler/autoscaling/kpa/scaler.go b/pkg/reconciler/autoscaling/kpa/scaler.go
index af8aa1118..00954607e 100644
--- a/pkg/reconciler/autoscaling/kpa/scaler.go
+++ b/pkg/reconciler/autoscaling/kpa/scaler.go
@@ -320,7 +320,8 @@ func (ks *scaler) scale(ctx context.Context, pa *pav1alpha1.PodAutoscaler, sks *
 
 	min, max := pa.ScaleBounds(asConfig)
 	initialScale := kparesources.GetInitialScale(asConfig, pa)
-	logger.Debugf("MinScale = %d, MaxScale = %d, InitialScale = %d, DesiredScale = %d Reachable = %v",
+	// Log reachability as quoted string, since default value is "".
+	logger.Debugf("MinScale = %d, MaxScale = %d, InitialScale = %d, DesiredScale = %d Reachable = %q",
 		min, max, initialScale, desiredScale, pa.Spec.Reachability)
 	// If initial scale has been attained, ignore the initialScale altogether.
 	if initialScale > 1 && !pa.Status.IsScaleTargetInitialized() {
diff --git a/pkg/reconciler/revision/revision.go b/pkg/reconciler/revision/revision.go
index 90a41b415..aea2a0e88 100644
--- a/pkg/reconciler/revision/revision.go
+++ b/pkg/reconciler/revision/revision.go
@@ -21,7 +21,9 @@ import (
 	"strings"
 	"time"
 
+	"github.com/davecgh/go-spew/spew"
 	"github.com/google/go-containerregistry/pkg/authn/k8schain"
+	"go.uber.org/zap/zapcore"
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
@@ -136,6 +138,15 @@ func (c *Reconciler) ReconcileKind(ctx context.Context, rev *v1.Revision) pkgrec
 		return nil
 	}
 
+	// Spew is an expensive operation so guard the computation on the debug level
+	// being enabled.
+	// Some things, like PA reachability, etc are computed based on various labels/annotations
+	// of revision. So it is useful to provide this information for debugging.
+	logger := logging.FromContext(ctx).Desugar()
+	if logger.Core().Enabled(zapcore.DebugLevel) {
+		logger.Debug("Revision meta: " + spew.Sdump(rev.ObjectMeta))
+	}
+
 	for _, phase := range []func(context.Context, *v1.Revision) error{
 		c.reconcileDeployment,
 		c.reconcileImageCache,
@@ -147,10 +158,12 @@ func (c *Reconciler) ReconcileKind(ctx context.Context, rev *v1.Revision) pkgrec
 	}
 	readyAfterReconcile := rev.Status.GetCondition(v1.RevisionConditionReady).IsTrue()
 	if !readyBeforeReconcile && readyAfterReconcile {
-		logging.FromContext(ctx).Info("Revision became ready")
+		logger.Info("Revision became ready")
 		controller.GetEventRecorder(ctx).Event(
 			rev, corev1.EventTypeNormal, "RevisionReady",
 			"Revision becomes ready upon all resources being ready")
+	} else if readyBeforeReconcile && !readyAfterReconcile {
+		logger.Info("Revision stopped being ready")
 	}
 
 	return nil
diff --git a/pkg/reconciler/route/reconcile_resources.go b/pkg/reconciler/route/reconcile_resources.go
index d8751ab3f..109e38baa 100644
--- a/pkg/reconciler/route/reconcile_resources.go
+++ b/pkg/reconciler/route/reconcile_resources.go
@@ -18,9 +18,11 @@ package route
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 
+	"github.com/davecgh/go-spew/spew"
 	"go.uber.org/zap"
 	"golang.org/x/sync/errgroup"
 	corev1 "k8s.io/api/core/v1"
@@ -30,20 +32,40 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/sets"
 
+	"knative.dev/networking/pkg/apis/networking"
 	netv1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1"
 	"knative.dev/pkg/apis/duck"
 	"knative.dev/pkg/controller"
+	"knative.dev/pkg/kmeta"
 	"knative.dev/pkg/logging"
 	v1 "knative.dev/serving/pkg/apis/serving/v1"
 	"knative.dev/serving/pkg/reconciler/route/config"
 	"knative.dev/serving/pkg/reconciler/route/resources"
+	"knative.dev/serving/pkg/reconciler/route/resources/names"
 	"knative.dev/serving/pkg/reconciler/route/traffic"
 )
 
-func (c *Reconciler) reconcileIngress(ctx context.Context, r *v1.Route, desired *netv1alpha1.Ingress) (*netv1alpha1.Ingress, error) {
+func (c *Reconciler) reconcileIngress(
+	ctx context.Context, r *v1.Route, tc *traffic.Config,
+	tls []netv1alpha1.IngressTLS,
+	ingressClass string,
+	acmeChallenges ...netv1alpha1.HTTP01Challenge,
+) (*netv1alpha1.Ingress, error) {
 	recorder := controller.GetEventRecorder(ctx)
-	ingress, err := c.ingressLister.Ingresses(desired.Namespace).Get(desired.Name)
+
+	desired, err := resources.MakeIngress(ctx, r, tc, tls, ingressClass, acmeChallenges...)
+	if err != nil {
+		return nil, err
+	}
+	// Get the current rollout state as described by the traffic.
+	curRO := tc.BuildRollout()
+
+	ingress, err := c.ingressLister.Ingresses(r.Namespace).Get(names.Ingress(r))
 	if apierrs.IsNotFound(err) {
+		// If there is no exisiting Ingress, then current rollout is _the_ rollout.
+		desired.Annotations = kmeta.UnionMaps(desired.Annotations, map[string]string{
+			networking.RolloutAnnotationKey: serializeRollout(ctx, curRO),
+		})
 		ingress, err = c.netclient.NetworkingV1alpha1().Ingresses(desired.Namespace).Create(ctx, desired, metav1.CreateOptions{})
 		if err != nil {
 			recorder.Eventf(r, corev1.EventTypeWarning, "CreationFailed", "Failed to create Ingress: %v", err)
@@ -54,25 +76,35 @@ func (c *Reconciler) reconcileIngress(ctx context.Context, r *v1.Route, desired
 		return ingress, nil
 	} else if err != nil {
 		return nil, err
-	} else if !equality.Semantic.DeepEqual(ingress.Spec, desired.Spec) ||
-		!equality.Semantic.DeepEqual(ingress.Annotations, desired.Annotations) ||
-		!equality.Semantic.DeepEqual(ingress.Labels, desired.Labels) {
-		// It is notable that one reason for differences here may be defaulting.
-		// When that is the case, the Update will end up being a nop because the
-		// webhook will bring them into alignment and no new reconciliation will occur.
-		// Also, compare annotation and label in case ingress.Class or parent route's labels
-		// is updated.
-
-		// Don't modify the informers copy
-		origin := ingress.DeepCopy()
-		origin.Spec = desired.Spec
-		origin.Annotations = desired.Annotations
-		origin.Labels = desired.Labels
-		updated, err := c.netclient.NetworkingV1alpha1().Ingresses(origin.Namespace).Update(ctx, origin, metav1.UpdateOptions{})
-		if err != nil {
-			return nil, fmt.Errorf("failed to update Ingress: %w", err)
+	} else {
+		// Ingress exists. We need to compute the rollout spec diff.
+		prevRO := deserializeRollout(ctx, ingress.Annotations[networking.RolloutAnnotationKey])
+		effectiveRO := curRO.Step(prevRO)
+		// Update the annotation.
+		desired.Annotations[networking.RolloutAnnotationKey] = serializeRollout(ctx, effectiveRO)
+		// TODO(vagababov): apply the Rollout to the ingress spec here.
+		if !equality.Semantic.DeepEqual(ingress.Spec, desired.Spec) ||
+			!equality.Semantic.DeepEqual(ingress.Annotations, desired.Annotations) ||
+			!equality.Semantic.DeepEqual(ingress.Labels, desired.Labels) {
+			// It is notable that one reason for differences here may be defaulting.
+			// When that is the case, the Update will end up being a nop because the
+			// webhook will bring them into alignment and no new reconciliation will occur.
+			// Also, compare annotation and label in case ingress.Class or parent route's labels
+			// is updated.
+
+			// Don't modify the informers copy
+			origin := ingress.DeepCopy()
+			origin.Spec = desired.Spec
+			origin.Annotations = desired.Annotations
+			origin.Labels = desired.Labels
+
+			updated, err := c.netclient.NetworkingV1alpha1().Ingresses(origin.Namespace).Update(
+				ctx, origin, metav1.UpdateOptions{})
+			if err != nil {
+				return nil, fmt.Errorf("failed to update Ingress: %w", err)
+			}
+			return updated, nil
 		}
-		return updated, nil
 	}
 
 	return ingress, err
@@ -227,3 +259,30 @@ func (c *Reconciler) reconcileTargetRevisions(ctx context.Context, t *traffic.Co
 	}
 	return eg.Wait()
 }
+
+func serializeRollout(ctx context.Context, r *traffic.Rollout) string {
+	sr, err := json.Marshal(r)
+	if err != nil {
+		// This must never happen in the normal course of things.
+		logging.FromContext(ctx).Warnw("Error serializing Rollout: "+spew.Sprint(r),
+			zap.Error(err))
+		return ""
+	}
+	return string(sr)
+}
+
+func deserializeRollout(ctx context.Context, ro string) *traffic.Rollout {
+	if ro == "" {
+		return nil
+	}
+	r := &traffic.Rollout{}
+	// Failure can happen if users manually tweaked the
+	// annotation or there's etcd corruption. Just log, rollouts
+	// are not mission critical.
+	if err := json.Unmarshal([]byte(ro), r); err != nil {
+		logging.FromContext(ctx).Warnw("Error deserializing Rollout: "+ro,
+			zap.Error(err))
+		return nil
+	}
+	return r
+}
diff --git a/pkg/reconciler/route/resources/ingress.go b/pkg/reconciler/route/resources/ingress.go
index b73590396..129d66e9e 100644
--- a/pkg/reconciler/route/resources/ingress.go
+++ b/pkg/reconciler/route/resources/ingress.go
@@ -18,11 +18,8 @@ package resources
 
 import (
 	"context"
-	"encoding/json"
 	"sort"
 
-	"github.com/davecgh/go-spew/spew"
-	"go.uber.org/zap"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
@@ -33,7 +30,6 @@ import (
 	netv1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1"
 	ingress "knative.dev/networking/pkg/ingress"
 	"knative.dev/pkg/kmeta"
-	"knative.dev/pkg/logging"
 	"knative.dev/serving/pkg/activator"
 	apicfg "knative.dev/serving/pkg/apis/config"
 	"knative.dev/serving/pkg/apis/serving"
@@ -78,7 +74,6 @@ func MakeIngress(
 			}),
 			Annotations: kmeta.FilterMap(kmeta.UnionMaps(map[string]string{
 				networking.IngressClassAnnotationKey: ingressClass,
-				networking.RolloutAnnotationKey:      serializeRollout(ctx, tc.BuildRollout()),
 			}, r.GetAnnotations()), func(key string) bool {
 				return key == corev1.LastAppliedConfigAnnotation
 			}),
@@ -88,17 +83,6 @@ func MakeIngress(
 	}, nil
 }
 
-func serializeRollout(ctx context.Context, r *traffic.Rollout) string {
-	sr, err := json.Marshal(r)
-	if err != nil {
-		// This must not never happen in the normal course of things.
-		logging.FromContext(ctx).Warnw("Error serializing Rollout: "+spew.Sprint(r),
-			zap.Error(err))
-		return ""
-	}
-	return string(sr)
-}
-
 // makeIngressSpec builds a new IngressSpec from inputs.
 func makeIngressSpec(
 	ctx context.Context,
@@ -260,7 +244,7 @@ func makeBaseIngressPath(ns string, targets traffic.RevisionTargets) *netv1alpha
 	// Optimistically allocate |targets| elements.
 	splits := make([]netv1alpha1.IngressBackendSplit, 0, len(targets))
 	for _, t := range targets {
-		if t.Percent == nil || *t.Percent == 0 {
+		if *t.Percent == 0 {
 			continue
 		}
 
diff --git a/pkg/reconciler/route/route.go b/pkg/reconciler/route/route.go
index a6ddb89b1..c149d4833 100644
--- a/pkg/reconciler/route/route.go
+++ b/pkg/reconciler/route/route.go
@@ -154,8 +154,7 @@ func (c *Reconciler) ReconcileKind(ctx context.Context, r *v1.Route) pkgreconcil
 	}
 
 	// Reconcile ingress and its children resources.
-	ingress, err := c.reconcileIngressResources(ctx, r, traffic, tls, ingressClassForRoute(ctx, r), acmeChallenges...)
-
+	ingress, err := c.reconcileIngress(ctx, r, traffic, tls, ingressClassForRoute(ctx, r), acmeChallenges...)
 	if err != nil {
 		return err
 	}
@@ -175,22 +174,6 @@ func (c *Reconciler) ReconcileKind(ctx context.Context, r *v1.Route) pkgreconcil
 	return nil
 }
 
-func (c *Reconciler) reconcileIngressResources(ctx context.Context, r *v1.Route, tc *traffic.Config, tls []netv1alpha1.IngressTLS,
-	ingressClass string, acmeChallenges ...netv1alpha1.HTTP01Challenge) (*netv1alpha1.Ingress, error) {
-
-	desired, err := resources.MakeIngress(ctx, r, tc, tls, ingressClass, acmeChallenges...)
-	if err != nil {
-		return nil, err
-	}
-
-	ingress, err := c.reconcileIngress(ctx, r, desired)
-	if err != nil {
-		return nil, err
-	}
-
-	return ingress, nil
-}
-
 func (c *Reconciler) tls(ctx context.Context, host string, r *v1.Route, traffic *traffic.Config) ([]netv1alpha1.IngressTLS, []netv1alpha1.HTTP01Challenge, error) {
 	tls := []netv1alpha1.IngressTLS{}
 	if !autoTLSEnabled(ctx, r) {
diff --git a/pkg/reconciler/route/traffic/rollout.go b/pkg/reconciler/route/traffic/rollout.go
index 61ffb5256..d496ae2a3 100644
--- a/pkg/reconciler/route/traffic/rollout.go
+++ b/pkg/reconciler/route/traffic/rollout.go
@@ -143,6 +143,31 @@ func (cur *Rollout) Step(prev *Rollout) *Rollout {
 	return ro
 }
 
+// adjustPercentage updates the rollout with the new percentage values.
+// If new percentage is larger than the previous, the last revision gets
+// the difference, if it is decreasing then we start removing traffic from
+// the older revisions.
+func adjustPercentage(goal int, cr *ConfigurationRollout) {
+	switch diff := goal - cr.Percent; {
+	case diff > 0:
+		cr.Revisions[len(cr.Revisions)-1].Percent += diff
+	case diff < 0:
+		diff = -diff // To make logic more natural.
+		i := 0
+		for diff > 0 && i < len(cr.Revisions) {
+			if cr.Revisions[i].Percent > diff {
+				cr.Revisions[i].Percent -= diff
+				break
+			}
+			diff -= cr.Revisions[i].Percent
+			i++
+		}
+		cr.Revisions = cr.Revisions[i:]
+	default: // diff = 0
+		// noop
+	}
+}
+
 // stepConfig takes previous and goal configuration shapes and returns a new
 // config rollout, after computing the percetage allocations.
 func stepConfig(goal, prev *ConfigurationRollout) *ConfigurationRollout {
@@ -151,16 +176,21 @@ func stepConfig(goal, prev *ConfigurationRollout) *ConfigurationRollout {
 		ConfigurationName: goal.ConfigurationName,
 		Tag:               goal.Tag,
 		Percent:           goal.Percent,
+		Revisions:         goal.Revisions,
+	}
+
+	if len(prev.Revisions) > 0 {
+		adjustPercentage(goal.Percent, prev)
 	}
 	// goal will always have just one revision in the list – the current desired revision.
 	// If it matches the last revision of the previous rollout state (or there were no revisions)
-	// then no new rollout hast begun for this configuration.
+	// then no new rollout has begun for this configuration.
 	if len(prev.Revisions) == 0 || goal.Revisions[0].RevisionName == prev.Revisions[pc-1].RevisionName {
 		// TODO(vagababov): here would go the logic to compute new percentages for the rollout,
 		// i.e step function, so return value will change, depending on that.
-		// TODO(vagababov): percentage might change, so this should trigger recompute of existing
-		// revision rollouts.
-		ret.Revisions = goal.Revisions
+		if len(prev.Revisions) > 0 {
+			ret.Revisions = prev.Revisions
+		}
 		return ret
 	}
 
diff --git a/pkg/reconciler/route/traffic/traffic.go b/pkg/reconciler/route/traffic/traffic.go
index 05e6036b9..c9c233cce 100644
--- a/pkg/reconciler/route/traffic/traffic.go
+++ b/pkg/reconciler/route/traffic/traffic.go
@@ -194,12 +194,12 @@ func buildRolloutForTag(r *Rollout, tag string, rts RevisionTargets) {
 		r.Configurations = append(r.Configurations, ConfigurationRollout{
 			ConfigurationName: rt.ConfigurationName,
 			Tag:               tag,
-			Percent:           int(zeroIfNil(rt.Percent)),
+			Percent:           int(*rt.Percent),
 			Revisions: []RevisionRollout{{
 				RevisionName: rt.RevisionName,
 				// Note: this will match config value in steady state, but
 				// during rollout it will be overridden by the rollout logic.
-				Percent: int(zeroIfNil(rt.Percent)),
+				Percent: int(*rt.Percent),
 			}},
 		})
 	}
@@ -335,21 +335,13 @@ func (cb *configBuilder) addRevisionTarget(tt *v1.TrafficTarget) error {
 	return nil
 }
 
-// zeroIfNil returns `0` if `ptr==nil`, or `*ptr` otherwise.
-func zeroIfNil(ptr *int64) int64 {
-	if ptr == nil {
-		return 0
-	}
-	return *ptr
-}
-
 // This find the exact revision+tag pair and if so, just adds the percentages.
 // This expects single digit lists, so just does an O(N) search.
 func mergeIfNecessary(rts RevisionTargets, rt RevisionTarget) RevisionTargets {
 	for i := range rts {
 		if rts[i].Tag == rt.Tag && rts[i].RevisionName == rt.RevisionName &&
 			*rt.LatestRevision == *rts[i].LatestRevision {
-			rts[i].Percent = ptr.Int64(zeroIfNil(rts[i].Percent) + zeroIfNil(rt.Percent))
+			rts[i].Percent = ptr.Int64(*rts[i].Percent + *rt.Percent)
 			return rts
 		}
 	}


Test patch:
diff --git a/.github/workflows/kind-e2e.yaml b/.github/workflows/kind-e2e.yaml
index ab73f23ba..242a60249 100644
--- a/.github/workflows/kind-e2e.yaml
+++ b/.github/workflows/kind-e2e.yaml
@@ -53,12 +53,6 @@ jobs:
         - test-suite: ./test/conformance/api/v1alpha1
           test-flags: "--enable-alpha"
 
-        exclude:
-          # TODO(#9874): Un-exclude this when Kourier implements the
-          # RewriteHost feature DomainMappings relies on.
-        - test-suite: ./test/conformance/api/v1alpha1
-          k8s-version: v1.18.8
-
     env:
       GOPATH: ${{ github.workspace }}
       GO111MODULE: off
diff --git a/pkg/activator/net/helpers_test.go b/pkg/activator/net/helpers_test.go
index 59cdf5f30..7ff592459 100644
--- a/pkg/activator/net/helpers_test.go
+++ b/pkg/activator/net/helpers_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package net
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -168,3 +169,54 @@ func TestGetServicePort(t *testing.T) {
 		})
 	}
 }
+
+func BenchmarkHealthyAddresses(b *testing.B) {
+	for _, n := range []int{1, 10, 100, 1000, 10000} {
+		b.Run(fmt.Sprint("addresses-", n), func(b *testing.B) {
+			ep := eps(10, n)
+			for i := 0; i < b.N; i++ {
+				healthyAddresses(ep, networking.ServicePortNameHTTP1)
+			}
+		})
+	}
+}
+
+func BenchmarkEndpointsToDests(b *testing.B) {
+	for _, n := range []int{1, 10, 100, 1000, 10000} {
+		b.Run(fmt.Sprint("addresses-", n), func(b *testing.B) {
+			ep := eps(10, n)
+			for i := 0; i < b.N; i++ {
+				endpointsToDests(ep, networking.ServicePortNameHTTP1)
+			}
+		})
+	}
+}
+
+func eps(activators, apps int) *corev1.Endpoints {
+	return &corev1.Endpoints{
+		Subsets: []corev1.EndpointSubset{{
+			Addresses: addresses("activator", activators),
+			Ports: []corev1.EndpointPort{{
+				Name: networking.ServicePortNameHTTP1,
+				Port: 1234,
+			}},
+		}, {
+			Addresses:         addresses("app", apps),
+			NotReadyAddresses: addresses("app-non-ready", apps),
+			Ports: []corev1.EndpointPort{{
+				Name: networking.ServicePortNameHTTP1,
+				Port: 1234,
+			}},
+		}},
+	}
+}
+
+func addresses(prefix string, n int) []corev1.EndpointAddress {
+	addrs := make([]corev1.EndpointAddress, 0, n)
+	for i := 0; i < n; i++ {
+		addrs = append(addrs, corev1.EndpointAddress{
+			IP: fmt.Sprintf("%s-%d", prefix, i),
+		})
+	}
+	return addrs
+}
diff --git a/pkg/apis/serving/v1/revision_lifecycle_test.go b/pkg/apis/serving/v1/revision_lifecycle_test.go
index 1bb700f40..d6f90e915 100644
--- a/pkg/apis/serving/v1/revision_lifecycle_test.go
+++ b/pkg/apis/serving/v1/revision_lifecycle_test.go
@@ -317,7 +317,7 @@ func TestTypicalFlowWithContainerMissing(t *testing.T) {
 	apistest.CheckConditionOngoing(r, RevisionConditionContainerHealthy, t)
 	apistest.CheckConditionOngoing(r, RevisionConditionReady, t)
 
-	const want = "something about the container being not found"
+	const want = "something about the container being not found %s"
 	r.MarkContainerHealthyFalse(ReasonContainerMissing, want)
 	apistest.CheckConditionOngoing(r, RevisionConditionResourcesAvailable, t)
 	apistest.CheckConditionFailed(r, RevisionConditionContainerHealthy, t)
diff --git a/pkg/autoscaler/metrics/collector_test.go b/pkg/autoscaler/metrics/collector_test.go
index ea7868baf..f054c39e9 100644
--- a/pkg/autoscaler/metrics/collector_test.go
+++ b/pkg/autoscaler/metrics/collector_test.go
@@ -111,9 +111,7 @@ func TestMetricCollectorCRUD(t *testing.T) {
 			t.Errorf("Updated scraper URL = %s, want: %s, diff: %s", got, want, cmp.Diff(got, want))
 		}
 
-		if err := coll.Delete(defaultNamespace, defaultName); err != nil {
-			t.Errorf("Delete() = %v, want no error", err)
-		}
+		coll.Delete(defaultNamespace, defaultName)
 	})
 }
 
diff --git a/pkg/autoscaler/scaling/multiscaler_test.go b/pkg/autoscaler/scaling/multiscaler_test.go
index 429be6b2b..03e01a65d 100644
--- a/pkg/autoscaler/scaling/multiscaler_test.go
+++ b/pkg/autoscaler/scaling/multiscaler_test.go
@@ -171,9 +171,7 @@ func TestMultiScalerScaling(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if err := ms.Delete(ctx, decider.Namespace, decider.Name); err != nil {
-		t.Error("Delete() =", err)
-	}
+	ms.Delete(ctx, decider.Namespace, decider.Name)
 
 	// Verify that we stop seeing "ticks"
 	mtp.Channel <- time.Now()
@@ -206,9 +204,7 @@ func TestMultiscalerCreateTBC42(t *testing.T) {
 	if got, want := d.Status.ExcessBurstCapacity, int32(50-42); got != want {
 		t.Errorf("Decider.Status.ExcessBurstCapacity = %d, want: %d", got, want)
 	}
-	if err := ms.Delete(ctx, decider.Namespace, decider.Name); err != nil {
-		t.Error("Delete() =", err)
-	}
+	ms.Delete(ctx, decider.Namespace, decider.Name)
 }
 
 func TestMultiscalerCreateTBCMinus1(t *testing.T) {
@@ -233,9 +229,7 @@ func TestMultiscalerCreateTBCMinus1(t *testing.T) {
 	if got, want := d.Status.ExcessBurstCapacity, int32(-1); got != want {
 		t.Errorf("Decider.Status.ExcessBurstCapacity = %d, want: %d", got, want)
 	}
-	if err := ms.Delete(ctx, decider.Namespace, decider.Name); err != nil {
-		t.Error("Delete() =", err)
-	}
+	ms.Delete(ctx, decider.Namespace, decider.Name)
 }
 
 func TestMultiScalerOnlyCapacityChange(t *testing.T) {
@@ -273,9 +267,7 @@ func TestMultiScalerOnlyCapacityChange(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if err := ms.Delete(ctx, decider.Namespace, decider.Name); err != nil {
-		t.Error("Delete() =", err)
-	}
+	ms.Delete(ctx, decider.Namespace, decider.Name)
 
 	// Verify that we stop seeing "ticks".
 	mtp.Channel <- time.Now()
@@ -316,10 +308,7 @@ func TestMultiScalerScaleToZero(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	err = ms.Delete(ctx, decider.Namespace, decider.Name)
-	if err != nil {
-		t.Error("Delete() =", err)
-	}
+	ms.Delete(ctx, decider.Namespace, decider.Name)
 
 	// Verify that we stop seeing "ticks"
 	mtp.Channel <- time.Now()
@@ -365,9 +354,7 @@ func TestMultiScalerScaleFromZero(t *testing.T) {
 	if err := verifyTick(errCh); err != nil {
 		t.Fatal(err)
 	}
-	if err := ms.Delete(ctx, decider.Namespace, decider.Name); err != nil {
-		t.Error("Delete() =", err)
-	}
+	ms.Delete(ctx, decider.Namespace, decider.Name)
 }
 
 func TestMultiScalerUpdate(t *testing.T) {
@@ -404,9 +391,7 @@ func TestMultiScalerUpdate(t *testing.T) {
 	if got, want := m.Spec.TargetValue, 10.0; got != want {
 		t.Errorf("Got target concurrency %v. Wanted %v", got, want)
 	}
-	if err := ms.Delete(ctx, decider.Namespace, decider.Name); err != nil {
-		t.Error("Delete() =", err)
-	}
+	ms.Delete(ctx, decider.Namespace, decider.Name)
 }
 
 func createMultiScaler(ctx context.Context, l *zap.SugaredLogger) (*MultiScaler, *fakeUniScaler) {
@@ -445,9 +430,7 @@ func (u *fakeUniScaler) setScaleResult(replicas, surplus, na int32, scaled bool)
 	u.numActivators = na
 }
 
-func (u *fakeUniScaler) Update(*DeciderSpec) error {
-	return nil
-}
+func (u *fakeUniScaler) Update(*DeciderSpec) {}
 
 func newDecider() *Decider {
 	return &Decider{
diff --git a/pkg/queue/readiness/probe_test.go b/pkg/queue/readiness/probe_test.go
index b747f7574..6a94eedbe 100644
--- a/pkg/queue/readiness/probe_test.go
+++ b/pkg/queue/readiness/probe_test.go
@@ -24,6 +24,7 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"net/url"
+	"strings"
 	"testing"
 	"time"
 
@@ -79,6 +80,29 @@ func TestTCPFailure(t *testing.T) {
 	}
 }
 
+func TestAggressiveFailureOnlyLogsOnce(t *testing.T) {
+	pb := NewProbe(&corev1.Probe{
+		PeriodSeconds:    0, // Aggressive probe.
+		TimeoutSeconds:   1,
+		SuccessThreshold: 1,
+		FailureThreshold: 1,
+		Handler: corev1.Handler{
+			TCPSocket: &corev1.TCPSocketAction{
+				Host: "127.0.0.1",
+				Port: intstr.FromInt(12345),
+			},
+		},
+	})
+
+	var buf bytes.Buffer
+	pb.out = &buf
+
+	pb.ProbeContainer()
+	if got := strings.Count(buf.String(), "aggressive probe error"); got != 1 {
+		t.Error("Expected exactly one instance of 'aggressive probe error' in the log, got", got)
+	}
+}
+
 func TestEmptyHandler(t *testing.T) {
 	pb := NewProbe(&corev1.Probe{
 		PeriodSeconds:    1,
@@ -637,7 +661,7 @@ func TestKnTCPProbeSuccessThresholdIncludesFailure(t *testing.T) {
 	}
 
 	if probeErr := <-errChan; !probeErr {
-		t.Error("Wanted ProbeContainer() successed but got error")
+		t.Error("Wanted ProbeContainer() to succeed, but got error")
 	}
 	if got := pb.count; got < successThreshold {
 		t.Errorf("Count = %d, want: %d", got, successThreshold)
diff --git a/pkg/reconciler/autoscaling/kpa/kpa_test.go b/pkg/reconciler/autoscaling/kpa/kpa_test.go
index 540148222..001891b68 100644
--- a/pkg/reconciler/autoscaling/kpa/kpa_test.go
+++ b/pkg/reconciler/autoscaling/kpa/kpa_test.go
@@ -1671,7 +1671,7 @@ func (km *testDeciders) Create(ctx context.Context, decider *scaling.Decider) (*
 	return decider, nil
 }
 
-func (km *testDeciders) Delete(ctx context.Context, namespace, name string) error {
+func (km *testDeciders) Delete(ctx context.Context, namespace, name string) {
 	km.mutex.Lock()
 	defer km.mutex.Unlock()
 
@@ -1681,7 +1681,6 @@ func (km *testDeciders) Delete(ctx context.Context, namespace, name string) erro
 		km.deleteBeforeCreate.Store(true)
 	}
 	km.deleteCall <- struct{}{}
-	return nil
 }
 
 func (km *testDeciders) Update(ctx context.Context, decider *scaling.Decider) (*scaling.Decider, error) {
@@ -1699,7 +1698,6 @@ func (km *testDeciders) Watch(fn func(types.NamespacedName)) {}
 type failingDeciders struct {
 	getErr    error
 	createErr error
-	deleteErr error
 }
 
 func (km *failingDeciders) Get(ctx context.Context, namespace, name string) (*scaling.Decider, error) {
@@ -1710,9 +1708,7 @@ func (km *failingDeciders) Create(ctx context.Context, decider *scaling.Decider)
 	return nil, km.createErr
 }
 
-func (km *failingDeciders) Delete(ctx context.Context, namespace, name string) error {
-	return km.deleteErr
-}
+func (km *failingDeciders) Delete(ctx context.Context, namespace, name string) {}
 
 func (km *failingDeciders) Watch(fn func(types.NamespacedName)) {
 }
diff --git a/pkg/reconciler/metric/metric_test.go b/pkg/reconciler/metric/metric_test.go
index d17365552..2b5cf6c49 100644
--- a/pkg/reconciler/metric/metric_test.go
+++ b/pkg/reconciler/metric/metric_test.go
@@ -267,7 +267,6 @@ type testCollector struct {
 	createOrUpdateError error
 
 	deleteCalls atomic.Int32
-	deleteError error
 }
 
 func (c *testCollector) CreateOrUpdate(metric *av1alpha1.Metric) error {
@@ -275,9 +274,8 @@ func (c *testCollector) CreateOrUpdate(metric *av1alpha1.Metric) error {
 	return c.createOrUpdateError
 }
 
-func (c *testCollector) Delete(namespace, name string) error {
+func (c *testCollector) Delete(namespace, name string) {
 	c.deleteCalls.Inc()
-	return c.deleteError
 }
 
 func (c *testCollector) Watch(func(types.NamespacedName)) {}
diff --git a/pkg/reconciler/revision/resources/pa_test.go b/pkg/reconciler/revision/resources/pa_test.go
index 974ee34b8..88ec41f03 100644
--- a/pkg/reconciler/revision/resources/pa_test.go
+++ b/pkg/reconciler/revision/resources/pa_test.go
@@ -315,7 +315,7 @@ func TestMakePA(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 			got := MakePA(test.rev)
 			if !cmp.Equal(got, test.want) {
-				t.Error("MakeK8sService (-want, +got) =", cmp.Diff(test.want, got))
+				t.Error("MakePA (-want, +got) =", cmp.Diff(test.want, got))
 			}
 		})
 	}
diff --git a/pkg/reconciler/route/reconcile_resources_test.go b/pkg/reconciler/route/reconcile_resources_test.go
index 24fecf20a..e0f3426bf 100644
--- a/pkg/reconciler/route/reconcile_resources_test.go
+++ b/pkg/reconciler/route/reconcile_resources_test.go
@@ -27,7 +27,7 @@ import (
 
 	"knative.dev/networking/pkg/apis/networking"
 	netv1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1"
-	fakeciinformer "knative.dev/networking/pkg/client/injection/informers/networking/v1alpha1/ingress/fake"
+	fakeingressinformer "knative.dev/networking/pkg/client/injection/informers/networking/v1alpha1/ingress/fake"
 	"knative.dev/pkg/ptr"
 	"knative.dev/serving/pkg/apis/serving"
 	v1 "knative.dev/serving/pkg/apis/serving/v1"
@@ -35,7 +35,6 @@ import (
 	fakerevisioninformer "knative.dev/serving/pkg/client/injection/informers/serving/v1/revision/fake"
 	"knative.dev/serving/pkg/gc"
 	"knative.dev/serving/pkg/reconciler/route/config"
-	"knative.dev/serving/pkg/reconciler/route/resources"
 	"knative.dev/serving/pkg/reconciler/route/traffic"
 
 	. "knative.dev/serving/pkg/testing/v1"
@@ -49,9 +48,8 @@ func TestReconcileIngressInsert(t *testing.T) {
 	defer cancel()
 
 	r := Route("test-ns", "test-route")
-	ci := newTestIngress(t, r)
-
-	if _, err := reconciler.reconcileIngress(ctx, r, ci); err != nil {
+	tc, tls := testIngressParams(t, r)
+	if _, err := reconciler.reconcileIngress(updateContext(ctx), r, tc, tls, "foo-ingress"); err != nil {
 		t.Error("Unexpected error:", err)
 	}
 }
@@ -65,15 +63,15 @@ func TestReconcileIngressUpdate(t *testing.T) {
 
 	r := Route("test-ns", "test-route")
 
-	ci := newTestIngress(t, r)
-	if _, err := reconciler.reconcileIngress(ctx, r, ci); err != nil {
+	tc, tls := testIngressParams(t, r)
+	if _, err := reconciler.reconcileIngress(updateContext(ctx), r, tc, tls, "foo-ingress"); err != nil {
 		t.Error("Unexpected error:", err)
 	}
 
-	updated := getRouteIngressFromClient(ctx, t, r)
-	fakeciinformer.Get(ctx).Informer().GetIndexer().Add(updated)
+	initial := getRouteIngressFromClient(ctx, t, r)
+	fakeingressinformer.Get(ctx).Informer().GetIndexer().Add(initial)
 
-	ci2 := newTestIngress(t, r, func(tc *traffic.Config) {
+	tc, tls = testIngressParams(t, r, func(tc *traffic.Config) {
 		tc.Targets[traffic.DefaultTarget][0].TrafficTarget.Percent = ptr.Int64(50)
 		tc.Targets[traffic.DefaultTarget] = append(tc.Targets[traffic.DefaultTarget], traffic.RevisionTarget{
 			TrafficTarget: v1.TrafficTarget{
@@ -82,15 +80,12 @@ func TestReconcileIngressUpdate(t *testing.T) {
 			},
 		})
 	})
-	if _, err := reconciler.reconcileIngress(ctx, r, ci2); err != nil {
+	if _, err := reconciler.reconcileIngress(updateContext(ctx), r, tc, tls, "foo-ingress"); err != nil {
 		t.Error("Unexpected error:", err)
 	}
 
-	updated = getRouteIngressFromClient(ctx, t, r)
-	if diff := cmp.Diff(ci2, updated); diff != "" {
-		t.Error("Unexpected diff (-want +got):", diff)
-	}
-	if diff := cmp.Diff(ci, updated); diff == "" {
+	updated := getRouteIngressFromClient(ctx, t, r)
+	if diff := cmp.Diff(initial, updated); diff == "" {
 		t.Error("Expected difference, but found none")
 	}
 }
@@ -128,7 +123,7 @@ func TestReconcileTargetValidRevision(t *testing.T) {
 		t.Fatal("Error getting last pinned:", err)
 	}
 
-	if err = reconciler.reconcileTargetRevisions(ctx, &tc, r); err != nil {
+	if err = reconciler.reconcileTargetRevisions(updateContext(ctx), &tc, r); err != nil {
 		t.Fatal("Error reconciling target revisions:", err)
 	}
 
@@ -197,7 +192,8 @@ func getLastPinnedTimestamp(t *testing.T, rev *v1.Revision) (string, error) {
 	return lastPinnedTime, nil
 }
 
-func newTestIngress(t *testing.T, r *v1.Route, trafficOpts ...func(tc *traffic.Config)) *netv1alpha1.Ingress {
+func testIngressParams(t *testing.T, r *v1.Route, trafficOpts ...func(tc *traffic.Config)) (*traffic.Config,
+	[]netv1alpha1.IngressTLS) {
 	tc := &traffic.Config{Targets: map[string]traffic.RevisionTargets{
 		traffic.DefaultTarget: {{
 			TrafficTarget: v1.TrafficTarget{
@@ -215,11 +211,7 @@ func newTestIngress(t *testing.T, r *v1.Route, trafficOpts ...func(tc *traffic.C
 		SecretName:      "test-secret",
 		SecretNamespace: "test-ns",
 	}}
-	ingress, err := resources.MakeIngress(getContext(), r, tc, tls, "foo-ingress")
-	if err != nil {
-		t.Error("Unexpected error:", err)
-	}
-	return ingress
+	return tc, tls
 }
 
 func TestReconcileIngressClassAnnotation(t *testing.T) {
@@ -232,19 +224,15 @@ func TestReconcileIngressClassAnnotation(t *testing.T) {
 	const expClass = "foo.ingress.networking.knative.dev"
 
 	r := Route("test-ns", "test-route")
-	ci := newTestIngress(t, r)
-	if _, err := reconciler.reconcileIngress(ctx, r, ci); err != nil {
+	tc, tls := testIngressParams(t, r)
+	if _, err := reconciler.reconcileIngress(updateContext(ctx), r, tc, tls, "foo-ingress"); err != nil {
 		t.Error("Unexpected error:", err)
 	}
 
 	updated := getRouteIngressFromClient(ctx, t, r)
-	fakeciinformer.Get(ctx).Informer().GetIndexer().Add(updated)
-
-	ci2 := newTestIngress(t, r)
-	// Add ingress.class annotation.
-	ci2.Annotations[networking.IngressClassAnnotationKey] = expClass
+	fakeingressinformer.Get(ctx).Informer().GetIndexer().Add(updated)
 
-	if _, err := reconciler.reconcileIngress(ctx, r, ci2); err != nil {
+	if _, err := reconciler.reconcileIngress(updateContext(ctx), r, tc, tls, expClass); err != nil {
 		t.Error("Unexpected error:", err)
 	}
 
@@ -255,7 +243,11 @@ func TestReconcileIngressClassAnnotation(t *testing.T) {
 	}
 }
 
-func getContext() context.Context {
+func updateContext(ctx context.Context) context.Context {
 	cfg := reconcilerTestConfig(false)
-	return config.ToContext(context.Background(), cfg)
+	return config.ToContext(ctx, cfg)
+}
+
+func getContext() context.Context {
+	return updateContext(context.Background())
 }
diff --git a/pkg/reconciler/route/resources/ingress_test.go b/pkg/reconciler/route/resources/ingress_test.go
index 4c894bce3..c3e5753d5 100644
--- a/pkg/reconciler/route/resources/ingress_test.go
+++ b/pkg/reconciler/route/resources/ingress_test.go
@@ -51,8 +51,6 @@ const (
 	testRouteName       = "test-route"
 	testAnnotationValue = "test-annotation-value"
 	testIngressClass    = "test-ingress"
-
-	emptyRollout = "{}"
 )
 
 func TestMakeIngressCorrectMetadata(t *testing.T) {
@@ -81,7 +79,6 @@ func TestMakeIngressCorrectMetadata(t *testing.T) {
 			// Make sure to get passdownIngressClass instead of ingressClass
 			networking.IngressClassAnnotationKey: passdownIngressClass,
 			"test-annotation":                    "bar",
-			networking.RolloutAnnotationKey:      emptyRollout,
 		},
 		OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(r)},
 	}
@@ -133,7 +130,6 @@ func TestMakeIngressWithRollout(t *testing.T) {
 			// Make sure to get passdownIngressClass instead of ingressClass
 			networking.IngressClassAnnotationKey: passdownIngressClass,
 			"test-annotation":                    "bar",
-			networking.RolloutAnnotationKey:      serializeRollout(context.Background(), cfg.BuildRollout()),
 		},
 		OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(r)},
 	}
@@ -625,7 +621,7 @@ func TestMakeIngressRuleZeroPercentTarget(t *testing.T) {
 	}
 }
 
-// One active target and a target of nil (implied zero) percent.
+// One active target and a target of 0 percent (nil is now impossible with defaulting to 0).
 func TestMakeIngressRuleNilPercentTarget(t *testing.T) {
 	targets := []traffic.RevisionTarget{{
 		TrafficTarget: v1.TrafficTarget{
@@ -638,6 +634,7 @@ func TestMakeIngressRuleNilPercentTarget(t *testing.T) {
 		TrafficTarget: v1.TrafficTarget{
 			ConfigurationName: "new-config",
 			RevisionName:      "new-revision",
+			Percent:           ptr.Int64(0),
 		},
 	}}
 	domains := []string{"test.org"}
@@ -865,7 +862,7 @@ func TestMakeIngressRuleZeroPercentTargetInactive(t *testing.T) {
 	}
 }
 
-func TestMakeIngressRuleNilPercentTargetInactive(t *testing.T) {
+func TestMakeIngressRuleImplicit0PercentTargetInactive(t *testing.T) {
 	targets := []traffic.RevisionTarget{{
 		TrafficTarget: v1.TrafficTarget{
 			ConfigurationName: "config",
@@ -877,7 +874,7 @@ func TestMakeIngressRuleNilPercentTargetInactive(t *testing.T) {
 		TrafficTarget: v1.TrafficTarget{
 			ConfigurationName: "new-config",
 			RevisionName:      "new-revision",
-			Percent:           nil,
+			Percent:           ptr.Int64(0),
 		},
 	}}
 	domains := []string{"test.org"}
@@ -922,7 +919,6 @@ func TestMakeIngressWithTLS(t *testing.T) {
 			Namespace: ns,
 			Annotations: map[string]string{
 				networking.IngressClassAnnotationKey: ingressClass,
-				networking.RolloutAnnotationKey:      emptyRollout,
 			},
 			Labels: map[string]string{
 				serving.RouteLabelKey:          "test-route",
diff --git a/pkg/reconciler/route/table_test.go b/pkg/reconciler/route/table_test.go
index 95cbbe892..054439d1e 100644
--- a/pkg/reconciler/route/table_test.go
+++ b/pkg/reconciler/route/table_test.go
@@ -713,7 +713,11 @@ func TestReconcile(t *testing.T) {
 						}},
 					},
 				},
-			),
+				simpleRollout("config", []traffic.RevisionRollout{{
+					RevisionName: "config-00001", Percent: 99,
+				}, {
+					RevisionName: "config-00002", Percent: 1,
+				}})),
 		}},
 		WantStatusUpdates: []clientgotesting.UpdateActionImpl{{
 			Object: Route("default", "new-latest-ready", WithConfigTarget("config"),
@@ -913,7 +917,11 @@ func TestReconcile(t *testing.T) {
 						}},
 					},
 				},
-			),
+				simpleRollout("config", []traffic.RevisionRollout{{
+					RevisionName: "config-00001", Percent: 99,
+				}, {
+					RevisionName: "config-00002", Percent: 1,
+				}})),
 		}},
 		WantStatusUpdates: []clientgotesting.UpdateActionImpl{{
 			Object: Route("default", "update-ci-failure", WithConfigTarget("config"),
@@ -2555,6 +2563,12 @@ func ingressWithClass(r *v1.Route, tc *traffic.Config, class string, io ...Ingre
 func baseIngressWithClass(r *v1.Route, tc *traffic.Config, class string, io ...IngressOption) *netv1alpha1.Ingress {
 	ingress, _ := resources.MakeIngress(getContext(), r, tc, nil, class)
 
+	// By default attach current rollout.
+	ro := tc.BuildRollout()
+	ingress.Annotations = kmeta.UnionMaps(ingress.Annotations, map[string]string{
+		networking.RolloutAnnotationKey: serializeRollout(context.Background(), ro),
+	})
+
 	for _, opt := range io {
 		opt(ingress)
 	}
@@ -2569,6 +2583,12 @@ func ingressWithTLS(r *v1.Route, tc *traffic.Config, tls []netv1alpha1.IngressTL
 func baseIngressWithTLS(r *v1.Route, tc *traffic.Config, tls []netv1alpha1.IngressTLS, challenges []netv1alpha1.HTTP01Challenge, io ...IngressOption) *netv1alpha1.Ingress {
 	ingress, _ := resources.MakeIngress(getContext(), r, tc, tls, TestIngressClass, challenges...)
 
+	// By default attach current rollout.
+	ro := tc.BuildRollout()
+	ingress.Annotations = kmeta.UnionMaps(ingress.Annotations, map[string]string{
+		networking.RolloutAnnotationKey: serializeRollout(context.Background(), ro),
+	})
+
 	for _, opt := range io {
 		opt(ingress)
 	}
@@ -2724,3 +2744,17 @@ func setResponsiveGCFeature(ctx context.Context, flag cfgmap.Flag) context.Conte
 	c.Features.ResponsiveRevisionGC = flag
 	return cfgmap.ToContext(ctx, c)
 }
+
+func simpleRollout(cfg string, revs []traffic.RevisionRollout) IngressOption {
+	return func(i *netv1alpha1.Ingress) {
+		r := &traffic.Rollout{
+			Configurations: []traffic.ConfigurationRollout{{
+				ConfigurationName: cfg,
+				Percent:           100,
+				Revisions:         revs,
+			}},
+		}
+		ro := serializeRollout(context.Background(), r)
+		i.Annotations[networking.RolloutAnnotationKey] = ro
+	}
+}
diff --git a/pkg/reconciler/route/traffic/rollout_test.go b/pkg/reconciler/route/traffic/rollout_test.go
index 6e4a0b5f5..dd73569e8 100644
--- a/pkg/reconciler/route/traffic/rollout_test.go
+++ b/pkg/reconciler/route/traffic/rollout_test.go
@@ -193,6 +193,88 @@ func TestStep(t *testing.T) {
 				}},
 			}},
 		},
+	}, {
+		name: "roll with percentage change down",
+		cur: &Rollout{
+			Configurations: []ConfigurationRollout{{
+				ConfigurationName: "mick",
+				Percent:           33,
+				Revisions: []RevisionRollout{{
+					RevisionName: "let-it-bleed",
+					Percent:      33,
+				}},
+			}},
+		},
+		prev: &Rollout{
+			Configurations: []ConfigurationRollout{{
+				ConfigurationName: "mick",
+				Percent:           42,
+				Revisions: []RevisionRollout{{
+					RevisionName: "goat-head-soup",
+					Percent:      11,
+				}, {
+					RevisionName: "aftermath",
+					Percent:      31,
+				}},
+			}},
+		},
+		want: &Rollout{
+			Configurations: []ConfigurationRollout{{
+				ConfigurationName: "mick",
+				Percent:           33,
+				Revisions: []RevisionRollout{{
+					RevisionName: "goat-head-soup",
+					Percent:      2,
+				}, {
+					RevisionName: "aftermath",
+					Percent:      30,
+				}, {
+					RevisionName: "let-it-bleed",
+					Percent:      1,
+				}},
+			}},
+		},
+	}, {
+		name: "roll with percentage change up",
+		cur: &Rollout{
+			Configurations: []ConfigurationRollout{{
+				ConfigurationName: "mick",
+				Percent:           75,
+				Revisions: []RevisionRollout{{
+					RevisionName: "let-it-bleed",
+					Percent:      75,
+				}},
+			}},
+		},
+		prev: &Rollout{
+			Configurations: []ConfigurationRollout{{
+				ConfigurationName: "mick",
+				Percent:           25,
+				Revisions: []RevisionRollout{{
+					RevisionName: "goat-head-soup",
+					Percent:      11,
+				}, {
+					RevisionName: "aftermath",
+					Percent:      14,
+				}},
+			}},
+		},
+		want: &Rollout{
+			Configurations: []ConfigurationRollout{{
+				ConfigurationName: "mick",
+				Percent:           75,
+				Revisions: []RevisionRollout{{
+					RevisionName: "goat-head-soup",
+					Percent:      11,
+				}, {
+					RevisionName: "aftermath",
+					Percent:      63,
+				}, {
+					RevisionName: "let-it-bleed",
+					Percent:      1,
+				}},
+			}},
+		},
 	}, {
 		name: "roll, where sum < 100% (one route targets a revision, e.g.)",
 		cur: &Rollout{
@@ -528,3 +610,130 @@ func TestStep(t *testing.T) {
 		})
 	}
 }
+
+func TestAdjustPercentage(t *testing.T) {
+	tests := []struct {
+		name string
+		goal int
+		prev *ConfigurationRollout
+		want []RevisionRollout
+	}{{
+		name: "noop, 100%",
+		goal: 100,
+		prev: &ConfigurationRollout{
+			Percent: 100,
+			Revisions: []RevisionRollout{{
+				Percent: 71,
+			}, {
+				Percent: 29,
+			}},
+		},
+		want: []RevisionRollout{{
+			Percent: 71,
+		}, {
+			Percent: 29,
+		}},
+	}, {
+		name: "noop, 42%",
+		goal: 42,
+		prev: &ConfigurationRollout{
+			Percent: 42,
+			Revisions: []RevisionRollout{{
+				Percent: 21,
+			}, {
+				Percent: 21,
+			}},
+		},
+		want: []RevisionRollout{{
+			Percent: 21,
+		}, {
+			Percent: 21,
+		}},
+	}, {
+		name: "raise, 42% -> 75%",
+		goal: 75,
+		prev: &ConfigurationRollout{
+			Percent: 42,
+			Revisions: []RevisionRollout{{
+				Percent: 21,
+			}, {
+				Percent: 21,
+			}},
+		},
+		want: []RevisionRollout{{
+			Percent: 21,
+		}, {
+			Percent: 54,
+		}},
+	}, {
+		name: "lower, 75%->42%, lose 1",
+		goal: 42,
+		prev: &ConfigurationRollout{
+			Percent: 75,
+			Revisions: []RevisionRollout{{
+				Percent: 21,
+			}, {
+				Percent: 54,
+			}},
+		},
+		want: []RevisionRollout{{
+			Percent: 42,
+		}},
+	}, {
+		name: "lower, 75%->42%, lose 1, update 2, keep 3",
+		goal: 42,
+		prev: &ConfigurationRollout{
+			Percent: 75,
+			Revisions: []RevisionRollout{{
+				Percent: 21,
+			}, {
+				Percent: 22,
+			}, {
+				Percent: 32,
+			}},
+		},
+		want: []RevisionRollout{{
+			Percent: 10,
+		}, {
+			Percent: 32,
+		}},
+	}, {
+		name: "lower, 75%->42%, lose 2, update 3",
+		goal: 42,
+		prev: &ConfigurationRollout{
+			Percent: 75,
+			Revisions: []RevisionRollout{{
+				Percent: 10,
+			}, {
+				Percent: 5,
+			}, {
+				Percent: 60,
+			}},
+		},
+		want: []RevisionRollout{{
+			Percent: 42,
+		}},
+	}, {
+		name: "go to 0%",
+		goal: 0,
+		prev: &ConfigurationRollout{
+			Percent: 75,
+			Revisions: []RevisionRollout{{
+				Percent: 10,
+			}, {
+				Percent: 5,
+			}, {
+				Percent: 60,
+			}},
+		},
+		want: []RevisionRollout{},
+	}}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			adjustPercentage(tc.goal, tc.prev)
+			if got, want := tc.prev.Revisions, tc.want; !cmp.Equal(got, want) {
+				t.Errorf("Rollout Mistmatch(-want,+got):\n%s", cmp.Diff(want, got))
+			}
+		})
+	}
+}
diff --git a/pkg/reconciler/route/traffic/traffic_test.go b/pkg/reconciler/route/traffic/traffic_test.go
index 863b2e870..6f610d74c 100644
--- a/pkg/reconciler/route/traffic/traffic_test.go
+++ b/pkg/reconciler/route/traffic/traffic_test.go
@@ -63,6 +63,10 @@ var (
 	unreadyConfig *v1.Configuration
 	unreadyRev    *v1.Revision
 
+	readyUnreadyConfig *v1.Configuration
+	readyRevision      *v1.Revision
+	newUnreadyRev      *v1.Revision
+
 	// failedConfig only has failedRev, and it fails to be ready.
 	failedConfig *v1.Configuration
 	failedRev    *v1.Revision
@@ -94,6 +98,7 @@ func setUp() {
 	emptyConfig = getTestEmptyConfig("empty")
 	revDeletedConfig = testConfigWithDeletedRevision("latest-rev-deleted")
 	unreadyConfig, unreadyRev = getTestUnreadyConfig("unready")
+	readyUnreadyConfig, readyRevision, newUnreadyRev = createReadyUnreadyConfig("ready-unready")
 	failedConfig, failedRev = getTestFailedConfig("failed")
 	inactiveConfig, inactiveRev = getTestInactiveConfig("inactive")
 	goodConfig, goodOldRev, goodNewRev = getTestReadyConfig("good")
@@ -117,6 +122,7 @@ func setUp() {
 		emptyConfig,
 		goodConfig, goodOldRev, goodNewRev,
 		niceConfig, niceOldRev, niceNewRev,
+		readyUnreadyConfig, readyRevision, newUnreadyRev,
 	}
 
 	for _, obj := range objs {
@@ -1136,6 +1142,65 @@ func TestBuildTrafficConfigurationNotRoutableRevision(t *testing.T) {
 	}
 }
 
+func TestBuildTrafficConfigurationReadyNotReadyConfig(t *testing.T) {
+	expected := &Config{
+		Targets: map[string]RevisionTargets{
+			DefaultTarget: {{
+				TrafficTarget: v1.TrafficTarget{
+					RevisionName:      readyRevision.Name,
+					ConfigurationName: readyUnreadyConfig.Name,
+					LatestRevision:    ptr.Bool(true),
+					Percent:           ptr.Int64(100),
+				},
+				Protocol: net.ProtocolHTTP1,
+			}},
+		},
+		revisionTargets: []RevisionTarget{{
+			TrafficTarget: v1.TrafficTarget{
+				RevisionName:      readyRevision.Name,
+				ConfigurationName: readyUnreadyConfig.Name,
+				LatestRevision:    ptr.Bool(true),
+				Percent:           ptr.Int64(100),
+			},
+			Protocol: net.ProtocolHTTP1,
+		}},
+		Revisions: map[string]*v1.Revision{
+			readyRevision.Name: readyRevision,
+		},
+		Configurations: map[string]*v1.Configuration{
+			readyUnreadyConfig.Name: readyUnreadyConfig,
+		},
+	}
+	tc, err := BuildTrafficConfiguration(
+		configLister, revLister,
+		testRouteWithTrafficTargets(WithSpecTraffic(v1.TrafficTarget{
+			ConfigurationName: readyUnreadyConfig.Name,
+			LatestRevision:    ptr.Bool(true),
+			Percent:           ptr.Int64(100),
+		},
+		)))
+	if err != nil {
+		t.Fatal("BuildTrafficConfiguration =", err)
+	}
+	if got, want := tc, expected; !cmp.Equal(want, got, cmpOpts...) {
+		t.Errorf("Incorrect trafficm diff:(-want +got):\n%s", cmp.Diff(want, got, cmpOpts...))
+	}
+	gotR := tc.BuildRollout()
+	wantR := &Rollout{
+		Configurations: []ConfigurationRollout{{
+			ConfigurationName: readyUnreadyConfig.Name,
+			Percent:           100,
+			Revisions: []RevisionRollout{{
+				RevisionName: readyRevision.Name,
+				Percent:      100,
+			}},
+		}},
+	}
+	if !cmp.Equal(gotR, wantR) {
+		t.Errorf("Rollout mismatch, diff(-want,+got):\n%s", cmp.Diff(wantR, gotR))
+	}
+}
+
 func TestBuildTrafficConfigurationNotRoutableConfiguration(t *testing.T) {
 	expected := &Config{
 		Targets:        map[string]RevisionTargets{},
@@ -1358,8 +1423,23 @@ func testConfigWithDeletedRevision(name string) *v1.Configuration {
 	return config
 }
 
+func createReadyUnreadyConfig(name string) (*v1.Configuration, *v1.Revision, *v1.Revision) {
+	config := testConfig(name + "-config")
+
+	rev1 := testRevForConfig(config, name+"-revision-1")
+	rev1.Status.MarkResourcesAvailableTrue()
+	rev1.Status.MarkContainerHealthyTrue()
+	rev1.Status.MarkActiveTrue()
+	config.Status.SetLatestReadyRevisionName(rev1.Name)
+
+	rev2 := testRevForConfig(config, name+"-revision")
+	config.Status.SetLatestCreatedRevisionName(rev2.Name)
+	return config, rev1, rev2
+}
+
 func getTestUnreadyConfig(name string) (*v1.Configuration, *v1.Revision) {
 	config := testConfig(name + "-config")
+
 	rev := testRevForConfig(config, name+"-revision")
 	config.Status.SetLatestCreatedRevisionName(rev.Name)
 	return config, rev
diff --git a/test/config/100-namespace.yaml b/test/config/100-namespace.yaml
index b1414b416..1fcb556f4 100644
--- a/test/config/100-namespace.yaml
+++ b/test/config/100-namespace.yaml
@@ -24,10 +24,5 @@ metadata:
 ---
 apiVersion: v1
 kind: Namespace
-metadata:
-  name: serving-tests-security
----
-apiVersion: v1
-kind: Namespace
 metadata:
   name: tls
diff --git a/test/config/security/peerauthentication.yaml b/test/config/security/peerauthentication.yaml
deleted file mode 100644
index ab285965a..000000000
--- a/test/config/security/peerauthentication.yaml
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2019 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: "security.istio.io/v1beta1"
-kind: "PeerAuthentication"
-metadata:
-  name: "default"
-  namespace: "istio-system"
-  labels:
-    test.knative.dev/dependency: istio-sidecar
-spec:
-  mtls:
-    mode: STRICT
----
-apiVersion: "security.istio.io/v1beta1"
-kind: "PeerAuthentication"
-metadata:
-  name: "default"
-  namespace: "serving-tests"
-  labels:
-    test.knative.dev/dependency: istio-sidecar
-spec:
-  mtls:
-    mode: STRICT
----
-apiVersion: "security.istio.io/v1beta1"
-kind: "PeerAuthentication"
-metadata:
-  name: "default"
-  namespace: "knative-serving"
-  labels:
-    test.knative.dev/dependency: istio-sidecar
-spec:
-  mtls:
-    mode: PERMISSIVE
diff --git a/test/conformance/api/v1/service_test.go b/test/conformance/api/v1/service_test.go
index 2d9b8f5df..b35151899 100644
--- a/test/conformance/api/v1/service_test.go
+++ b/test/conformance/api/v1/service_test.go
@@ -598,35 +598,32 @@ func TestServiceCreateWithMultipleContainers(t *testing.T) {
 
 	names := test.ResourceNames{
 		Service: test.ObjectNameForTest(t),
+		Image:   test.ServingContainer,
+		Sidecars: []string{
+			test.SidecarContainer,
+		},
 	}
 
 	// Clean up on test failure or interrupt
 	test.EnsureTearDown(t, clients, &names)
-	// images are used to validate digest in validateControlPlane function
-	images := map[string]string{
-		"serving-container": test.ServingContainer,
-		"sidecar-container": test.SidecarContainer,
-	}
 	containers := []corev1.Container{{
-		Name:  "serving-container",
-		Image: pkgTest.ImagePath(test.ServingContainer),
+		Image: pkgTest.ImagePath(names.Image),
 		Ports: []corev1.ContainerPort{{
 			ContainerPort: 8881,
 		}},
 	}, {
-		Name:  "sidecar-container",
-		Image: pkgTest.ImagePath(test.SidecarContainer),
+		Image: pkgTest.ImagePath(names.Sidecars[0]),
 	}}
 
 	// Setup initial Service
-	if _, err := v1test.CreateServiceReadyForMultiContainer(t, clients, &names, func(svc *v1.Service) {
+	if _, err := v1test.CreateServiceReady(t, clients, &names, func(svc *v1.Service) {
 		svc.Spec.Template.Spec.Containers = containers
 	}); err != nil {
 		t.Fatalf("Failed to create initial Service %v: %v", names.Service, err)
 	}
 
 	// Validate State after Creation
-	if err := validateControlPlane(t, clients, names, "1" /*1 is the expected generation value*/, images); err != nil {
+	if err := validateControlPlane(t, clients, names, "1" /*1 is the expected generation value*/); err != nil {
 		t.Error(err)
 	}
 
diff --git a/test/conformance/api/v1/util.go b/test/conformance/api/v1/util.go
index bf453a0e3..5d5f9d6f0 100644
--- a/test/conformance/api/v1/util.go
+++ b/test/conformance/api/v1/util.go
@@ -121,26 +121,17 @@ func validateDataPlane(t testing.TB, clients *test.Clients, names test.ResourceN
 // Validates the state of Configuration, Revision, and Route objects for a runLatest Service.
 // The checks in this method should be able to be performed at any point in a
 // runLatest Service's lifecycle so long as the service is in a "Ready" state.
-func validateControlPlane(t *testing.T, clients *test.Clients, names test.ResourceNames, expectedGeneration string, imagesForMultipleContainers ...map[string]string) error {
+func validateControlPlane(t *testing.T, clients *test.Clients, names test.ResourceNames, expectedGeneration string) error {
 	t.Log("Checking to ensure Revision is in desired state with", "generation", expectedGeneration)
 	if err := v1test.CheckRevisionState(clients.ServingClient, names.Revision, func(r *v1.Revision) (bool, error) {
 		if ready, err := v1test.IsRevisionReady(r); !ready {
 			return false, fmt.Errorf("revision %s did not become ready to serve traffic: %w", names.Revision, err)
 		}
-		// for multi containers there will be more than one container and names.Image is a string,
-		// so in order to validate imageDigest for each images validateControlPlane accepting optional field called imagesForMultipleContainers
-		if names.Image == "" {
-			for _, value := range imagesForMultipleContainers {
-				for _, v := range r.Status.ContainerStatuses {
-					if image, ok := value[v.Name]; ok {
-						if validDigest, err := shared.ValidateImageDigest(t, image, v.ImageDigest); !validDigest {
-							return false, fmt.Errorf("imageDigest %s is not valid for imageName %s: %w", v.ImageDigest, image, err)
-						}
-					}
-				}
+		images := append([]string{names.Image}, names.Sidecars...)
+		for i, v := range r.Status.ContainerStatuses {
+			if validDigest, err := shared.ValidateImageDigest(t, images[i], v.ImageDigest); !validDigest {
+				return false, fmt.Errorf("imageDigest %s is not valid for imageName %s: %w", v.ImageDigest, images[i], err)
 			}
-		} else if validDigest, err := shared.ValidateImageDigest(t, names.Image, r.Status.ContainerStatuses[0].ImageDigest); !validDigest {
-			return false, fmt.Errorf("imageDigest %s is not valid for imageName %s: %w", r.Status.ContainerStatuses[0].ImageDigest, names.Image, err)
 		}
 		return true, nil
 	}); err != nil {
diff --git a/test/conformance/api/v1alpha1/domain_mapping_test.go b/test/conformance/api/v1alpha1/domain_mapping_test.go
index d8eec4cf0..98e6906b3 100644
--- a/test/conformance/api/v1alpha1/domain_mapping_test.go
+++ b/test/conformance/api/v1alpha1/domain_mapping_test.go
@@ -30,6 +30,7 @@ import (
 	"knative.dev/serving/pkg/apis/serving/v1alpha1"
 	"knative.dev/serving/test"
 	"knative.dev/serving/test/conformance/api/shared"
+	"knative.dev/serving/test/e2e"
 	v1test "knative.dev/serving/test/v1"
 )
 
@@ -43,13 +44,13 @@ func TestDomainMapping(t *testing.T) {
 
 	names := test.ResourceNames{
 		Service: test.ObjectNameForTest(t),
-		Image:   test.HelloWorld,
+		Image:   test.PizzaPlanet1,
 	}
 
 	// Clean up on test failure or interrupt.
 	test.EnsureTearDown(t, clients, &names)
 
-	// Setup initial Service.
+	// Set up initial Service.
 	svc, err := v1test.CreateServiceReady(t, clients, &names)
 	if err != nil {
 		t.Fatalf("Failed to create initial Service %v: %v", names.Service, err)
@@ -99,7 +100,88 @@ func TestDomainMapping(t *testing.T) {
 	}
 
 	// Should be able to access the test image text via the mapped domain.
-	if err := shared.CheckDistribution(ctx, t, clients, &url.URL{Host: host, Scheme: "http"}, test.ConcurrentRequests, test.ConcurrentRequests, []string{test.HelloWorldText}); err != nil {
+	if err := shared.CheckDistribution(ctx, t, clients, &url.URL{Host: host, Scheme: "http"}, test.ConcurrentRequests, test.ConcurrentRequests, []string{test.PizzaPlanetText1}); err != nil {
+		t.Errorf("CheckDistribution=%v, expected no error", err)
+	}
+
+	altClients := e2e.SetupAlternativeNamespace(t)
+	altNames := test.ResourceNames{
+		Service: test.ObjectNameForTest(t),
+		Image:   test.PizzaPlanet2,
+	}
+	test.EnsureTearDown(t, altClients, &altNames)
+
+	// Set up second Service in alt namespace.
+	altSvc, err := v1test.CreateServiceReady(t, altClients, &altNames)
+	if err != nil {
+		t.Fatalf("Failed to create initial Service %v: %v", altNames.Service, err)
+	}
+
+	// Create second domain mapping with same name in alt namespace - this will collide with the existing mapping.
+	var altDm *v1alpha1.DomainMapping
+	if err := reconciler.RetryTestErrors(func(int) error {
+		altDm, err = altClients.ServingAlphaClient.DomainMappings.Create(ctx, &v1alpha1.DomainMapping{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      host,
+				Namespace: altSvc.Service.Namespace,
+			},
+			Spec: v1alpha1.DomainMappingSpec{
+				Ref: duckv1.KReference{
+					Namespace:  altSvc.Service.Namespace,
+					Name:       altSvc.Service.Name,
+					APIVersion: "serving.knative.dev/v1",
+					Kind:       "Service",
+				},
+			},
+		}, metav1.CreateOptions{})
+		return err
+	}); err != nil {
+		t.Fatalf("Create(DomainMapping) = %v, expected no error", err)
+	}
+
+	t.Cleanup(func() {
+		altClients.ServingAlphaClient.DomainMappings.Delete(ctx, altDm.Name, metav1.DeleteOptions{})
+	})
+
+	// Second domain mapping should go to DomainMappingConditionDomainClaimed=false state.
+	waitErr = wait.PollImmediate(test.PollInterval, test.PollTimeout, func() (bool, error) {
+		state, err := altClients.ServingAlphaClient.DomainMappings.Get(context.Background(), dm.Name, metav1.GetOptions{})
+		if err != nil {
+			return true, err
+		}
+
+		return state.Generation == state.Status.ObservedGeneration &&
+			state.Status.GetCondition(v1alpha1.DomainMappingConditionDomainClaimed).IsFalse(), nil
+	})
+	if waitErr != nil {
+		t.Fatalf("The second DomainMapping %s did not enter DomainMappingConditionDomainClaimed=false state: %v", altDm.Name, waitErr)
+	}
+
+	// Because the second DomainMapping collided with the first, it should not have taken effect.
+	if err := shared.CheckDistribution(ctx, t, clients, &url.URL{Host: host, Scheme: "http"}, test.ConcurrentRequests, test.ConcurrentRequests, []string{test.PizzaPlanetText1}); err != nil {
+		t.Errorf("CheckDistribution=%v, expected no error", err)
+	}
+
+	// Delete the first DomainMapping.
+	if err := clients.ServingAlphaClient.DomainMappings.Delete(ctx, dm.Name, metav1.DeleteOptions{}); err != nil {
+		t.Fatalf("Delete=%v, expected no error", err)
+	}
+
+	// The second DomainMapping should now be able to claim the domain.
+	waitErr = wait.PollImmediate(test.PollInterval, test.PollTimeout, func() (bool, error) {
+		state, err := altClients.ServingAlphaClient.DomainMappings.Get(context.Background(), altDm.Name, metav1.GetOptions{})
+		if err != nil {
+			return true, err
+		}
+
+		return state.IsReady(), nil
+	})
+	if waitErr != nil {
+		t.Fatalf("The second DomainMapping %s was not marked as Ready: %v", dm.Name, waitErr)
+	}
+
+	// The domain name should now point to the second service.
+	if err := shared.CheckDistribution(ctx, t, clients, &url.URL{Host: host, Scheme: "http"}, test.ConcurrentRequests, test.ConcurrentRequests, []string{test.PizzaPlanetText2}); err != nil {
 		t.Errorf("CheckDistribution=%v, expected no error", err)
 	}
 }
diff --git a/test/crd.go b/test/crd.go
index 9f45c8b53..260fdd7d4 100644
--- a/test/crd.go
+++ b/test/crd.go
@@ -33,6 +33,7 @@ type ResourceNames struct {
 	TrafficTarget string
 	URL           *url.URL
 	Image         string
+	Sidecars      []string
 }
 
 // AppendRandomString will generate a random string that begins with prefix. This is useful
diff --git a/test/e2e-common.sh b/test/e2e-common.sh
index 36a4f8885..2c65c7af0 100644
--- a/test/e2e-common.sh
+++ b/test/e2e-common.sh
@@ -406,8 +406,6 @@ function test_setup() {
   if (( MESH )); then
     kubectl label namespace serving-tests istio-injection=enabled
     kubectl label namespace serving-tests-alt istio-injection=enabled
-    kubectl label namespace serving-tests-security istio-injection=enabled
-    ko apply ${KO_FLAGS} -f ${TEST_CONFIG_DIR}/security/ --selector=test.knative.dev/dependency=istio-sidecar || return 1
   fi
 
   echo ">> Uploading test images..."
@@ -420,6 +418,12 @@ function test_setup() {
   wait_until_ingress_running || return 1
 }
 
+# Apply the logging config for testing. This should be called after test_setup has been triggered.
+function test_logging_config_setup() {
+  echo ">> Setting up test logging config..."
+  ko apply ${KO_FLAGS} -f ${TMP_DIR}/test/config/config-logging.yaml || return 1
+}
+
 # Delete test resources
 function test_teardown() {
   local TEST_CONFIG_DIR=${TMP_DIR}/test/config
@@ -433,8 +437,6 @@ function test_teardown() {
   kubectl delete --ignore-not-found --now --timeout 60s namespace serving-tests
   kubectl delete all --all --ignore-not-found --now --timeout 60s -n serving-tests-alt
   kubectl delete --ignore-not-found --now --timeout 60s namespace serving-tests-alt
-  kubectl delete all --all --ignore-not-found --now --timeout 60s -n serving-tests-security
-  kubectl delete --ignore-not-found --now --timeout 60s namespace serving-tests-security
 }
 
 # Dump more information when test fails.
diff --git a/test/e2e-networking-library.sh b/test/e2e-networking-library.sh
index c9cec53ca..635cab9fa 100644
--- a/test/e2e-networking-library.sh
+++ b/test/e2e-networking-library.sh
@@ -20,8 +20,14 @@ function install_istio() {
   fi
 
   if [[ -z "${NET_ISTIO_COMMIT}" ]]; then
-    # TODO: Figure out the commit of net-istio.yaml from net-istio.yaml
-    readonly NET_ISTIO_COMMIT="8102cd3d32f05be1c58260a9717d532a4a6d2f60"
+    NET_ISTIO_COMMIT=$(head -n 1 ${1} | grep "# Generated when HEAD was" | sed 's/^.* //')
+    echo "Got NET_ISTIO_COMMIT from ${1}: ${NET_ISTIO_COMMIT}"
+  fi
+
+  # TODO: remove this when all the net-istio.yaml in use contain a commit ID
+  if [[ -z "${NET_ISTIO_COMMIT}" ]]; then
+    NET_ISTIO_COMMIT="8102cd3d32f05be1c58260a9717d532a4a6d2f60"
+    echo "Hard coded NET_ISTIO_COMMIT: ${NET_ISTIO_COMMIT}"
   fi
 
   # And checkout the setup script based on that commit.
@@ -157,7 +163,7 @@ function install_contour() {
 function wait_until_ingress_running() {
   if [[ -n "${ISTIO_VERSION}" ]]; then
     wait_until_pods_running istio-system || return 1
-    wait_until_service_has_external_http_address istio-system istio-ingressgateway
+    wait_until_service_has_external_http_address istio-system istio-ingressgateway || return 1
   fi
   if [[ -n "${GLOO_VERSION}" ]]; then
     # we must set these override values to allow the test spoofing client to work with Gloo
diff --git a/test/e2e-tests.sh b/test/e2e-tests.sh
index 599cb76b6..7feac7b0a 100755
--- a/test/e2e-tests.sh
+++ b/test/e2e-tests.sh
@@ -86,10 +86,12 @@ sleep 30
 
 # Run conformance and e2e tests.
 
-# Currently only Istio and Contour implement the alpha features.
-# TODO(#9874): Remove this guard once other ingresses implement these too.
+# Currently only Istio, Contour and Kourier implement the alpha features.
 alpha=""
-if [[ -z "${INGRESS_CLASS}" || "${INGRESS_CLASS}" == "istio.ingress.networking.knative.dev" || "${INGRESS_CLASS}" == "contour.ingress.networking.knative.dev" ]]; then
+if [[ -z "${INGRESS_CLASS}" \
+  || "${INGRESS_CLASS}" == "istio.ingress.networking.knative.dev" \
+  || "${INGRESS_CLASS}" == "contour.ingress.networking.knative.dev" \
+  || "${INGRESS_CLASS}" == "kourier.ingress.networking.knative.dev" ]]; then
   alpha="--enable-alpha"
 fi
 
diff --git a/test/e2e-upgrade-tests.sh b/test/e2e-upgrade-tests.sh
index 462e176be..c6d1d43ee 100755
--- a/test/e2e-upgrade-tests.sh
+++ b/test/e2e-upgrade-tests.sh
@@ -53,6 +53,7 @@ function install_latest_release() {
 
   install_knative_serving latest-release \
       || fail_test "Knative latest release installation failed"
+  test_logging_config_setup
   wait_until_pods_running ${SYSTEM_NAMESPACE}
   wait_until_batch_job_complete ${SYSTEM_NAMESPACE}
 }
@@ -60,6 +61,7 @@ function install_latest_release() {
 function install_head() {
   header "Installing Knative head release"
   install_knative_serving || fail_test "Knative head release installation failed"
+  test_logging_config_setup
   wait_until_pods_running ${SYSTEM_NAMESPACE}
   wait_until_batch_job_complete ${SYSTEM_NAMESPACE}
 }
diff --git a/test/e2e/activator_probe_permitted_test.go b/test/e2e/activator_probe_permitted_test.go
deleted file mode 100644
index a1a8cb820..000000000
--- a/test/e2e/activator_probe_permitted_test.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// +build e2e
-
-/*
-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 e2e
-
-import (
-	"context"
-	"net/http"
-	"testing"
-
-	pkgTest "knative.dev/pkg/test"
-	"knative.dev/serving/test"
-	v1test "knative.dev/serving/test/v1"
-)
-
-//This test checks if the activator can probe
-//the service when istio end user auth policy is
-//applied on the service.
-//This test needs istio side car injected and
-//istio policy check enabled.
-//policy is present in test/config/security/policy.yaml
-//apply policy before running this test
-func TestAllowedProbes(t *testing.T) {
-	t.Parallel()
-
-	clients := SetupServingNamespaceforSecurityTesting(t)
-
-	names := test.ResourceNames{
-		Service: test.ObjectNameForTest(t),
-		Image:   "helloworld",
-	}
-
-	test.EnsureTearDown(t, clients, &names)
-
-	t.Log("Creating a new Service")
-	resources, err := v1test.CreateServiceReady(t, clients, &names)
-	if err != nil {
-		t.Fatalf("Failed to create initial Service: %v: %v", names.Service, err)
-	}
-
-	url := resources.Route.Status.URL.URL()
-	if _, err := pkgTest.WaitForEndpointState(
-		context.Background(),
-		clients.KubeClient,
-		t.Logf,
-		url,
-		v1test.RetryingRouteInconsistency(pkgTest.MatchesAllOf(pkgTest.IsOneOfStatusCodes(http.StatusUnauthorized))),
-		"HelloWorldServesAuthFailed",
-		test.ServingFlags.ResolvableDomain,
-		test.AddRootCAtoTransport(context.Background(), t.Logf, clients, test.ServingFlags.HTTPS),
-	); err != nil {
-		// check if side car is injected before reporting error
-		if _, err := getContainer(clients.KubeClient, resources.Service.Name, "istio-proxy", resources.Service.Namespace); err != nil {
-			t.Skip("Side car not enabled, skipping test")
-		}
-		t.Fatalf("The endpoint %s for Route %s didn't serve the expected status %d: %v", url, names.Route, http.StatusUnauthorized, err)
-	}
-}
diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go
index 22817f92e..02572a22b 100644
--- a/test/e2e/e2e.go
+++ b/test/e2e/e2e.go
@@ -58,12 +58,6 @@ func SetupAlternativeNamespace(t *testing.T) *test.Clients {
 	return SetupWithNamespace(t, test.AlternativeServingNamespace)
 }
 
-//SetupServingNamespaceforSecurityTesting creates the client objects needed in e2e tests
-// under the security testing namespace.
-func SetupServingNamespaceforSecurityTesting(t *testing.T) *test.Clients {
-	return SetupWithNamespace(t, test.ServingNamespaceforSecurityTesting)
-}
-
 // SetupWithNamespace creates the client objects needed in the e2e tests under the specified namespace.
 func SetupWithNamespace(t *testing.T, namespace string) *test.Clients {
 	t.Helper()
diff --git a/test/e2e/multicontainer/multicontainer_test.go b/test/e2e/multicontainer/multicontainer_test.go
index 0138deac3..1f1e735de 100644
--- a/test/e2e/multicontainer/multicontainer_test.go
+++ b/test/e2e/multicontainer/multicontainer_test.go
@@ -36,23 +36,27 @@ func TestMultiContainer(t *testing.T) {
 
 	clients := e2e.Setup(t)
 
+	names := test.ResourceNames{
+		Service: test.ObjectNameForTest(t),
+		Image:   test.ServingContainer,
+		Sidecars: []string{
+			test.SidecarContainer,
+		},
+	}
+
 	containers := []corev1.Container{{
-		Image: pkgTest.ImagePath(test.ServingContainer),
+		Image: pkgTest.ImagePath(names.Image),
 		Ports: []corev1.ContainerPort{{
 			ContainerPort: 8881,
 		}},
 	}, {
-		Image: pkgTest.ImagePath(test.SidecarContainer),
+		Image: pkgTest.ImagePath(names.Sidecars[0]),
 	}}
 
-	names := test.ResourceNames{
-		Service: test.ObjectNameForTest(t),
-	}
-
 	test.EnsureTearDown(t, clients, &names)
 	t.Log("Creating a new Service")
 
-	resources, err := v1test.CreateServiceReadyForMultiContainer(t, clients, &names, func(svc *v1.Service) {
+	resources, err := v1test.CreateServiceReady(t, clients, &names, func(svc *v1.Service) {
 		svc.Spec.Template.Spec.Containers = containers
 	})
 	if err != nil {
diff --git a/test/e2e_constants.go b/test/e2e_constants.go
index b58e1fd93..d1fb41e31 100644
--- a/test/e2e_constants.go
+++ b/test/e2e_constants.go
@@ -28,9 +28,6 @@ const (
 	// namespace tests in.
 	AlternativeServingNamespace = "serving-tests-alt"
 
-	// ServingNamespaceforSecurityTesting is the namespace for security tests.
-	ServingNamespaceforSecurityTesting = "serving-tests-security"
-
 	// TLSNamespace is the namespace for serving tls tests
 	TLSNamespace = "tls"
 
diff --git a/test/performance/README.md b/test/performance/README.md
index 76ab1e3f6..de0a1a562 100644
--- a/test/performance/README.md
+++ b/test/performance/README.md
@@ -1,9 +1,9 @@
 # Performance tests
 
 Knative performance tests are tests geared towards producing useful performance
-metrics of the knative system. As such they can choose to take a blackbox
+metrics of the knative system. As such they can choose to take a closed-box
 point-of-view of the system and use it just like an end-user might see it. They
-can also go more whiteboxy to narrow down the components under test.
+can also go more open-boxy to narrow down the components under test.
 
 ## Load Generator
 
diff --git a/test/v1/service.go b/test/v1/service.go
index 572154c9f..4415ec9cb 100644
--- a/test/v1/service.go
+++ b/test/v1/service.go
@@ -87,20 +87,6 @@ func GetResourceObjects(clients *test.Clients, names test.ResourceNames) (*Resou
 	}, nil
 }
 
-// CreateServiceReadyForMultiContainer creates a new Service for multi container in 'Ready' state . This function expects Service
-// name passed in through 'names'. Names is updated with the Route and Configuration created by the Service
-// and ResourceObjects is returned with the Service, Route, and Configuration objects.
-// Returns error if the service does not come up correctly.
-func CreateServiceReadyForMultiContainer(t testing.TB, clients *test.Clients, names *test.ResourceNames, fopt ...rtesting.ServiceOption) (*ResourceObjects, error) {
-	t.Log("Creating a new Service", "service", names.Service)
-	svc := rtesting.ServiceWithoutNamespace(names.Service, fopt...)
-	svc, err := createService(t, clients, svc)
-	if err != nil {
-		return nil, err
-	}
-	return getResourceObjects(t, clients, names, svc)
-}
-
 // CreateServiceReady creates a new Service in state 'Ready'. This function expects Service and Image name
 // passed in through 'names'.  Names is updated with the Route and Configuration created by the Service
 // and ResourceObjects is returned with the Service, Route, and Configuration objects.
@@ -227,10 +213,13 @@ func WaitForServiceLatestRevision(clients *test.Clients, names test.ResourceName
 // Service returns a Service object in namespace with the name names.Service
 // that uses the image specified by names.Image.
 func Service(names test.ResourceNames, fopt ...rtesting.ServiceOption) *v1.Service {
-	a := append([]rtesting.ServiceOption{
-		rtesting.WithConfigSpec(ConfigurationSpec(pkgTest.ImagePath(names.Image))),
-	}, fopt...)
-	return rtesting.ServiceWithoutNamespace(names.Service, a...)
+	if names.Image != "" && len(names.Sidecars) == 0 {
+		a := append([]rtesting.ServiceOption{
+			rtesting.WithConfigSpec(ConfigurationSpec(pkgTest.ImagePath(names.Image))),
+		}, fopt...)
+		return rtesting.ServiceWithoutNamespace(names.Service, a...)
+	}
+	return rtesting.ServiceWithoutNamespace(names.Service, fopt...)
 }
 
 // WaitForServiceState polls the status of the Service called name
diff --git a/third_party/cert-manager-latest/net-certmanager.yaml b/third_party/cert-manager-latest/net-certmanager.yaml
index ae507a820..43bb920b1 100644
--- a/third_party/cert-manager-latest/net-certmanager.yaml
+++ b/third_party/cert-manager-latest/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: "v20201106-859a9fd"
+    serving.knative.dev/release: "v20201110-859a9fd"
     serving.knative.dev/controller: "true"
     networking.knative.dev/certificate-provider: cert-manager
 rules:
@@ -49,7 +49,7 @@ kind: ValidatingWebhookConfiguration
 metadata:
   name: config.webhook.net-certmanager.networking.internal.knative.dev
   labels:
-    serving.knative.dev/release: "v20201106-859a9fd"
+    serving.knative.dev/release: "v20201110-859a9fd"
 webhooks:
   - admissionReviewVersions:
       - v1beta1
@@ -86,7 +86,7 @@ metadata:
   name: net-certmanager-webhook-certs
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201106-859a9fd"
+    serving.knative.dev/release: "v20201110-859a9fd"
 
 ---
 # Copyright 2020 The Knative Authors
@@ -109,7 +109,7 @@ metadata:
   name: config-certmanager
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201106-859a9fd"
+    serving.knative.dev/release: "v20201110-859a9fd"
     networking.knative.dev/certificate-provider: cert-manager
 data:
   _example: |
@@ -156,7 +156,7 @@ metadata:
   name: networking-certmanager
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201106-859a9fd"
+    serving.knative.dev/release: "v20201110-859a9fd"
     networking.knative.dev/certificate-provider: cert-manager
 spec:
   selector:
@@ -168,7 +168,7 @@ spec:
         cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
       labels:
         app: networking-certmanager
-        serving.knative.dev/release: "v20201106-859a9fd"
+        serving.knative.dev/release: "v20201110-859a9fd"
     spec:
       serviceAccountName: controller
       containers:
@@ -213,7 +213,7 @@ kind: Service
 metadata:
   labels:
     app: networking-certmanager
-    serving.knative.dev/release: "v20201106-859a9fd"
+    serving.knative.dev/release: "v20201110-859a9fd"
     networking.knative.dev/certificate-provider: cert-manager
   name: networking-certmanager
   namespace: knative-serving
@@ -250,7 +250,7 @@ metadata:
   name: net-certmanager-webhook
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201106-859a9fd"
+    serving.knative.dev/release: "v20201110-859a9fd"
 spec:
   selector:
     matchLabels:
@@ -263,7 +263,7 @@ spec:
       labels:
         app: net-certmanager-webhook
         role: net-certmanager-webhook
-        serving.knative.dev/release: "v20201106-859a9fd"
+        serving.knative.dev/release: "v20201110-859a9fd"
     spec:
       serviceAccountName: controller
       containers:
@@ -329,7 +329,7 @@ metadata:
   namespace: knative-serving
   labels:
     role: net-certmanager-webhook
-    serving.knative.dev/release: "v20201106-859a9fd"
+    serving.knative.dev/release: "v20201110-859a9fd"
 spec:
   ports:
     # Define metrics and profiling for them to be accessible within service meshes.
diff --git a/third_party/contour-latest/net-contour.yaml b/third_party/contour-latest/net-contour.yaml
index c7dcf582d..4c8ff5388 100644
--- a/third_party/contour-latest/net-contour.yaml
+++ b/third_party/contour-latest/net-contour.yaml
@@ -34,7 +34,7 @@ metadata:
   namespace: knative-serving
   labels:
     networking.knative.dev/ingress-provider: contour
-    serving.knative.dev/release: "v20201106-2ae0c66f"
+    serving.knative.dev/release: "v20201114-2ae0c66f"
 data:
   _example: |
     ################################
@@ -105,7 +105,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:79097613e8368fc0a29e64cbf7e00197a4075279faf18f2ab1200bc27bb45dc6
+          image: gcr.io/knative-nightly/knative.dev/net-contour/cmd/controller@sha256:6a1866086c4ee0570306239ece6d0a95504042b50c081f39516247063f3e341a
           resources:
             requests:
               cpu: 40m
diff --git a/third_party/istio-latest/net-istio.yaml b/third_party/istio-latest/net-istio.yaml
index e42ee9257..fe5004350 100644
--- a/third_party/istio-latest/net-istio.yaml
+++ b/third_party/istio-latest/net-istio.yaml
@@ -1,4 +1,4 @@
-# Generated when HEAD was 40b59920910828a29cf584c77bd785c8c6cf4a90
+# Generated when HEAD was cbc1a3d4e2ab6826fc042e3dad501c24d26dc327
 #
 # Copyright 2019 The Knative Authors
 #
@@ -20,7 +20,7 @@ metadata:
   # These are the permissions needed by the Istio Ingress implementation.
   name: knative-serving-istio
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     serving.knative.dev/controller: "true"
     networking.knative.dev/ingress-provider: istio
 rules:
@@ -50,7 +50,7 @@ metadata:
   name: knative-ingress-gateway
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -87,7 +87,7 @@ metadata:
   name: cluster-local-gateway
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -106,7 +106,7 @@ metadata:
   name: knative-local-gateway
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -125,7 +125,7 @@ metadata:
   name: knative-local-gateway
   namespace: istio-system
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 spec:
   type: ClusterIP
@@ -145,7 +145,7 @@ metadata:
   name: "webhook"
   namespace: "knative-serving"
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -160,7 +160,7 @@ metadata:
   name: "istio-webhook"
   namespace: "knative-serving"
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -189,7 +189,7 @@ kind: MutatingWebhookConfiguration
 metadata:
   name: webhook.istio.networking.internal.knative.dev
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 webhooks:
   - admissionReviewVersions:
@@ -225,7 +225,7 @@ kind: ValidatingWebhookConfiguration
 metadata:
   name: config.webhook.istio.networking.internal.knative.dev
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 webhooks:
   - admissionReviewVersions:
@@ -263,7 +263,7 @@ metadata:
   name: istio-webhook-certs
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 
 ---
@@ -287,7 +287,7 @@ metadata:
   name: config-istio
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 data:
   _example: |
@@ -357,7 +357,7 @@ metadata:
   name: networking-istio
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -373,14 +373,14 @@ spec:
         sidecar.istio.io/inject: "false"
       labels:
         app: networking-istio
-        serving.knative.dev/release: "v20201106-40b59920"
+        serving.knative.dev/release: "v20201111-cbc1a3d4"
     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:0ef2abf0e613ab19ff927cd2687273dceaadf1f04a6d7c45a33cc0d3e6448146
+          image: gcr.io/knative-nightly/knative.dev/net-istio/cmd/controller@sha256:604660e3e300ae8e4ebb92724cefea9eb14d997b664ec9cf76be2efc5c86bf20
           resources:
             requests:
               cpu: 30m
@@ -437,7 +437,7 @@ metadata:
   name: istio-webhook
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -451,14 +451,14 @@ spec:
       labels:
         app: istio-webhook
         role: istio-webhook
-        serving.knative.dev/release: "v20201106-40b59920"
+        serving.knative.dev/release: "v20201111-cbc1a3d4"
     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:66e4b06f933e56461161af236ced64adb04c2c1bb9e5376a8a86f2ddc4a36859
+          image: gcr.io/knative-nightly/knative.dev/net-istio/cmd/webhook@sha256:17bc9958b175fb5d58995d35614bae730fbe84bdc48b7adacdad2f556c78d9d6
           resources:
             requests:
               cpu: 20m
@@ -512,7 +512,7 @@ metadata:
   namespace: knative-serving
   labels:
     role: istio-webhook
-    serving.knative.dev/release: "v20201106-40b59920"
+    serving.knative.dev/release: "v20201111-cbc1a3d4"
     networking.knative.dev/ingress-provider: istio
 spec:
   ports:
diff --git a/third_party/kourier-latest/kourier.yaml b/third_party/kourier-latest/kourier.yaml
index 63041ec6e..7d63e78d4 100644
--- a/third_party/kourier-latest/kourier.yaml
+++ b/third_party/kourier-latest/kourier.yaml
@@ -117,7 +117,7 @@ spec:
         app: 3scale-kourier-control
     spec:
       containers:
-        - image: gcr.io/knative-nightly/knative.dev/net-kourier/cmd/kourier@sha256:5ccfd87d41145b79d3f8f2b583ccef4e8ab7bb9320ec098b8738df209d1c81fa
+        - image: gcr.io/knative-nightly/knative.dev/net-kourier/cmd/kourier@sha256:28183c42d1b02e927db16861ed9984c653cc9c0cc72b5d9ab450f3c229c2872e
           name: kourier-control
           env:
             - name: CERTS_SECRET_NAMESPACE