Repository: knative/serving
Pull request #10334 (2020-12-09 21:19:31)

 Unify the params to all be in nanoseconds.
- fix comments, which were a bit off
- switch all fields to nanoseconds (some already were)
- This removes lots of rounding and makes the process more precise and easier
to reason about. Ceil(1.1) = 2s, over 40% of difference — so avoid that.

/hold
For the base pr to merge

/assign @tcnghia 

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

This pull-request has been approved by: *<a href="https://github.com/knative/serving/pull/10334#" title="Author self-approved">vagababov</a>*

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

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[pkg/reconciler/route/OWNERS](https://github.com/knative/serving/blob/master/pkg/reconciler/route/OWNERS)~~ [vagababov]

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":[]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/knative/serving/pull/10334?src=pr&el=h1) Report
> Merging [#10334](https://codecov.io/gh/knative/serving/pull/10334?src=pr&el=desc) (b9459a0) into [master](https://codecov.io/gh/knative/serving/commit/9eb3cd5b3e4857d2cd0b54b6c1b0fc72a6d2e4b6?el=desc) (9eb3cd5) will **decrease** coverage by `0.00%`.
> The diff coverage is `100.00%`.

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

```diff
@@            Coverage Diff             @@
##           master   #10334      +/-   ##
==========================================
- Coverage   88.22%   88.21%   -0.01%     
==========================================
  Files         184      184              
  Lines        8679     8674       -5     
==========================================
- Hits         7657     7652       -5     
  Misses        786      786              
  Partials      236      236              
```


| [Impacted Files](https://codecov.io/gh/knative/serving/pull/10334?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/reconciler/route/traffic/rollout.go](https://codecov.io/gh/knative/serving/pull/10334/diff?src=pr&el=tree#diff-cGtnL3JlY29uY2lsZXIvcm91dGUvdHJhZmZpYy9yb2xsb3V0Lmdv) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/knative/serving/pull/10334?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/10334?src=pr&el=footer). Last update [9eb3cd5...1c53aee](https://codecov.io/gh/knative/serving/pull/10334?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

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

This pull-request has been approved by: *<a href="https://github.com/knative/serving/pull/10334#pullrequestreview-548642754" title="LGTM">tcnghia</a>*, *<a href="https://github.com/knative/serving/pull/10334#" title="Author self-approved">vagababov</a>*

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

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[pkg/reconciler/route/OWNERS](https://github.com/knative/serving/blob/master/pkg/reconciler/route/OWNERS)~~ [tcnghia,vagababov]

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":[]} -->
knative-prow-robot: @vagababov: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
knative-prow-robot: New changes are detected. LGTM label has been removed.
vagababov: Rebased.
tcnghia: /lgtm

Patch:
diff --git a/.github/workflows/knative-boilerplate.yaml b/.github/workflows/knative-boilerplate.yaml
index 9329fdd7e..c0e648b1c 100644
--- a/.github/workflows/knative-boilerplate.yaml
+++ b/.github/workflows/knative-boilerplate.yaml
@@ -19,7 +19,7 @@ name: Boilerplate
 
 on:
   pull_request:
-    branches: [ 'master', 'release-*' ]
+    branches: [ 'main', 'master', 'release-*' ]
 
 jobs:
 
diff --git a/.github/workflows/knative-donotsubmit.yaml b/.github/workflows/knative-donotsubmit.yaml
index d9131173d..cec5c3077 100644
--- a/.github/workflows/knative-donotsubmit.yaml
+++ b/.github/workflows/knative-donotsubmit.yaml
@@ -19,7 +19,7 @@ name: Do Not Submit
 
 on:
   pull_request:
-    branches: [ 'master', 'release-*' ]
+    branches: [ 'main', 'master', 'release-*' ]
 
 jobs:
 
diff --git a/.github/workflows/knative-go-build.yaml b/.github/workflows/knative-go-build.yaml
index 226eac87d..7bd13004a 100644
--- a/.github/workflows/knative-go-build.yaml
+++ b/.github/workflows/knative-go-build.yaml
@@ -19,7 +19,7 @@ name: Build
 
 on:
   pull_request:
-    branches: [ 'master', 'release-*' ]
+    branches: [ 'main', 'master', 'release-*' ]
 
 jobs:
 
diff --git a/.github/workflows/knative-security.yaml b/.github/workflows/knative-security.yaml
index 47186ef2e..00fa9fccf 100644
--- a/.github/workflows/knative-security.yaml
+++ b/.github/workflows/knative-security.yaml
@@ -19,10 +19,10 @@ name: 'Security'
 
 on:
   push:
-    branches: [ 'master', 'release-*' ]
+    branches: [ 'main', 'master', 'release-*' ]
 
   pull_request:
-    branches: [ 'master', 'release-*' ]
+    branches: [ 'main', 'master', 'release-*' ]
 
 jobs:
   analyze:
diff --git a/.github/workflows/knative-style.yaml b/.github/workflows/knative-style.yaml
index 5d3d3163e..b38122ab6 100644
--- a/.github/workflows/knative-style.yaml
+++ b/.github/workflows/knative-style.yaml
@@ -19,7 +19,7 @@ name: Code Style
 
 on:
   pull_request:
-    branches: [ 'master', 'release-*' ]
+    branches: [ 'main', 'master', 'release-*' ]
 
 jobs:
 
diff --git a/.github/workflows/knative-verify.yaml b/.github/workflows/knative-verify.yaml
index ed5bb3a13..3443a0496 100644
--- a/.github/workflows/knative-verify.yaml
+++ b/.github/workflows/knative-verify.yaml
@@ -19,7 +19,7 @@ name: Verify
 
 on:
   pull_request:
-    branches: [ 'master', 'release-*' ]
+    branches: [ 'main', 'master', 'release-*' ]
 
 jobs:
 
diff --git a/pkg/autoscaler/statforwarder/forwarder.go b/pkg/autoscaler/statforwarder/forwarder.go
index 1d3ed2216..30efe1782 100644
--- a/pkg/autoscaler/statforwarder/forwarder.go
+++ b/pkg/autoscaler/statforwarder/forwarder.go
@@ -73,16 +73,10 @@ type stat struct {
 // 3. Can be used to forward the metrics owned by a bucket based on
 //    the holder IP.
 type Forwarder struct {
-	selfIP          string
-	logger          *zap.SugaredLogger
-	kc              kubernetes.Interface
-	endpointsLister corev1listers.EndpointsLister
-
-	// `accept` is the function to process a StatMessage which doesn't need
-	// to be forwarded.
-	accept statProcessor
+	logger *zap.SugaredLogger
 	// bs is the BucketSet including all Autoscaler buckets.
 	bs *hash.BucketSet
+
 	// processorsLock is the lock for processors.
 	processorsLock sync.RWMutex
 	processors     map[string]bucketProcessor
@@ -93,41 +87,42 @@ type Forwarder struct {
 	// on when shutting down.
 	processingWg sync.WaitGroup
 
-	// id2ip stores the IP extracted from the holder identity to avoid
-	// string split each time.
-	id2ip map[string]string
-
 	statCh chan stat
 	stopCh chan struct{}
 }
 
 // New creates a new Forwarder.
 func New(ctx context.Context, logger *zap.SugaredLogger, kc kubernetes.Interface, selfIP string, bs *hash.BucketSet, accept statProcessor) *Forwarder {
-	ns := system.Namespace()
 	bkts := bs.Buckets()
 	endpointsInformer := endpointsinformer.Get(ctx)
 	f := &Forwarder{
-		selfIP:          selfIP,
+		logger:     logger,
+		bs:         bs,
+		processors: make(map[string]bucketProcessor, len(bkts)),
+		statCh:     make(chan stat, 1000),
+		stopCh:     make(chan struct{}),
+	}
+
+	f.processingWg.Add(1)
+	go f.process()
+
+	lt := &leaseTracker{
 		logger:          logger,
+		selfIP:          selfIP,
+		bs:              bs,
 		kc:              kc,
 		endpointsLister: endpointsInformer.Lister(),
-		bs:              bs,
-		processors:      make(map[string]bucketProcessor, len(bkts)),
-		accept:          accept,
 		id2ip:           make(map[string]string),
-		statCh:          make(chan stat, 1000),
-		stopCh:          make(chan struct{}),
+		accept:          accept,
+		fwd:             f,
 	}
 
-	f.processingWg.Add(1)
-	go f.process()
-
 	leaseInformer := leaseinformer.Get(ctx)
 	leaseInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
-		FilterFunc: f.filterFunc(ns),
+		FilterFunc: lt.filterFunc(system.Namespace()),
 		Handler: cache.ResourceEventHandlerFuncs{
-			AddFunc:    f.leaseUpdated,
-			UpdateFunc: controller.PassNew(f.leaseUpdated),
+			AddFunc:    lt.leaseUpdated,
+			UpdateFunc: controller.PassNew(lt.leaseUpdated),
 			// TODO(yanweiguo): Set up DeleteFunc.
 		},
 	})
@@ -135,7 +130,31 @@ func New(ctx context.Context, logger *zap.SugaredLogger, kc kubernetes.Interface
 	return f
 }
 
-func (f *Forwarder) extractIP(holder string) (string, error) {
+// leaseTracker monitors lease resources to update the Forwarder's processor configuration(s)
+// with the appropriate owner's stats endpoint.  When we own the lease, a localProcessor is
+// used, and when someone else owns the lease a remoteProcessor is used with their stats
+// endpoint.
+type leaseTracker struct {
+	logger *zap.SugaredLogger
+	selfIP string
+	// bs is the BucketSet including all Autoscaler buckets.
+	bs *hash.BucketSet
+
+	kc              kubernetes.Interface
+	endpointsLister corev1listers.EndpointsLister
+
+	// id2ip stores the IP extracted from the holder identity to avoid
+	// string split each time.
+	id2ip map[string]string
+
+	// `accept` is the function to process a StatMessage which doesn't need
+	// to be forwarded.
+	accept statProcessor
+
+	fwd *Forwarder
+}
+
+func (f *leaseTracker) extractIP(holder string) (string, error) {
 	if ip, ok := f.id2ip[holder]; ok {
 		return ip, nil
 	}
@@ -147,7 +166,7 @@ func (f *Forwarder) extractIP(holder string) (string, error) {
 	return ip, err
 }
 
-func (f *Forwarder) filterFunc(namespace string) func(interface{}) bool {
+func (f *leaseTracker) filterFunc(namespace string) func(interface{}) bool {
 	return func(obj interface{}) bool {
 		l := obj.(*coordinationv1.Lease)
 
@@ -172,7 +191,7 @@ func (f *Forwarder) filterFunc(namespace string) func(interface{}) bool {
 			f.logger.Warn("Found invalid Lease holder identify ", holder)
 			return false
 		}
-		if p := f.getProcessor(l.Name); p != nil && p.is(holder) {
+		if p := f.fwd.getProcessor(l.Name); p != nil && p.is(holder) {
 			return false
 		}
 
@@ -180,7 +199,7 @@ func (f *Forwarder) filterFunc(namespace string) func(interface{}) bool {
 	}
 }
 
-func (f *Forwarder) leaseUpdated(obj interface{}) {
+func (f *leaseTracker) leaseUpdated(obj interface{}) {
 	l := obj.(*coordinationv1.Lease)
 	ns, n := l.Namespace, l.Name
 	ctx := context.Background()
@@ -194,7 +213,10 @@ func (f *Forwarder) leaseUpdated(obj interface{}) {
 
 	// Close existing connection if there's one. The target address won't
 	// be the same as the IP has changed.
-	f.shutdown(f.getProcessor(n))
+	if p := f.fwd.getProcessor(n); p != nil {
+		p.shutdown()
+	}
+
 	holder := *l.Spec.HolderIdentity
 	// The map should have the IP because of the operations in the filter when queueing.
 	ip, ok := f.id2ip[holder]
@@ -204,13 +226,23 @@ func (f *Forwarder) leaseUpdated(obj interface{}) {
 		f.logger.Warn("IP not found in cached map for ", holder)
 		return
 	}
-	f.setProcessor(n, f.createProcessor(ns, n, ip, holder))
 
 	if ip != f.selfIP {
+		f.fwd.setProcessor(n, newForwardProcessor(f.logger.With(zap.String("bucket", n)), n, holder,
+			fmt.Sprintf("ws://%s:%d", ip, autoscalerPort),
+			fmt.Sprintf("ws://%s.%s.%s", n, ns, svcURLSuffix)))
+
 		// Skip creating/updating Service and Endpoints if not the leader.
 		return
 	}
 
+	f.fwd.setProcessor(n, &localProcessor{
+		bkt:    n,
+		holder: holder,
+		logger: f.logger.With(zap.String("bucket", n)),
+		accept: f.accept,
+	})
+
 	if err := f.createService(ctx, ns, n); err != nil {
 		f.logger.Fatalf("Failed to create Service for Lease %s/%s: %v", ns, n, err)
 		return
@@ -224,7 +256,7 @@ func (f *Forwarder) leaseUpdated(obj interface{}) {
 	f.logger.Infof("Created Endpoints for Lease %s/%s", ns, n)
 }
 
-func (f *Forwarder) createService(ctx context.Context, ns, n string) error {
+func (f *leaseTracker) createService(ctx context.Context, ns, n string) error {
 	var lastErr error
 	if err := wait.PollImmediate(retryInterval, retryTimeout, func() (bool, error) {
 		_, lastErr = f.kc.CoreV1().Services(ns).Create(ctx, &corev1.Service{
@@ -257,7 +289,7 @@ func (f *Forwarder) createService(ctx context.Context, ns, n string) error {
 // createOrUpdateEndpoints creates an Endpoints object with the given namespace and
 // name, and the Forwarder.selfIP. If the Endpoints object already
 // exists, it will update the Endpoints with the Forwarder.selfIP.
-func (f *Forwarder) createOrUpdateEndpoints(ctx context.Context, ns, n string) error {
+func (f *leaseTracker) createOrUpdateEndpoints(ctx context.Context, ns, n string) error {
 	wantSubsets := []corev1.EndpointSubset{{
 		Addresses: []corev1.EndpointAddress{{
 			IP: f.selfIP,
@@ -334,21 +366,6 @@ func (f *Forwarder) setProcessor(bkt string, p bucketProcessor) {
 	f.processors[bkt] = p
 }
 
-func (f *Forwarder) createProcessor(ns, bkt, ip, holder string) bucketProcessor {
-	if ip == f.selfIP {
-		return &localProcessor{
-			bkt:    bkt,
-			holder: holder,
-			logger: f.logger.With(zap.String("bucket", bkt)),
-			accept: f.accept,
-		}
-	}
-
-	return newForwardProcessor(f.logger.With(zap.String("bucket", bkt)), bkt, holder,
-		fmt.Sprintf("ws://%s:%d", ip, autoscalerPort),
-		fmt.Sprintf("ws://%s.%s.%s", bkt, ns, svcURLSuffix))
-}
-
 // Process enqueues the given Stat for processing asynchronously.
 // It calls Forwarder.accept if the pod where this Forwarder is running is the owner
 // of the given StatMessage. Otherwise it forwards the given StatMessage to the right
@@ -403,12 +420,6 @@ func (f *Forwarder) maybeRetry(logger *zap.SugaredLogger, s stat) {
 	}()
 }
 
-func (f *Forwarder) shutdown(p bucketProcessor) {
-	if p != nil {
-		p.shutdown()
-	}
-}
-
 // Cancel is the function to call when terminating a Forwarder.
 func (f *Forwarder) Cancel() {
 	// Tell process go-runtine to stop.
@@ -417,7 +428,9 @@ func (f *Forwarder) Cancel() {
 	f.processorsLock.RLock()
 	defer f.processorsLock.RUnlock()
 	for _, p := range f.processors {
-		f.shutdown(p)
+		if p != nil {
+			p.shutdown()
+		}
 	}
 
 	f.processingWg.Wait()
diff --git a/pkg/autoscaler/statforwarder/processor.go b/pkg/autoscaler/statforwarder/processor.go
index 3f2ab18b4..358735d7e 100644
--- a/pkg/autoscaler/statforwarder/processor.go
+++ b/pkg/autoscaler/statforwarder/processor.go
@@ -74,8 +74,13 @@ type remoteProcessor struct {
 	// The name of the bucket
 	bkt string
 	// holder is the HolderIdentity of a Lease for a bucket.
-	holder   string
-	svcDNS   string
+	holder string
+
+	// addrs contains the list of addresses for the remote to try in the order to try them.
+	// The first address to establish a successful connection will be used until things reset
+	// at which point the order is re-evaluated.
+	addrs []string
+
 	connLock sync.RWMutex
 	// conn is the WebSocket connection to the holder pod.
 	conn *websocket.ManagedConnection
@@ -83,22 +88,12 @@ type remoteProcessor struct {
 
 var _ bucketProcessor = (*remoteProcessor)(nil)
 
-func newForwardProcessor(logger *zap.SugaredLogger, bkt, holder, podDNS, svcDNS string) *remoteProcessor {
-	// First try to connect via Pod IP address synchronously. If the connection can
-	// not be established within `establishTimeout`, we assume the pods can not be
-	// accessed by IP address. Then try to connect via Pod IP address synchronously.
-	logger.Info("Connecting to Autoscaler bucket at ", podDNS)
-	c, err := websocket.NewDurableSendingConnectionGuaranteed(podDNS, establishTimeout, logger)
-	if err != nil {
-		logger.Info("Autoscaler pods can't be accessed by IP address. Connecting to Autoscaler bucket at ", svcDNS)
-		c, _ = websocket.NewDurableSendingConnectionGuaranteed(svcDNS, establishTimeout, logger)
-	}
+func newForwardProcessor(logger *zap.SugaredLogger, bkt, holder string, addrs ...string) *remoteProcessor {
 	return &remoteProcessor{
 		logger: logger,
 		bkt:    bkt,
 		holder: holder,
-		conn:   c,
-		svcDNS: svcDNS,
+		addrs:  addrs,
 	}
 }
 
@@ -130,11 +125,17 @@ func (p *remoteProcessor) process(sm asmetrics.StatMessage) error {
 
 	c := p.getConn()
 	if c == nil {
-		c, err = websocket.NewDurableSendingConnectionGuaranteed(p.svcDNS, establishTimeout, p.logger)
+		for _, addr := range p.addrs {
+			c, err = websocket.NewDurableSendingConnectionGuaranteed(addr, establishTimeout, p.logger)
+			if err != nil {
+				continue
+			}
+			p.setConn(c)
+			break
+		}
 		if err != nil {
 			return err
 		}
-		p.setConn(c)
 	}
 
 	return c.SendRaw(gorillawebsocket.BinaryMessage, b)
diff --git a/pkg/reconciler/route/traffic/rollout.go b/pkg/reconciler/route/traffic/rollout.go
index 333b34be9..5a1432fd0 100644
--- a/pkg/reconciler/route/traffic/rollout.go
+++ b/pkg/reconciler/route/traffic/rollout.go
@@ -65,18 +65,17 @@ type ConfigurationRollout struct {
 // RolloutParams contains the timing and sizing parameters for the
 // ConfigurationRollout.
 type RolloutParams struct {
-	// StartTime is the Unix timestamp by when (+/- reconcile precision)
+	// StartTime is the Unix timestamp in ns by when (+/- reconcile precision)
 	// the Rollout has started.
 	// This is required to compute step time and deadline.
 	StartTime int `json:"starttime,omitempty"`
 
-	// NextStepTime is the Unix timestamp when the next
+	// NextStepTime is the Unix timestamp in ns when the next
 	// rollout step should performed.
 	NextStepTime int `json:"nextStepTime,omitempty"`
 
-	// StepDuration is a rounded up number of seconds how long it took
-	// for ingress to successfully move first 1% of traffic to the new revision.
-	// Note, that his number does not include any coldstart, etc timing.
+	// StepDuration is the number of nanoseconds between two successive steps
+	// of rollout.
 	StepDuration int `json:"stepDuration,omitempty"`
 
 	// How much traffic to move in a single step.
@@ -411,13 +410,11 @@ func (cur *ConfigurationRollout) computeProperties(nowTS, minStepSec, durationSe
 	stepSize := math.Floor((pf - 1) / numSteps)
 
 	// The time we sleep between the steps.
-	// We round up here since it's better to roll slightly longer,
-	// than slightly shorter.
-	stepDuration := math.Ceil(durationSecs / numSteps)
+	stepDuration := durationSecs / numSteps * float64(time.Second)
 
 	cur.StepParams.StepDuration = int(stepDuration)
 	cur.StepParams.StepSize = int(stepSize)
-	cur.StepParams.NextStepTime = int(nowTS + stepDuration*float64(time.Second))
+	cur.StepParams.NextStepTime = int(nowTS + stepDuration)
 }
 
 // sortRollout sorts the rollout based on tag so it's consistent


Test patch:
diff --git a/.github/workflows/knative-go-test.yaml b/.github/workflows/knative-go-test.yaml
index 9a77276c1..965df1e74 100644
--- a/.github/workflows/knative-go-test.yaml
+++ b/.github/workflows/knative-go-test.yaml
@@ -20,10 +20,10 @@ name: Test
 on:
 
   push:
-    branches: [ 'master' ]
+    branches: [ 'main', 'master' ]
 
   pull_request:
-    branches: [ 'master', 'release-*' ]
+    branches: [ 'main', 'master', 'release-*' ]
 
 jobs:
 
diff --git a/pkg/autoscaler/statforwarder/processor_test.go b/pkg/autoscaler/statforwarder/processor_test.go
index 211e3f453..8fe166116 100644
--- a/pkg/autoscaler/statforwarder/processor_test.go
+++ b/pkg/autoscaler/statforwarder/processor_test.go
@@ -79,7 +79,7 @@ func TestProcessorForwardingViaSvcRetry(t *testing.T) {
 	}
 
 	// Change to a working URL
-	p.svcDNS = "ws" + strings.TrimPrefix(s.URL, "http")
+	p.addrs = []string{"ws" + strings.TrimPrefix(s.URL, "http")}
 	p.process(stat1)
 
 	select {
diff --git a/pkg/reconciler/route/table_test.go b/pkg/reconciler/route/table_test.go
index b8ca78975..2a935087f 100644
--- a/pkg/reconciler/route/table_test.go
+++ b/pkg/reconciler/route/table_test.go
@@ -435,10 +435,10 @@ func TestReconcile(t *testing.T) {
 					RevisionName: "config-00001", Percent: 1,
 				}}, fakeCurTime.Add(-3*time.Second),
 					func(r *traffic.Rollout) {
-						// Step duration is 3s (now - (now-3s)).
-						r.Configurations[0].StepParams.StepDuration = 3
-						// 120 / 3 = 40 steps. floor(100/40) = 2.
-						r.Configurations[0].StepParams.StepSize = 2
+						numSteps := 120 / 3 // 40
+						// Step duration is 3s (duration / numSteps = 120/40)
+						r.Configurations[0].StepParams.StepDuration = int(time.Second) * 120 / numSteps // 3s in ns.
+						r.Configurations[0].StepParams.StepSize = (100 - 1) / numSteps                  // 2
 						// StepDuration is 3, and so next step is `now` + 3.
 						r.Configurations[0].StepParams.NextStepTime = int(fakeCurTime.Add(3 * time.Second).UnixNano())
 					},
diff --git a/pkg/reconciler/route/traffic/rollout_test.go b/pkg/reconciler/route/traffic/rollout_test.go
index d0df53810..f8cdc88e1 100644
--- a/pkg/reconciler/route/traffic/rollout_test.go
+++ b/pkg/reconciler/route/traffic/rollout_test.go
@@ -876,16 +876,16 @@ func TestObserveReady(t *testing.T) {
 			Percent:           100,
 			StepParams: RolloutParams{
 				StartTime:    200620092020,
-				StepDuration: 2,
+				StepDuration: 1212121212, // 120/99
 				StepSize:     1,
-				NextStepTime: now + int(2*time.Second),
+				NextStepTime: now + 1212121212,
 			},
 		}, {
 			ConfigurationName: "step-begin > 1s",
 			Percent:           100,
 			StepParams: RolloutParams{
 				StartTime:    oldenDays,
-				StepDuration: 3,
+				StepDuration: 3 * int(time.Second),
 				StepSize:     100 / 40,
 				NextStepTime: now + 3*int(time.Second),
 			},
@@ -894,7 +894,7 @@ func TestObserveReady(t *testing.T) {
 			Percent:           50,
 			StepParams: RolloutParams{
 				StartTime:    oldenDays,
-				StepDuration: 3,
+				StepDuration: 3 * int(time.Second),
 				StepSize:     50 / 40,
 				NextStepTime: now + 3*int(time.Second),
 			},
diff --git a/third_party/istio-latest/net-istio.yaml b/third_party/istio-latest/net-istio.yaml
index 5412614a3..ace60a975 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 e87b1c73e380aa7798c73cd0a38ec32734af353a
+# Generated when HEAD was b7fe1983bd4d1f1764f7dd83e93157020b0a1dbc
 #
 # 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: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     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: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -87,7 +87,7 @@ metadata:
   name: knative-local-gateway
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -106,7 +106,7 @@ metadata:
   name: knative-local-gateway
   namespace: istio-system
   labels:
-    serving.knative.dev/release: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     networking.knative.dev/ingress-provider: istio
 spec:
   type: ClusterIP
@@ -126,7 +126,7 @@ metadata:
   name: "webhook"
   namespace: "knative-serving"
   labels:
-    serving.knative.dev/release: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -141,7 +141,7 @@ metadata:
   name: "istio-webhook"
   namespace: "knative-serving"
   labels:
-    serving.knative.dev/release: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -170,7 +170,7 @@ kind: MutatingWebhookConfiguration
 metadata:
   name: webhook.istio.networking.internal.knative.dev
   labels:
-    serving.knative.dev/release: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     networking.knative.dev/ingress-provider: istio
 webhooks:
   - admissionReviewVersions:
@@ -206,7 +206,7 @@ kind: ValidatingWebhookConfiguration
 metadata:
   name: config.webhook.istio.networking.internal.knative.dev
   labels:
-    serving.knative.dev/release: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     networking.knative.dev/ingress-provider: istio
 webhooks:
   - admissionReviewVersions:
@@ -244,7 +244,7 @@ metadata:
   name: istio-webhook-certs
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     networking.knative.dev/ingress-provider: istio
 
 ---
@@ -268,7 +268,7 @@ metadata:
   name: config-istio
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     networking.knative.dev/ingress-provider: istio
 data:
   _example: |
@@ -338,7 +338,7 @@ metadata:
   name: networking-istio
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -354,14 +354,14 @@ spec:
         sidecar.istio.io/inject: "false"
       labels:
         app: networking-istio
-        serving.knative.dev/release: "v20201208-e87b1c73"
+        serving.knative.dev/release: "v20201209-b7fe1983"
     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:39fde178bac6f7994fe76871fae2c294bce5971627d3874be85176b421749233
+          image: gcr.io/knative-nightly/knative.dev/net-istio/cmd/controller@sha256:d4481e081c6ec6862e0a7cd7cd4c40f5f2021e5488a5e3589063c82be51f8abd
           resources:
             requests:
               cpu: 30m
@@ -418,7 +418,7 @@ metadata:
   name: istio-webhook
   namespace: knative-serving
   labels:
-    serving.knative.dev/release: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     networking.knative.dev/ingress-provider: istio
 spec:
   selector:
@@ -432,14 +432,14 @@ spec:
       labels:
         app: istio-webhook
         role: istio-webhook
-        serving.knative.dev/release: "v20201208-e87b1c73"
+        serving.knative.dev/release: "v20201209-b7fe1983"
     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:38dd61407135120cb3a452914e566e19bdc2fd90499ed1a9f32b824efa605710
+          image: gcr.io/knative-nightly/knative.dev/net-istio/cmd/webhook@sha256:3e873c4895fb1ae766f5d35e9a699fd9612fecca232029f2d453ffc17c8f608f
           resources:
             requests:
               cpu: 20m
@@ -493,7 +493,7 @@ metadata:
   namespace: knative-serving
   labels:
     role: istio-webhook
-    serving.knative.dev/release: "v20201208-e87b1c73"
+    serving.knative.dev/release: "v20201209-b7fe1983"
     networking.knative.dev/ingress-provider: istio
 spec:
   ports: