Repository: knative/serving
Pull request #8612 (2020-07-13 14:58:33)

Handle InitialScale != 1 when creating multiScaler and some tidy-ups
<!--
Request Prow to automatically lint any go code in this PR:

/lint
-->

Now InitialScale has landed, take it into account when calculating initial EBC in multiscaler. 

Also:
 - Tidy up some comments.
 - Fix up test assertion messages.
 - Move mutexes above the things they're guarding.
 - Use %d and %0.3f for printing numbers, consistently with existing Debug statement at start of same method.

/assign @vagababov @markusthoemmes @taragu 

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

This pull-request has been approved by: *<a href="https://github.com/knative/serving/pull/8612#" title="Author self-approved">julz</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **vagababov**
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:

- **[pkg/autoscaler/OWNERS](https://github.com/knative/serving/blob/master/pkg/autoscaler/OWNERS)**
- **[pkg/reconciler/autoscaling/OWNERS](https://github.com/knative/serving/blob/master/pkg/reconciler/autoscaling/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"]} -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-serving-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/reconciler/autoscaling/kpa/scaler.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_serving/8612/pull-knative-serving-go-coverage/1282691018801549319/artifacts/line-cov.html#file2) | 94.2% | 93.4% | -0.8


Patch:
diff --git a/go.mod b/go.mod
index f4917bfba..84539a887 100644
--- a/go.mod
+++ b/go.mod
@@ -40,8 +40,8 @@ require (
 	k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29
 	knative.dev/caching v0.0.0-20200707200344-95a2aaeace0f
 	knative.dev/networking v0.0.0-20200707203944-725ec013d8a2
-	knative.dev/pkg v0.0.0-20200710163519-a0cb3d689532
-	knative.dev/test-infra v0.0.0-20200710160019-5b9732bc24f7
+	knative.dev/pkg v0.0.0-20200713031612-b09a159e12c9
+	knative.dev/test-infra v0.0.0-20200713045417-850e4e37918d
 )
 
 replace (
diff --git a/go.sum b/go.sum
index 531a2c743..1656e86c4 100644
--- a/go.sum
+++ b/go.sum
@@ -1634,11 +1634,13 @@ knative.dev/networking v0.0.0-20200707203944-725ec013d8a2 h1:Co9j0Q4ZJxwkzVFKUc6
 knative.dev/networking v0.0.0-20200707203944-725ec013d8a2/go.mod h1:e1NL29AarTcgaR240oc4GUzqHtTfTu62JNrUHN3kIG0=
 knative.dev/pkg v0.0.0-20200207155214-fef852970f43/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q=
 knative.dev/pkg v0.0.0-20200707190344-0a8314b44495/go.mod h1:AqAJV6rYi8IGikDjJ/9ZQd9qKdkXVlesVnVjwx62YB8=
-knative.dev/pkg v0.0.0-20200710163519-a0cb3d689532 h1:1dSRUstjueNPF06yxGlmeEzzMZpQJnlddA4VehDuTp8=
-knative.dev/pkg v0.0.0-20200710163519-a0cb3d689532/go.mod h1:AqAJV6rYi8IGikDjJ/9ZQd9qKdkXVlesVnVjwx62YB8=
+knative.dev/pkg v0.0.0-20200713031612-b09a159e12c9 h1:YSapebbZZbpH31YMSF0Egt7+IDi/og4S574eWqEXReo=
+knative.dev/pkg v0.0.0-20200713031612-b09a159e12c9/go.mod h1:aWPsPIHISvZetAm/2pnz+v6Ro5EYaX704Z/Zd9rTZ4M=
 knative.dev/test-infra v0.0.0-20200707183444-aed09e56ddc7/go.mod h1:RjYAhXnZqeHw9+B0zsbqSPlae0lCvjekO/nw5ZMpLCs=
 knative.dev/test-infra v0.0.0-20200710160019-5b9732bc24f7 h1:fAl3pG2I323tie8kuuNlB88B7RB8WJtCrsXIKuNh1U8=
 knative.dev/test-infra v0.0.0-20200710160019-5b9732bc24f7/go.mod h1:vtT6dLs/iNj8pKcfag8CSVqHKNMgyCFtU/g1pV7Bovs=
+knative.dev/test-infra v0.0.0-20200713045417-850e4e37918d h1:Q3LrAYSi+Ii2yZVUiA5Y3Jr4TCU6g/XN9ClVosejpJk=
+knative.dev/test-infra v0.0.0-20200713045417-850e4e37918d/go.mod h1:vtT6dLs/iNj8pKcfag8CSVqHKNMgyCFtU/g1pV7Bovs=
 modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
 modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
 modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
diff --git a/pkg/autoscaler/scaling/autoscaler.go b/pkg/autoscaler/scaling/autoscaler.go
index 94d49caa8..4cd240a06 100644
--- a/pkg/autoscaler/scaling/autoscaler.go
+++ b/pkg/autoscaler/scaling/autoscaler.go
@@ -242,7 +242,7 @@ func (a *autoscaler) Scale(ctx context.Context, now time.Time) ScaleResult {
 
 	// Here we compute two numbers: excess burst capacity and number of activators
 	// for subsetting.
-	// - the excess burst capacity based on panic value, since we don't want to
+	// - the excess burst capacity is based on panic value, since we don't want to
 	//   be making knee-jerk decisions about Activator in the request path.
 	//   Negative EBC means that the deployment does not have enough capacity to serve
 	//   the desired burst off hand.
@@ -252,7 +252,7 @@ func (a *autoscaler) Scale(ctx context.Context, now time.Time) ScaleResult {
 	//   the default number.
 	//   if tbc > 0, then revision gets number of activators to support total capacity and
 	//   tbc additional units.
-	//   if tbc==-1, then revision gets to number of activators to support total capacity.
+	//   if tbc==-1, then revision gets the number of activators needed to support total capacity.
 	//   With default target utilization of 0.7, we're overprovisioning number of needed activators
 	//   by rate of 1/0.7=1.42.
 	excessBCF := -1.
@@ -260,7 +260,7 @@ func (a *autoscaler) Scale(ctx context.Context, now time.Time) ScaleResult {
 	switch {
 	case a.deciderSpec.TargetBurstCapacity == 0:
 		excessBCF = 0
-		// numAct stays 1, only needed to scale from 0.
+		// numAct stays at MinActivators, only needed to scale from 0.
 	case a.deciderSpec.TargetBurstCapacity > 0:
 		// Extra float64 cast disables fused multiply-subtract to force identical behavior on
 		// all platforms. See floating point section in https://golang.org/ref/spec#Operators.
@@ -272,7 +272,7 @@ func (a *autoscaler) Scale(ctx context.Context, now time.Time) ScaleResult {
 		numAct = int32(math.Max(MinActivators,
 			math.Ceil(float64(originalReadyPodsCount)*a.deciderSpec.TotalValue/a.deciderSpec.ActivatorCapacity)))
 	}
-	logger.Debugf("PodCount=%v Total1PodCapacity=%v ObsStableValue=%v ObsPanicValue=%v TargetBC=%v ExcessBC=%v NumActivators=%d",
+	logger.Debugf("PodCount=%d Total1PodCapacity=%0.3f ObsStableValue=%0.3f ObsPanicValue=%0.3f TargetBC=%0.3f ExcessBC=%0.3f NumActivators=%d",
 		originalReadyPodsCount, a.deciderSpec.TotalValue, observedStableValue,
 		observedPanicValue, a.deciderSpec.TargetBurstCapacity, excessBCF, numAct)
 
diff --git a/pkg/autoscaler/scaling/multiscaler.go b/pkg/autoscaler/scaling/multiscaler.go
index 638b94ae6..0e06342a2 100644
--- a/pkg/autoscaler/scaling/multiscaler.go
+++ b/pkg/autoscaler/scaling/multiscaler.go
@@ -45,7 +45,7 @@ type Decider struct {
 	Status DeciderStatus
 }
 
-// DeciderSpec is the parameters in which the Revision should scaled.
+// DeciderSpec is the parameters by which the Revision should be scaled.
 type DeciderSpec struct {
 	MaxScaleUpRate   float64
 	MaxScaleDownRate float64
@@ -159,7 +159,7 @@ func (sr *scalerRunner) updateLatestScale(sRes ScaleResult) bool {
 		ret = true
 	}
 
-	// If sign has changed -- then we have to update KPA
+	// If sign has changed -- then we have to update KPA.
 	ret = ret || !sameSign(sr.decider.Status.ExcessBurstCapacity, sRes.ExcessBurstCapacity)
 
 	// Update with the latest calculation anyway.
@@ -167,18 +167,19 @@ func (sr *scalerRunner) updateLatestScale(sRes ScaleResult) bool {
 	return ret
 }
 
-// MultiScaler maintains a collection of Uniscalers.
+// MultiScaler maintains a collection of UniScalers.
 type MultiScaler struct {
-	scalers       map[types.NamespacedName]*scalerRunner
-	scalersMutex  sync.RWMutex
+	scalersMutex sync.RWMutex
+	scalers      map[types.NamespacedName]*scalerRunner
+
 	scalersStopCh <-chan struct{}
 
 	uniScalerFactory UniScalerFactory
 
 	logger *zap.SugaredLogger
 
-	watcher      func(types.NamespacedName)
 	watcherMutex sync.RWMutex
+	watcher      func(types.NamespacedName)
 
 	tickProvider func(time.Duration) *time.Ticker
 }
@@ -234,7 +235,7 @@ func (m *MultiScaler) Create(ctx context.Context, decider *Decider) (*Decider, e
 	return scaler.decider, nil
 }
 
-// Update applied the desired DeciderSpec to a currently running Decider.
+// Update applies the desired DeciderSpec to a currently running Decider.
 func (m *MultiScaler) Update(_ context.Context, decider *Decider) (*Decider, error) {
 	key := types.NamespacedName{Namespace: decider.Namespace, Name: decider.Name}
 	m.scalersMutex.Lock()
@@ -324,10 +325,9 @@ func (m *MultiScaler) createScaler(ctx context.Context, decider *Decider) (*scal
 	case -1, 0:
 		d.Status.ExcessBurstCapacity = int32(tbc)
 	default:
-		// If TBC > Target * InitialScale (currently 1), then we know initial
+		// If TBC > Target * InitialScale, then we know initial
 		// scale won't be enough to cover TBC and we'll be behind activator.
-		// TODO(autoscale-wg): fix this when we switch to non "1" initial scale.
-		d.Status.ExcessBurstCapacity = int32(1*d.Spec.TotalValue - tbc)
+		d.Status.ExcessBurstCapacity = int32(float64(d.Spec.InitialScale)*d.Spec.TotalValue - tbc)
 	}
 
 	m.runScalerTicker(ctx, runner)
diff --git a/pkg/reconciler/autoscaling/kpa/scaler.go b/pkg/reconciler/autoscaling/kpa/scaler.go
index afc5a81a2..4b75a7dac 100644
--- a/pkg/reconciler/autoscaling/kpa/scaler.go
+++ b/pkg/reconciler/autoscaling/kpa/scaler.go
@@ -319,7 +319,7 @@ func (ks *scaler) scale(ctx context.Context, pa *pav1alpha1.PodAutoscaler, sks *
 	min, max := pa.ScaleBounds()
 	initialScale := kparesources.GetInitialScale(config.FromContext(ctx).Autoscaler, pa)
 	if initialScale > 1 {
-		// Ignore initial scale if minScale >= initialScale
+		// Ignore initial scale if minScale >= initialScale.
 		if min < initialScale {
 			logger.Debugf("Adjusting min to meet the initial scale: %d -> %d", min, initialScale)
 		}
diff --git a/vendor/knative.dev/pkg/apis/duck/ducktypes/ducktypes.go b/vendor/knative.dev/pkg/apis/duck/ducktypes/ducktypes.go
new file mode 100644
index 000000000..6f99e0882
--- /dev/null
+++ b/vendor/knative.dev/pkg/apis/duck/ducktypes/ducktypes.go
@@ -0,0 +1,43 @@
+/*
+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 ducktypes
+
+import (
+	"knative.dev/pkg/apis"
+)
+
+// Implementable is implemented by the Fooable duck type that consumers
+// are expected to embed as a `.status.fooable` field.
+type Implementable interface {
+	// GetFullType returns an instance of a full resource wrapping
+	// an instance of this Implementable that can populate its fields
+	// to verify json roundtripping.
+	GetFullType() Populatable
+}
+
+// Populatable is implemented by a skeleton resource wrapping an Implementable
+// duck type.  It will generally have TypeMeta, ObjectMeta, and a Status field
+// wrapping a Fooable field.
+type Populatable interface {
+	apis.Listable
+
+	// Populate fills in all possible fields, so that we can verify that
+	// they roundtrip properly through JSON.
+	Populate()
+}
+
+const GroupName = "duck.knative.dev"
diff --git a/vendor/knative.dev/pkg/apis/duck/register.go b/vendor/knative.dev/pkg/apis/duck/register.go
index d84cd49d1..93cf55d7f 100644
--- a/vendor/knative.dev/pkg/apis/duck/register.go
+++ b/vendor/knative.dev/pkg/apis/duck/register.go
@@ -16,8 +16,12 @@ limitations under the License.
 
 package duck
 
+import (
+	"knative.dev/pkg/apis/duck/ducktypes"
+)
+
 const (
-	GroupName = "duck.knative.dev"
+	GroupName = ducktypes.GroupName
 
 	// AddressableDuckVersionLabel is the label we use to declare
 	// that a type conforms to the Addressable duck type.
diff --git a/vendor/knative.dev/pkg/apis/duck/v1/addressable_types.go b/vendor/knative.dev/pkg/apis/duck/v1/addressable_types.go
index 579e6976e..3cfcac331 100644
--- a/vendor/knative.dev/pkg/apis/duck/v1/addressable_types.go
+++ b/vendor/knative.dev/pkg/apis/duck/v1/addressable_types.go
@@ -24,7 +24,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 
 	"knative.dev/pkg/apis"
-	"knative.dev/pkg/apis/duck"
+	"knative.dev/pkg/apis/duck/ducktypes"
 )
 
 // +genduck
@@ -40,8 +40,6 @@ type Addressable struct {
 }
 
 var (
-	// Addressable is an Implementable "duck type".
-	_ duck.Implementable = (*Addressable)(nil)
 	// Addressable is a Convertible type.
 	_ apis.Convertible = (*Addressable)(nil)
 )
@@ -66,13 +64,11 @@ type AddressStatus struct {
 }
 
 var (
-	// Verify AddressableType resources meet duck contracts.
-	_ duck.Populatable = (*AddressableType)(nil)
-	_ apis.Listable    = (*AddressableType)(nil)
+	_ apis.Listable = (*AddressableType)(nil)
 )
 
 // GetFullType implements duck.Implementable
-func (*Addressable) GetFullType() duck.Populatable {
+func (*Addressable) GetFullType() ducktypes.Populatable {
 	return &AddressableType{}
 }
 
diff --git a/vendor/knative.dev/pkg/apis/duck/v1/podspec_types.go b/vendor/knative.dev/pkg/apis/duck/v1/podspec_types.go
index 0dd9ec338..92ed7f1c4 100644
--- a/vendor/knative.dev/pkg/apis/duck/v1/podspec_types.go
+++ b/vendor/knative.dev/pkg/apis/duck/v1/podspec_types.go
@@ -22,7 +22,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 
 	"knative.dev/pkg/apis"
-	"knative.dev/pkg/apis/duck"
+	"knative.dev/pkg/apis/duck/ducktypes"
 )
 
 // +genduck
@@ -50,13 +50,11 @@ type WithPodSpec struct {
 // Assert that we implement the interfaces necessary to
 // use duck.VerifyType.
 var (
-	_ duck.Populatable   = (*WithPod)(nil)
-	_ duck.Implementable = (*PodSpecable)(nil)
-	_ apis.Listable      = (*WithPod)(nil)
+	_ apis.Listable = (*WithPod)(nil)
 )
 
 // GetFullType implements duck.Implementable
-func (*PodSpecable) GetFullType() duck.Populatable {
+func (*PodSpecable) GetFullType() ducktypes.Populatable {
 	return &WithPod{}
 }
 
diff --git a/vendor/knative.dev/pkg/apis/duck/v1/register.go b/vendor/knative.dev/pkg/apis/duck/v1/register.go
index e3af46d6f..69bb11578 100644
--- a/vendor/knative.dev/pkg/apis/duck/v1/register.go
+++ b/vendor/knative.dev/pkg/apis/duck/v1/register.go
@@ -20,11 +20,12 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
-	"knative.dev/pkg/apis/duck"
+
+	"knative.dev/pkg/apis/duck/ducktypes"
 )
 
 // SchemeGroupVersion is group version used to register these objects
-var SchemeGroupVersion = schema.GroupVersion{Group: duck.GroupName, Version: "v1"}
+var SchemeGroupVersion = schema.GroupVersion{Group: ducktypes.GroupName, Version: "v1"}
 
 // Kind takes an unqualified kind and returns back a Group qualified GroupKind
 func Kind(kind string) schema.GroupKind {
diff --git a/vendor/knative.dev/pkg/apis/duck/v1/source_types.go b/vendor/knative.dev/pkg/apis/duck/v1/source_types.go
index 3b378ca3d..03c4d40b9 100644
--- a/vendor/knative.dev/pkg/apis/duck/v1/source_types.go
+++ b/vendor/knative.dev/pkg/apis/duck/v1/source_types.go
@@ -24,12 +24,9 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 
 	"knative.dev/pkg/apis"
-	"knative.dev/pkg/apis/duck"
+	"knative.dev/pkg/apis/duck/ducktypes"
 )
 
-// Source is an Implementable "duck type".
-var _ duck.Implementable = (*Source)(nil)
-
 // +genduck
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
@@ -113,9 +110,7 @@ func (ss *SourceStatus) IsReady() bool {
 }
 
 var (
-	// Verify Source resources meet duck contracts.
-	_ duck.Populatable = (*Source)(nil)
-	_ apis.Listable    = (*Source)(nil)
+	_ apis.Listable = (*Source)(nil)
 )
 
 const (
@@ -125,7 +120,7 @@ const (
 )
 
 // GetFullType implements duck.Implementable
-func (*Source) GetFullType() duck.Populatable {
+func (*Source) GetFullType() ducktypes.Populatable {
 	return &Source{}
 }
 
diff --git a/vendor/knative.dev/pkg/apis/duck/v1/status_types.go b/vendor/knative.dev/pkg/apis/duck/v1/status_types.go
index aab246b1f..f25212e57 100644
--- a/vendor/knative.dev/pkg/apis/duck/v1/status_types.go
+++ b/vendor/knative.dev/pkg/apis/duck/v1/status_types.go
@@ -20,7 +20,7 @@ import (
 	"context"
 
 	"knative.dev/pkg/apis"
-	"knative.dev/pkg/apis/duck"
+	"knative.dev/pkg/apis/duck/ducktypes"
 	"knative.dev/pkg/kmeta"
 )
 
@@ -29,9 +29,6 @@ import (
 // Conditions is a simple wrapper around apis.Conditions to implement duck.Implementable.
 type Conditions apis.Conditions
 
-// Conditions is an Implementable "duck type".
-var _ duck.Implementable = (*Conditions)(nil)
-
 // Status shows how we expect folks to embed Conditions in
 // their Status field.
 // WARNING: Adding fields to this struct will add them to all Knative resources.
@@ -66,14 +63,11 @@ func (s *Status) SetConditions(c apis.Conditions) {
 	s.Conditions = Conditions(c)
 }
 
-// In order for Conditions to be Implementable, KResource must be Populatable.
-var _ duck.Populatable = (*KResource)(nil)
-
 // Ensure KResource satisfies apis.Listable
 var _ apis.Listable = (*KResource)(nil)
 
 // GetFullType implements duck.Implementable
-func (*Conditions) GetFullType() duck.Populatable {
+func (*Conditions) GetFullType() ducktypes.Populatable {
 	return &KResource{}
 }
 
diff --git a/vendor/knative.dev/pkg/apis/duck/v1beta1/addressable_types.go b/vendor/knative.dev/pkg/apis/duck/v1beta1/addressable_types.go
index 3e3f82867..46cfed160 100644
--- a/vendor/knative.dev/pkg/apis/duck/v1beta1/addressable_types.go
+++ b/vendor/knative.dev/pkg/apis/duck/v1beta1/addressable_types.go
@@ -24,8 +24,8 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 
 	"knative.dev/pkg/apis"
-	"knative.dev/pkg/apis/duck"
-	v1 "knative.dev/pkg/apis/duck/v1"
+	"knative.dev/pkg/apis/duck/ducktypes"
+	"knative.dev/pkg/apis/duck/v1"
 )
 
 // +genduck
@@ -41,8 +41,6 @@ type Addressable struct {
 }
 
 var (
-	// Addressable is an Implementable "duck type".
-	_ duck.Implementable = (*Addressable)(nil)
 	// Addressable is a Convertible type.
 	_ apis.Convertible = (*Addressable)(nil)
 )
@@ -67,13 +65,11 @@ type AddressStatus struct {
 }
 
 var (
-	// Verify AddressableType resources meet duck contracts.
-	_ duck.Populatable = (*AddressableType)(nil)
-	_ apis.Listable    = (*AddressableType)(nil)
+	_ apis.Listable = (*AddressableType)(nil)
 )
 
 // GetFullType implements duck.Implementable
-func (*Addressable) GetFullType() duck.Populatable {
+func (*Addressable) GetFullType() ducktypes.Populatable {
 	return &AddressableType{}
 }
 
diff --git a/vendor/knative.dev/pkg/apis/duck/v1beta1/register.go b/vendor/knative.dev/pkg/apis/duck/v1beta1/register.go
index ca8388ad4..324639c06 100644
--- a/vendor/knative.dev/pkg/apis/duck/v1beta1/register.go
+++ b/vendor/knative.dev/pkg/apis/duck/v1beta1/register.go
@@ -20,11 +20,12 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
-	"knative.dev/pkg/apis/duck"
+
+	"knative.dev/pkg/apis/duck/ducktypes"
 )
 
 // SchemeGroupVersion is group version used to register these objects
-var SchemeGroupVersion = schema.GroupVersion{Group: duck.GroupName, Version: "v1beta1"}
+var SchemeGroupVersion = schema.GroupVersion{Group: ducktypes.GroupName, Version: "v1beta1"}
 
 // Kind takes an unqualified kind and returns back a Group qualified GroupKind
 func Kind(kind string) schema.GroupKind {
diff --git a/vendor/knative.dev/pkg/apis/duck/v1beta1/source_types.go b/vendor/knative.dev/pkg/apis/duck/v1beta1/source_types.go
index 5853a1ae4..1a9be0d6c 100644
--- a/vendor/knative.dev/pkg/apis/duck/v1beta1/source_types.go
+++ b/vendor/knative.dev/pkg/apis/duck/v1beta1/source_types.go
@@ -24,12 +24,9 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 
 	"knative.dev/pkg/apis"
-	"knative.dev/pkg/apis/duck"
+	"knative.dev/pkg/apis/duck/ducktypes"
 )
 
-// Source is an Implementable "duck type".
-var _ duck.Implementable = (*Source)(nil)
-
 // +genduck
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
@@ -98,9 +95,7 @@ func (ss *SourceStatus) IsReady() bool {
 }
 
 var (
-	// Verify Source resources meet duck contracts.
-	_ duck.Populatable = (*Source)(nil)
-	_ apis.Listable    = (*Source)(nil)
+	_ apis.Listable = (*Source)(nil)
 )
 
 const (
@@ -110,7 +105,7 @@ const (
 )
 
 // GetFullType implements duck.Implementable
-func (*Source) GetFullType() duck.Populatable {
+func (*Source) GetFullType() ducktypes.Populatable {
 	return &Source{}
 }
 
diff --git a/vendor/knative.dev/pkg/apis/duck/v1beta1/status_types.go b/vendor/knative.dev/pkg/apis/duck/v1beta1/status_types.go
index 107592e85..8054c53be 100644
--- a/vendor/knative.dev/pkg/apis/duck/v1beta1/status_types.go
+++ b/vendor/knative.dev/pkg/apis/duck/v1beta1/status_types.go
@@ -25,7 +25,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 
 	"knative.dev/pkg/apis"
-	"knative.dev/pkg/apis/duck"
+	"knative.dev/pkg/apis/duck/ducktypes"
 	"knative.dev/pkg/kmeta"
 )
 
@@ -34,9 +34,6 @@ import (
 // Conditions is a simple wrapper around apis.Conditions to implement duck.Implementable.
 type Conditions apis.Conditions
 
-// Conditions is an Implementable "duck type".
-var _ duck.Implementable = (*Conditions)(nil)
-
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 
 // KResource is a skeleton type wrapping Conditions in the manner we expect
@@ -84,14 +81,11 @@ func (s *Status) SetConditions(c apis.Conditions) {
 	s.Conditions = Conditions(c)
 }
 
-// In order for Conditions to be Implementable, KResource must be Populatable.
-var _ duck.Populatable = (*KResource)(nil)
-
 // Ensure KResource satisfies apis.Listable
 var _ apis.Listable = (*KResource)(nil)
 
 // GetFullType implements duck.Implementable
-func (*Conditions) GetFullType() duck.Populatable {
+func (*Conditions) GetFullType() ducktypes.Populatable {
 	return &KResource{}
 }
 
diff --git a/vendor/knative.dev/pkg/apis/duck/verify.go b/vendor/knative.dev/pkg/apis/duck/verify.go
index 3f42330ff..cbcc51117 100644
--- a/vendor/knative.dev/pkg/apis/duck/verify.go
+++ b/vendor/knative.dev/pkg/apis/duck/verify.go
@@ -20,29 +20,12 @@ import (
 	"encoding/json"
 	"fmt"
 
-	"knative.dev/pkg/apis"
+	"knative.dev/pkg/apis/duck/ducktypes"
 	"knative.dev/pkg/kmp"
 )
 
-// Implementable is implemented by the Fooable duck type that consumers
-// are expected to embed as a `.status.fooable` field.
-type Implementable interface {
-	// GetFullType returns an instance of a full resource wrapping
-	// an instance of this Implementable that can populate its fields
-	// to verify json roundtripping.
-	GetFullType() Populatable
-}
-
-// Populatable is implemented by a skeleton resource wrapping an Implementable
-// duck type.  It will generally have TypeMeta, ObjectMeta, and a Status field
-// wrapping a Fooable field.
-type Populatable interface {
-	apis.Listable
-
-	// Populate fills in all possible fields, so that we can verify that
-	// they roundtrip properly through JSON.
-	Populate()
-}
+type Implementable = ducktypes.Implementable
+type Populatable = ducktypes.Populatable
 
 // VerifyType verifies that a particular concrete resource properly implements
 // the provided Implementable duck type.  It is expected that under the resource
diff --git a/vendor/knative.dev/pkg/hash/bucketer.go b/vendor/knative.dev/pkg/hash/bucketer.go
new file mode 100644
index 000000000..45f1100ab
--- /dev/null
+++ b/vendor/knative.dev/pkg/hash/bucketer.go
@@ -0,0 +1,112 @@
+/*
+Copyright 2020 The Knative Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// This file contains the utilities to make bucketing decisions.
+
+package hash
+
+import (
+	"sync"
+
+	lru "github.com/hashicorp/golang-lru"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/sets"
+	"knative.dev/pkg/reconciler"
+)
+
+var _ reconciler.Bucket = (*Bucket)(nil)
+
+// BucketSet answers to what bucket does key X belong in a
+// consistent manner (consistent as in consistent hashing).
+type BucketSet struct {
+	// Stores the cached lookups. cache is internally thread safe.
+	cache *lru.Cache
+
+	// mu guards buckets.
+	mu sync.RWMutex
+	// All the bucket names. Needed for building hash universe.
+	// `name` must be in this set.
+	buckets sets.String
+}
+
+// Bucket implements reconciler.Bucket and wraps around BuketSet
+// for bucketing functions.
+type Bucket struct {
+	name    string
+	buckets *BucketSet
+}
+
+// Scientifically inferred preferred cache size.
+const cacheSize = 4096
+
+func newCache() *lru.Cache {
+	c, _ := lru.New(cacheSize)
+	return c
+}
+
+// NewBucketSet creates a new bucket set with the given universe
+// of bucket names.
+func NewBucketSet(bucketList sets.String) *BucketSet {
+	return &BucketSet{
+		cache:   newCache(),
+		buckets: bucketList,
+	}
+}
+
+// NewBucket creates a new bucket.
+func NewBucket(name string, bl *BucketSet) *Bucket {
+	return &Bucket{
+		name:    name,
+		buckets: bl,
+	}
+}
+
+// Name implements Bucket.
+func (b *Bucket) Name() string {
+	return b.name
+}
+
+// Has returns true if this bucket owns the key and
+// implements reconciler.Bucket interface.
+func (b *Bucket) Has(nn types.NamespacedName) bool {
+	return b.buckets.Owner(nn.String()) == b.name
+}
+
+// Owner returns the owner of the key.
+// Owner will cache the results for faster lookup.
+func (b *BucketSet) Owner(key string) string {
+	if v, ok := b.cache.Get(key); ok {
+		return v.(string)
+	}
+	b.mu.RLock()
+	defer b.mu.RUnlock()
+	l := ChooseSubset(b.buckets, 1 /*single query wanted*/, key)
+	ret := l.UnsortedList()[0]
+	b.cache.Add(key, ret)
+	return ret
+}
+
+// Update updates the universe of buckets.
+func (b *BucketSet) Update(newB sets.String) {
+	b.mu.Lock()
+	defer b.mu.Unlock()
+	// In theory we can iterate over the map and
+	// purge only the keys that moved to a new shard.
+	// But this might be more expensive than re-build
+	// the cache as reconciliations happen.
+	b.cache.Purge()
+	b.buckets = newB
+}
diff --git a/vendor/knative.dev/pkg/hash/doc.go b/vendor/knative.dev/pkg/hash/doc.go
index 36569b84a..5cb01faa6 100644
--- a/vendor/knative.dev/pkg/hash/doc.go
+++ b/vendor/knative.dev/pkg/hash/doc.go
@@ -17,6 +17,11 @@ limitations under the License.
 // Package hash contains various Knative specific hashing utilities.
 //
 // - ChooseSubset is a consistent hashing/mapping function providing
-//   a  consistent selection of N keys from M (N<=M) keys for a given
+//   a consistent selection of N keys from M (N<=M) keys for a given
 //   target.
+// - BucketSet is a bucketer library which uses ChooseSubset under the
+//   the hood in order to implement consistent mapping between keys and
+//   set of buckets, indentified by unique names. Compared to basic bucket
+//   implementtion which just does hash%num_buckets, when the number of
+//   buckets change only a small subset of keys are supposed to migrate.
 package hash
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 39360dfb3..42758046f 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1034,12 +1034,13 @@ knative.dev/networking/pkg/client/injection/informers/networking/v1alpha1/server
 knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/serverlessservice
 knative.dev/networking/pkg/client/istio/listers/networking/v1alpha3
 knative.dev/networking/pkg/client/listers/networking/v1alpha1
-# knative.dev/pkg v0.0.0-20200710163519-a0cb3d689532
+# knative.dev/pkg v0.0.0-20200713031612-b09a159e12c9
 ## explicit
 knative.dev/pkg/apiextensions/storageversion
 knative.dev/pkg/apiextensions/storageversion/cmd/migrate
 knative.dev/pkg/apis
 knative.dev/pkg/apis/duck
+knative.dev/pkg/apis/duck/ducktypes
 knative.dev/pkg/apis/duck/v1
 knative.dev/pkg/apis/duck/v1alpha1
 knative.dev/pkg/apis/duck/v1beta1
@@ -1154,7 +1155,7 @@ knative.dev/pkg/webhook/resourcesemantics/conversion
 knative.dev/pkg/webhook/resourcesemantics/defaulting
 knative.dev/pkg/webhook/resourcesemantics/validation
 knative.dev/pkg/websocket
-# knative.dev/test-infra v0.0.0-20200710160019-5b9732bc24f7
+# knative.dev/test-infra v0.0.0-20200713045417-850e4e37918d
 ## explicit
 knative.dev/test-infra/scripts
 # sigs.k8s.io/yaml v1.2.0


Test patch:
diff --git a/pkg/autoscaler/scaling/multiscaler_test.go b/pkg/autoscaler/scaling/multiscaler_test.go
index ac3748b41..058e78169 100644
--- a/pkg/autoscaler/scaling/multiscaler_test.go
+++ b/pkg/autoscaler/scaling/multiscaler_test.go
@@ -50,7 +50,7 @@ func watchFunc(ctx context.Context, ms *MultiScaler, decider *Decider, desiredSc
 			return
 		}
 		if got, want := m.Status.DesiredScale, int32(desiredScale); got != want {
-			errCh <- fmt.Errorf("Get() = %v, wanted %v", got, want)
+			errCh <- fmt.Errorf("DesiredScale = %v, wanted %v", got, want)
 			return
 		}
 		errCh <- nil
@@ -114,10 +114,10 @@ func TestMultiScalerScaling(t *testing.T) {
 		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
 	}
 	if got, want := d.Status.ExcessBurstCapacity, int32(0); got != want {
-		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
+		t.Errorf("Decider.Status.ExcessBurstCapacity = %d, want: %d", got, want)
 	}
 	if got, want := d.Status.NumActivators, int32(0); got != want {
-		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
+		t.Errorf("Decider.Status.NumActivators = %d, want: %d", got, want)
 	}
 
 	// Verify that we see a "tick"
@@ -135,10 +135,10 @@ func TestMultiScalerScaling(t *testing.T) {
 		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
 	}
 	if got, want := d.Status.ExcessBurstCapacity, int32(1); got != want {
-		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
+		t.Errorf("Decider.Status.ExcessBurstCapacity = %d, want: %d", got, want)
 	}
 	if got, want := d.Status.NumActivators, int32(2); got != want {
-		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
+		t.Errorf("Decider.Status.NumActivators = %d, want: %d", got, want)
 	}
 
 	// Change number of activators, keeping the other data the same. E.g. CM
@@ -158,10 +158,10 @@ func TestMultiScalerScaling(t *testing.T) {
 		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
 	}
 	if got, want := d.Status.ExcessBurstCapacity, int32(1); got != want {
-		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
+		t.Errorf("Decider.Status.ExcessBurstCapacity = %d, want: %d", got, want)
 	}
 	if got, want := d.Status.NumActivators, int32(3); got != want {
-		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
+		t.Errorf("Decider.Status.NumActivators = %d, want: %d", got, want)
 	}
 
 	// Verify that subsequent "ticks" don't trigger a callback, since
@@ -188,6 +188,7 @@ func TestMultiscalerCreateTBC42(t *testing.T) {
 	ms, _ := createMultiScaler(ctx, TestLogger(t))
 
 	decider := newDecider()
+	decider.Spec.InitialScale = 2
 	decider.Spec.TargetBurstCapacity = 42
 	decider.Spec.TotalValue = 25
 
@@ -202,8 +203,8 @@ func TestMultiscalerCreateTBC42(t *testing.T) {
 	if got, want := d.Status.DesiredScale, int32(-1); got != want {
 		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
 	}
-	if got, want := d.Status.ExcessBurstCapacity, int32(25-42); got != want {
-		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
+	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.Errorf("Delete() = %v", err)
@@ -230,7 +231,7 @@ func TestMultiscalerCreateTBCMinus1(t *testing.T) {
 		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
 	}
 	if got, want := d.Status.ExcessBurstCapacity, int32(-1); got != want {
-		t.Errorf("Decider.Status.DesiredScale = %d, want: %d", got, want)
+		t.Errorf("Decider.Status.ExcessBurstCapacity = %d, want: %d", got, want)
 	}
 	if err := ms.Delete(ctx, decider.Namespace, decider.Name); err != nil {
 		t.Errorf("Delete() = %v", err)
diff --git a/test/e2e/logging_test.go b/test/e2e/logging_test.go
index 3f5d3ec25..4392a4861 100644
--- a/test/e2e/logging_test.go
+++ b/test/e2e/logging_test.go
@@ -31,6 +31,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/wait"
 
 	"knative.dev/pkg/system"
+	pkgTest "knative.dev/pkg/test"
 	"knative.dev/pkg/test/logstream"
 	"knative.dev/serving/pkg/apis/autoscaling"
 	"knative.dev/serving/pkg/network"
@@ -63,9 +64,16 @@ func TestRequestLogs(t *testing.T) {
 		t.Fatalf("Failed to create initial Service: %v: %v", names.Service, err)
 	}
 
-	_, err = sendRequest(t, clients, test.ServingFlags.ResolvableDomain, resources.Route.Status.URL.URL())
+	_, err = pkgTest.WaitForEndpointState(
+		clients.KubeClient,
+		t.Logf,
+		resources.Route.Status.URL.URL(),
+		v1test.RetryingRouteInconsistency(pkgTest.MatchesAllOf(pkgTest.IsStatusOK, pkgTest.MatchesBody(test.HelloWorldText))),
+		"WaitForEndpointToServeText",
+		test.ServingFlags.ResolvableDomain,
+		test.AddRootCAtoTransport(t.Logf, clients, test.ServingFlags.Https))
 	if err != nil {
-		t.Fatal("Unexpected error when sending request to helloworld:", err)
+		t.Fatalf("The endpoint didn't serve the expected text %q: %v", test.HelloWorldText, err)
 	}
 
 	pod, err := theOnlyPod(clients, resources.Revision.Namespace, resources.Revision.Name)
diff --git a/vendor/knative.dev/pkg/apis/duck/v1_tests.go b/vendor/knative.dev/pkg/apis/duck/v1_tests.go
new file mode 100644
index 000000000..ca68b4c89
--- /dev/null
+++ b/vendor/knative.dev/pkg/apis/duck/v1_tests.go
@@ -0,0 +1,78 @@
+/*
+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 duck
+
+import (
+	"testing"
+
+	appsv1 "k8s.io/api/apps/v1"
+	batchv1 "k8s.io/api/batch/v1"
+
+	"knative.dev/pkg/apis/duck/v1"
+)
+
+// Conditions is an Implementable "duck type".
+var _ Implementable = (*v1.Conditions)(nil)
+
+// In order for Conditions to be Implementable, KResource must be Populatable.
+var _ Populatable = (*v1.KResource)(nil)
+
+// Source is an Implementable "duck type".
+var _ Implementable = (*v1.Source)(nil)
+
+// Verify Source resources meet duck contracts.
+var _ Populatable = (*v1.Source)(nil)
+
+var _ Populatable = (*v1.WithPod)(nil)
+var _ Implementable = (*v1.PodSpecable)(nil)
+
+func TestV1TypesImplements(t *testing.T) {
+	testCases := []struct {
+		instance interface{}
+		iface    Implementable
+	}{
+		{instance: &v1.AddressableType{}, iface: &v1.Addressable{}},
+		{instance: &v1.KResource{}, iface: &v1.Conditions{}},
+	}
+	for _, tc := range testCases {
+		if err := VerifyType(tc.instance, tc.iface); err != nil {
+			t.Error(err)
+		}
+	}
+}
+
+func TestV1ImplementsPodSpecable(t *testing.T) {
+	instances := []interface{}{
+		&v1.WithPod{},
+		&appsv1.ReplicaSet{},
+		&appsv1.Deployment{},
+		&appsv1.StatefulSet{},
+		&appsv1.DaemonSet{},
+		&batchv1.Job{},
+	}
+	for _, instance := range instances {
+		if err := VerifyType(instance, &v1.PodSpecable{}); err != nil {
+			t.Error(err)
+		}
+	}
+}
+
+// Addressable is an Implementable "duck type".
+var _ Implementable = (*v1.Addressable)(nil)
+
+// Verify AddressableType resources meet duck contracts.
+var _ Populatable = (*v1.AddressableType)(nil)
diff --git a/vendor/knative.dev/pkg/apis/duck/v1beta1_tests.go b/vendor/knative.dev/pkg/apis/duck/v1beta1_tests.go
new file mode 100644
index 000000000..23731d8c7
--- /dev/null
+++ b/vendor/knative.dev/pkg/apis/duck/v1beta1_tests.go
@@ -0,0 +1,56 @@
+/*
+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 duck
+
+import (
+	"testing"
+
+	"knative.dev/pkg/apis/duck/v1beta1"
+)
+
+// v1beta1.Conditions is an Implementable "duck type".
+var _ Implementable = (*v1beta1.Conditions)(nil)
+
+// In order for v1beta1.Conditions to be Implementable, v1beta1.KResource must be Populatable.
+var _ Populatable = (*v1beta1.KResource)(nil)
+
+// v1beta1.Source is an Implementable "duck type".
+var _ Implementable = (*v1beta1.Source)(nil)
+
+// Verify v1beta1.Source resources meet duck contracts.
+var _ Populatable = (*v1beta1.Source)(nil)
+
+// Addressable is an Implementable "duck type".
+var _ Implementable = (*v1beta1.Addressable)(nil)
+
+// Verify AddressableType resources meet duck contracts.
+var _ Populatable = (*v1beta1.AddressableType)(nil)
+
+func TestV1Beta1TypesImplements(t *testing.T) {
+	testCases := []struct {
+		instance interface{}
+		iface    Implementable
+	}{
+		{instance: &v1beta1.AddressableType{}, iface: &v1beta1.Addressable{}},
+		{instance: &v1beta1.KResource{}, iface: &v1beta1.Conditions{}},
+	}
+	for _, tc := range testCases {
+		if err := VerifyType(tc.instance, tc.iface); err != nil {
+			t.Error(err)
+		}
+	}
+}
diff --git a/vendor/knative.dev/pkg/test/ha/ha.go b/vendor/knative.dev/pkg/test/ha/ha.go
index c1b5c045b..2859cc872 100644
--- a/vendor/knative.dev/pkg/test/ha/ha.go
+++ b/vendor/knative.dev/pkg/test/ha/ha.go
@@ -26,17 +26,36 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/apimachinery/pkg/util/wait"
+
 	"knative.dev/pkg/test"
 	"knative.dev/pkg/test/logging"
 )
 
+func countingRFind(wr rune, wc int) func(rune) bool {
+	cnt := 0
+	return func(r rune) bool {
+		if r == wr {
+			cnt++
+		}
+		return cnt == wc
+	}
+}
+
+func extractDeployment(pod string) string {
+	if x := strings.LastIndexFunc(pod, countingRFind('-', 2)); x != -1 {
+		return pod[:x]
+	}
+	return ""
+}
+
 // GetLeaders collects all of the leader pods from the specified deployment.
+// GetLeaders will return duplicate pods by design.
 func GetLeaders(t *testing.T, client *test.KubeClient, deploymentName, namespace string) ([]string, error) {
 	leases, err := client.Kube.CoordinationV1().Leases(namespace).List(metav1.ListOptions{})
 	if err != nil {
 		return nil, fmt.Errorf("error getting leases for deployment %q: %w", deploymentName, err)
 	}
-	var pods []string
+	ret := make([]string, 0, len(leases.Items))
 	for _, lease := range leases.Items {
 		if lease.Spec.HolderIdentity == nil {
 			continue
@@ -44,16 +63,12 @@ func GetLeaders(t *testing.T, client *test.KubeClient, deploymentName, namespace
 		pod := strings.SplitN(*lease.Spec.HolderIdentity, "_", 2)[0]
 
 		// Deconstruct the pod name and look for the deployment.  This won't work for very long deployment names.
-		parts := strings.Split(pod, "-")
-		if len(parts) < 3 {
-			continue
-		}
-		if strings.Join(parts[:len(parts)-2], "-") != deploymentName {
+		if extractDeployment(pod) != deploymentName {
 			continue
 		}
-		pods = append(pods, pod)
+		ret = append(ret, pod)
 	}
-	return pods, nil
+	return ret, nil
 }
 
 // WaitForNewLeaders waits until the collection of current leaders consists of "n" leaders
@@ -64,19 +79,20 @@ func WaitForNewLeaders(t *testing.T, client *test.KubeClient, deploymentName, na
 
 	var leaders sets.String
 	err := wait.PollImmediate(time.Second, time.Minute, func() (bool, error) {
-		currentLeaders, err := GetLeaders(t, client, deploymentName, namespace)
+		currLeaders, err := GetLeaders(t, client, deploymentName, namespace)
 		if err != nil {
 			return false, err
 		}
-		if len(currentLeaders) < n {
-			t.Logf("WaitForNewLeaders[%s] not enough leaders, got: %d, want: %d", deploymentName, len(currentLeaders), n)
+		if len(currLeaders) < n {
+			t.Logf("WaitForNewLeaders[%s] not enough leaders, got: %d, want: %d", deploymentName, len(currLeaders), n)
 			return false, nil
 		}
-		if previousLeaders.HasAny(currentLeaders...) {
-			t.Logf("WaitForNewLeaders[%s] still see intersection: %v", deploymentName, previousLeaders.Intersection(sets.NewString(currentLeaders...)))
+		l := sets.NewString(currLeaders...)
+		if previousLeaders.HasAny(currLeaders...) {
+			t.Logf("WaitForNewLeaders[%s] still see intersection: %v", deploymentName, previousLeaders.Intersection(l))
 			return false, nil
 		}
-		leaders = sets.NewString(currentLeaders...)
+		leaders = l
 		return true, nil
 	})
 	return leaders, err