Repository: istio/istio (go)

Issue:
add support for local origin errors in outlier detection
Envoy supports splitting of local origin/remote errors while deciding a host to be outlier.  https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/outlier_detection.proto#config-cluster-v3-outlierdetection.

This is useful in certain cases where we want to just rely on local failures to remove from load balancer pool.

The feature request is to add to Destination rule API.
     split_external_local_origin_errors
     consecutive_local_origin_failure
     enforcing_consecutive_local_origin_failure

Pull request:
implement local origin errors
Implements istio/api#1989
Fixes #32645

[ ] Configuration Infrastructure
[ ] Docs
[ ] Installation
[X] Networking
[ ] Performance and Scalability
[ ] Policies and Telemetry
[ ] Security
[ ] Test and Release
[ ] User Experience
[ ] Developer Infrastructure


Pull Request Attributes

Please check any characteristics that apply to this pull request. 

[X] Does not have any [user-facing](https://github.com/istio/istio/tree/master/releasenotes#when-to-add-release-notes) changes. This may include CLI changes, API changes, behavior changes, performance improvements, etc.


Patch:
diff --git a/common/.commonfiles.sha b/common/.commonfiles.sha
index 249d44f195..c46e7050b6 100644
--- a/common/.commonfiles.sha
+++ b/common/.commonfiles.sha
@@ -1,1 +1,1 @@
-e15a278612ccb4a1d53644193e7a4d3e2e1c6e8a
+6dd7adf938419af5876a29c2b7922ee9feac9c1f
diff --git a/common/scripts/setup_env.sh b/common/scripts/setup_env.sh
index f04af16f71..168da2f175 100755
--- a/common/scripts/setup_env.sh
+++ b/common/scripts/setup_env.sh
@@ -63,7 +63,7 @@ fi
 
 # Build image to use
 if [[ "${IMAGE_VERSION:-}" == "" ]]; then
-  export IMAGE_VERSION=master-2021-05-20T15-33-57
+  export IMAGE_VERSION=master-2021-05-20T21-27-37
 fi
 if [[ "${IMAGE_NAME:-}" == "" ]]; then
   export IMAGE_NAME=build-tools
diff --git a/go.mod b/go.mod
index f0e0c5a0cf..ed77fa3807 100644
--- a/go.mod
+++ b/go.mod
@@ -91,10 +91,10 @@ require (
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 	helm.sh/helm/v3 v3.5.3
 	honnef.co/go/tools v0.0.1-2020.1.5 // indirect
-	istio.io/api v0.0.0-20210520173553-a0740e283651
-	istio.io/client-go v0.0.0-20210519190722-880fbaebd090
+	istio.io/api v0.0.0-20210520230835-8e3314c46788
+	istio.io/client-go v0.0.0-20210520232816-77a421a83f55
 	istio.io/gogo-genproto v0.0.0-20210405163638-fcb891f20a5a
-	istio.io/pkg v0.0.0-20210520173733-5e897199cbb5
+	istio.io/pkg v0.0.0-20210520231143-d67b326618ba
 	k8s.io/api v0.21.0
 	k8s.io/apiextensions-apiserver v0.21.0
 	k8s.io/apimachinery v0.21.0
diff --git a/go.sum b/go.sum
index 7f52772582..40551c9fbd 100644
--- a/go.sum
+++ b/go.sum
@@ -1584,16 +1584,15 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k=
 honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-istio.io/api v0.0.0-20210519185020-6f705c42b034/go.mod h1:nsSFw1LIMmGL7r/+6fJI6FxeG/UGlLxRK8bkojIvBVs=
-istio.io/api v0.0.0-20210520173553-a0740e283651 h1:Q/S6LDhV5OSOPzWea4so0swA9hLmt8CotW4QntgIc7w=
-istio.io/api v0.0.0-20210520173553-a0740e283651/go.mod h1:nsSFw1LIMmGL7r/+6fJI6FxeG/UGlLxRK8bkojIvBVs=
-istio.io/client-go v0.0.0-20210519190722-880fbaebd090 h1:bd3M6gdU5KdxNZWowm7AlsCYMbZlHLTKX/MnCK55Da4=
-istio.io/client-go v0.0.0-20210519190722-880fbaebd090/go.mod h1:Y70BJRcqMSbIz4oEd2HN9iFZ9lmCY8SYIHyDSUVbLHg=
+istio.io/api v0.0.0-20210520230835-8e3314c46788 h1:MSs7kDFQ5t3Ejhd52TR9ttUbXXP1LbUhglJQgNf0t+Y=
+istio.io/api v0.0.0-20210520230835-8e3314c46788/go.mod h1:nsSFw1LIMmGL7r/+6fJI6FxeG/UGlLxRK8bkojIvBVs=
+istio.io/client-go v0.0.0-20210520232816-77a421a83f55 h1:rFIhtnE3tSa9C8jXRGXudBoYAQexMRzAJb4ydDwGPDg=
+istio.io/client-go v0.0.0-20210520232816-77a421a83f55/go.mod h1:2bqyMqPZMDx9Ee+dpyjTzYBkS7kdK0a+8VwNm0lj0Jo=
 istio.io/gogo-genproto v0.0.0-20210113155706-4daf5697332f/go.mod h1:6BwTZRNbWS570wHX/uR1Wqk5e0157TofTAUMzT7N4+s=
 istio.io/gogo-genproto v0.0.0-20210405163638-fcb891f20a5a h1:e3x86okAVbui7ddsCcuY9r3QjPI/Ulf31lVRfGiGFKM=
 istio.io/gogo-genproto v0.0.0-20210405163638-fcb891f20a5a/go.mod h1:6BwTZRNbWS570wHX/uR1Wqk5e0157TofTAUMzT7N4+s=
-istio.io/pkg v0.0.0-20210520173733-5e897199cbb5 h1:rSC2IryT7JiL0KLCI6hoEuYTYUrOX0BUKqt5Hfu1TuQ=
-istio.io/pkg v0.0.0-20210520173733-5e897199cbb5/go.mod h1:U14DF4p1AViKhOwpVv1UPEWtknGytJyfJw6hqVi0TmM=
+istio.io/pkg v0.0.0-20210520231143-d67b326618ba h1:tuc8cj2lCB98mTPoH7CYSx+BTKJ4NbaDrHcIqgnMxtw=
+istio.io/pkg v0.0.0-20210520231143-d67b326618ba/go.mod h1:U14DF4p1AViKhOwpVv1UPEWtknGytJyfJw6hqVi0TmM=
 k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
 k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA=
 k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4=
diff --git a/istioctl/pkg/verifier/verifier.go b/istioctl/pkg/verifier/verifier.go
index 0dc7044ee0..925debb033 100644
--- a/istioctl/pkg/verifier/verifier.go
+++ b/istioctl/pkg/verifier/verifier.go
@@ -394,13 +394,13 @@ func (v *StatusVerifier) injectorFromCluster(revision string) (*admit_v1.Mutatin
 
 	revCount := 0
 	var hookmatch *admit_v1.MutatingWebhookConfiguration
-	for _, hook := range hooks.Items {
+	for i, hook := range hooks.Items {
 		rev := hook.ObjectMeta.GetLabels()[label.IoIstioRev.Name]
 		if rev != "" {
 			revCount++
 			revision = rev
 			if revision == "" || revision == rev {
-				hookmatch = &hook
+				hookmatch = &hooks.Items[i]
 			}
 		}
 	}
diff --git a/istioctl/pkg/writer/envoy/configdump/route.go b/istioctl/pkg/writer/envoy/configdump/route.go
index e7a0685465..c731d5b1c7 100644
--- a/istioctl/pkg/writer/envoy/configdump/route.go
+++ b/istioctl/pkg/writer/envoy/configdump/route.go
@@ -94,21 +94,19 @@ func describeRouteDomains(domains []string) string {
 	}
 
 	// Return the shortest non-numeric domain.  Count of domains seems uninteresting.
-	candidate := domains[0]
-	for _, domain := range domains {
-		if len(domain) == 0 {
-			continue
-		}
-		firstChar := domain[0]
-		if firstChar >= '1' && firstChar <= '9' {
-			continue
-		}
-		if len(domain) < len(candidate) {
-			candidate = domain
+	max := 2
+	withoutPort := make([]string, 0, len(domains))
+	for _, d := range domains {
+		if !strings.Contains(d, ":") {
+			withoutPort = append(withoutPort, d)
 		}
 	}
-
-	return candidate
+	visible := withoutPort[:max]
+	ret := strings.Join(visible, ", ")
+	if len(withoutPort) > max {
+		return fmt.Sprintf("%s + %d more...", ret, len(withoutPort)-max)
+	}
+	return ret
 }
 
 func describeManagement(metadata *envoy_config_core_v3.Metadata) string {
diff --git a/pilot/pkg/model/config.go b/pilot/pkg/model/config.go
index 5a8572fd2e..9db7782b8f 100644
--- a/pilot/pkg/model/config.go
+++ b/pilot/pkg/model/config.go
@@ -15,7 +15,8 @@
 package model
 
 import (
-	"hash/crc32"
+	"crypto/md5"
+	"encoding/binary"
 	"net"
 	"sort"
 	"strings"
@@ -42,14 +43,20 @@ type ConfigKey struct {
 	Namespace string
 }
 
-func (key ConfigKey) HashCode() uint32 {
-	var result uint32
-	result = 31*result + crc32.ChecksumIEEE([]byte(key.Kind.Kind))
-	result = 31*result + crc32.ChecksumIEEE([]byte(key.Kind.Version))
-	result = 31*result + crc32.ChecksumIEEE([]byte(key.Kind.Group))
-	result = 31*result + crc32.ChecksumIEEE([]byte(key.Namespace))
-	result = 31*result + crc32.ChecksumIEEE([]byte(key.Name))
-	return result
+func (key ConfigKey) HashCode() uint64 {
+	hash := md5.New()
+	for _, v := range []string{
+		key.Name,
+		key.Namespace,
+		key.Kind.Kind,
+		key.Kind.Group,
+		key.Kind.Version,
+	} {
+		hash.Write([]byte(v))
+	}
+	var tmp [md5.Size]byte
+	sum := hash.Sum(tmp[:0])
+	return binary.BigEndian.Uint64(sum)
 }
 
 // ConfigsOfKind extracts configs of the specified kind.
diff --git a/pilot/pkg/model/push_context.go b/pilot/pkg/model/push_context.go
index 87fb23125c..a420e58331 100644
--- a/pilot/pkg/model/push_context.go
+++ b/pilot/pkg/model/push_context.go
@@ -1428,10 +1428,10 @@ func (ps *PushContext) initSidecarScopes(env *Environment) error {
 	// Currently we expect that it has no workloadSelectors
 	var rootNSConfig *config.Config
 	if ps.Mesh.RootNamespace != "" {
-		for _, sidecarConfig := range sidecarConfigs {
+		for i, sidecarConfig := range sidecarConfigs {
 			if sidecarConfig.Namespace == ps.Mesh.RootNamespace &&
 				sidecarConfig.Spec.(*networking.Sidecar).WorkloadSelector == nil {
-				rootNSConfig = &sidecarConfig
+				rootNSConfig = &sidecarConfigs[i]
 				break
 			}
 		}
diff --git a/pilot/pkg/model/sidecar.go b/pilot/pkg/model/sidecar.go
index 5f2678db25..fd6a0420e2 100644
--- a/pilot/pkg/model/sidecar.go
+++ b/pilot/pkg/model/sidecar.go
@@ -106,7 +106,7 @@ type SidecarScope struct {
 	// Set of known configs this sidecar depends on.
 	// This field will be used to determine the config/resource scope
 	// which means which config changes will affect the proxies within this scope.
-	configDependencies map[uint32]struct{}
+	configDependencies map[uint64]struct{}
 
 	// The namespace to treat as the administrative root namespace for
 	// Istio configuration.
@@ -187,7 +187,7 @@ func DefaultSidecarScopeForNamespace(ps *PushContext, configNamespace string) *S
 		services:           defaultEgressListener.services,
 		destinationRules:   make(map[host.Name]*config.Config),
 		servicesByHostname: make(map[host.Name]*Service, len(defaultEgressListener.services)),
-		configDependencies: make(map[uint32]struct{}),
+		configDependencies: make(map[uint64]struct{}),
 		RootNamespace:      ps.Mesh.RootNamespace,
 		Version:            ps.PushVersion,
 	}
@@ -250,7 +250,7 @@ func ConvertToSidecarScope(ps *PushContext, sidecarConfig *config.Config, config
 		Name:               sidecarConfig.Name,
 		Namespace:          configNamespace,
 		Sidecar:            sidecar,
-		configDependencies: make(map[uint32]struct{}),
+		configDependencies: make(map[uint64]struct{}),
 		RootNamespace:      ps.Mesh.RootNamespace,
 		Version:            ps.PushVersion,
 	}
@@ -539,7 +539,7 @@ func (sc *SidecarScope) AddConfigDependencies(dependencies ...ConfigKey) {
 		return
 	}
 	if sc.configDependencies == nil {
-		sc.configDependencies = make(map[uint32]struct{})
+		sc.configDependencies = make(map[uint64]struct{})
 	}
 
 	for _, config := range dependencies {
diff --git a/pilot/pkg/networking/core/v1alpha3/cluster.go b/pilot/pkg/networking/core/v1alpha3/cluster.go
index b122a3f8f9..bce01d0954 100644
--- a/pilot/pkg/networking/core/v1alpha3/cluster.go
+++ b/pilot/pkg/networking/core/v1alpha3/cluster.go
@@ -605,6 +605,15 @@ func applyOutlierDetection(c *cluster.Cluster, outlier *networking.OutlierDetect
 		out.MaxEjectionPercent = &wrappers.UInt32Value{Value: uint32(outlier.MaxEjectionPercent)}
 	}
 
+	if outlier.SplitExternalLocalOriginErrors {
+		out.SplitExternalLocalOriginErrors = true
+		if outlier.ConsecutiveLocalOriginFailures.GetValue() > 0 {
+			out.ConsecutiveLocalOriginFailure = &wrappers.UInt32Value{Value: outlier.ConsecutiveLocalOriginFailures.Value}
+		}
+		// SuccessRate based outlier detection should be disabled.
+		out.EnforcingLocalOriginSuccessRate = &wrappers.UInt32Value{Value: 0}
+	}
+
 	c.OutlierDetection = out
 
 	// Disable panic threshold by default as its not typically applicable in k8s environments
diff --git a/pilot/pkg/secrets/kube/secrets.go b/pilot/pkg/secrets/kube/secrets.go
index adbb6fac5e..1acc8cb323 100644
--- a/pilot/pkg/secrets/kube/secrets.go
+++ b/pilot/pkg/secrets/kube/secrets.go
@@ -26,7 +26,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	informersv1 "k8s.io/client-go/informers/core/v1"
-	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/kubernetes/fake"
 	authorizationv1client "k8s.io/client-go/kubernetes/typed/authorization/v1"
 	k8stesting "k8s.io/client-go/testing"
@@ -76,8 +75,6 @@ type authorizationResponse struct {
 
 var _ secrets.Controller = &SecretsController{}
 
-type RemoteKubeClientGetter func(clusterID string) kubernetes.Interface
-
 func NewSecretsController(client kube.Client, clusterID string) *SecretsController {
 	// Informer is lazy loaded, load it now
 	_ = client.KubeInformer().Core().V1().Secrets().Informer()
diff --git a/pilot/pkg/security/authn/utils/utils.go b/pilot/pkg/security/authn/utils/utils.go
index aed149008a..e90f872d25 100644
--- a/pilot/pkg/security/authn/utils/utils.go
+++ b/pilot/pkg/security/authn/utils/utils.go
@@ -24,11 +24,6 @@ import (
 	protovalue "istio.io/istio/pkg/proto"
 )
 
-const (
-	// Service account for Pilot (hardcoded values at setup time)
-	PilotSvcAccName string = "istio-pilot-service-account"
-)
-
 // SupportedCiphers for server side TLS configuration.
 var SupportedCiphers = []string{
 	"ECDHE-ECDSA-AES256-GCM-SHA384",
diff --git a/pilot/pkg/security/model/authentication.go b/pilot/pkg/security/model/authentication.go
index 5b0525f542..365d85f557 100644
--- a/pilot/pkg/security/model/authentication.go
+++ b/pilot/pkg/security/model/authentication.go
@@ -28,9 +28,6 @@ import (
 )
 
 const (
-	// SDSStatPrefix is the human readable prefix to use when emitting statistics for the SDS service.
-	SDSStatPrefix = "sdsstat"
-
 	// SDSClusterName is the name of the cluster for SDS connections
 	SDSClusterName = "sds-grpc"
 
diff --git a/pilot/pkg/serviceregistry/kube/controller/multicluster.go b/pilot/pkg/serviceregistry/kube/controller/multicluster.go
index 6e600667e1..ac495db0b5 100644
--- a/pilot/pkg/serviceregistry/kube/controller/multicluster.go
+++ b/pilot/pkg/serviceregistry/kube/controller/multicluster.go
@@ -359,7 +359,7 @@ func (m *Multicluster) updateHandler(svc *model.Service) {
 				Name:      string(svc.Hostname),
 				Namespace: svc.Attributes.Namespace,
 			}: {}},
-			Reason: []model.TriggerReason{model.UnknownTrigger},
+			Reason: []model.TriggerReason{model.ServiceUpdate},
 		}
 		m.XDSUpdater.ConfigUpdate(req)
 	}
diff --git a/pilot/pkg/serviceregistry/serviceentry/servicediscovery.go b/pilot/pkg/serviceregistry/serviceentry/servicediscovery.go
index 27010243d7..34505a9b11 100644
--- a/pilot/pkg/serviceregistry/serviceentry/servicediscovery.go
+++ b/pilot/pkg/serviceregistry/serviceentry/servicediscovery.go
@@ -902,7 +902,7 @@ func autoAllocateIPs(services []*model.Service) []*model.Service {
 	// To avoid allocating 240.240.(i).255, if X % 255 is 0, increment X.
 	// For example, when X=510, the resulting IP would be 240.240.2.0 (invalid)
 	// So we bump X to 511, so that the resulting IP is 240.240.2.1
-	maxIPs := 255 * 255 // are we going to exceeed this limit by processing 64K services?
+	maxIPs := 255 * 255 // are we going to exceed this limit by processing 64K services?
 	x := 0
 	for _, svc := range services {
 		// we can allocate IPs only if
diff --git a/pkg/security/security.go b/pkg/security/security.go
index 00b172cb03..ac855fa90a 100644
--- a/pkg/security/security.go
+++ b/pkg/security/security.go
@@ -281,9 +281,6 @@ const (
 )
 
 const (
-	// IdentityTemplate is the SPIFFE format template of the identity.
-	IdentityTemplate = "spiffe://%s/ns/%s/sa/%s"
-
 	authorizationMeta = "authorization"
 )
 
diff --git a/prow/config/metrics/aggregated-metrics-reader.yaml b/prow/config/metrics/aggregated-metrics-reader.yaml
deleted file mode 100644
index cdf3415fdd..0000000000
--- a/prow/config/metrics/aggregated-metrics-reader.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-kind: ClusterRole
-apiVersion: rbac.authorization.k8s.io/v1
-metadata:
-  name: system:aggregated-metrics-reader
-  labels:
-    rbac.authorization.k8s.io/aggregate-to-view: "true"
-    rbac.authorization.k8s.io/aggregate-to-edit: "true"
-    rbac.authorization.k8s.io/aggregate-to-admin: "true"
-rules:
-- apiGroups: ["metrics.k8s.io"]
-  resources: ["pods"]
-  verbs: ["get", "list", "watch"]
diff --git a/prow/config/metrics/auth-delegator.yaml b/prow/config/metrics/auth-delegator.yaml
deleted file mode 100644
index e3442c5750..0000000000
--- a/prow/config/metrics/auth-delegator.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
----
-apiVersion: rbac.authorization.k8s.io/v1beta1
-kind: ClusterRoleBinding
-metadata:
-  name: metrics-server:system:auth-delegator
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: system:auth-delegator
-subjects:
-- kind: ServiceAccount
-  name: metrics-server
-  namespace: kube-system
diff --git a/prow/config/metrics/auth-reader.yaml b/prow/config/metrics/auth-reader.yaml
deleted file mode 100644
index f0616e1635..0000000000
--- a/prow/config/metrics/auth-reader.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
----
-apiVersion: rbac.authorization.k8s.io/v1beta1
-kind: RoleBinding
-metadata:
-  name: metrics-server-auth-reader
-  namespace: kube-system
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: Role
-  name: extension-apiserver-authentication-reader
-subjects:
-- kind: ServiceAccount
-  name: metrics-server
-  namespace: kube-system
diff --git a/prow/config/metrics/metrics-apiservice.yaml b/prow/config/metrics/metrics-apiservice.yaml
deleted file mode 100644
index 08b0530d80..0000000000
--- a/prow/config/metrics/metrics-apiservice.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
----
-apiVersion: apiregistration.k8s.io/v1beta1
-kind: APIService
-metadata:
-  name: v1beta1.metrics.k8s.io
-spec:
-  service:
-    name: metrics-server
-    namespace: kube-system
-  group: metrics.k8s.io
-  version: v1beta1
-  insecureSkipTLSVerify: true
-  groupPriorityMinimum: 100
-  versionPriority: 100
diff --git a/prow/config/metrics/metrics-server-deployment.yaml b/prow/config/metrics/metrics-server-deployment.yaml
deleted file mode 100644
index 9fdc11139f..0000000000
--- a/prow/config/metrics/metrics-server-deployment.yaml
+++ /dev/null
@@ -1,37 +0,0 @@
----
-apiVersion: v1
-kind: ServiceAccount
-metadata:
-  name: metrics-server
-  namespace: kube-system
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: metrics-server
-  namespace: kube-system
-  labels:
-    k8s-app: metrics-server
-spec:
-  selector:
-    matchLabels:
-      k8s-app: metrics-server
-  template:
-    metadata:
-      name: metrics-server
-      labels:
-        k8s-app: metrics-server
-    spec:
-      serviceAccountName: metrics-server
-      volumes:
-      # mount in tmp so we can safely use from-scratch images and/or read-only containers
-      - name: tmp-dir
-        emptyDir: {}
-      containers:
-      - name: metrics-server
-        image: k8s.gcr.io/metrics-server-amd64:v0.3.2
-        imagePullPolicy: Always
-        volumeMounts:
-        - name: tmp-dir
-          mountPath: /tmp
-
diff --git a/prow/config/metrics/metrics-server-service.yaml b/prow/config/metrics/metrics-server-service.yaml
deleted file mode 100644
index ddf6f4a8a0..0000000000
--- a/prow/config/metrics/metrics-server-service.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
----
-apiVersion: v1
-kind: Service
-metadata:
-  name: metrics-server
-  namespace: kube-system
-  labels:
-    kubernetes.io/name: "Metrics-server"
-    kubernetes.io/cluster-service: "true"
-spec:
-  selector:
-    k8s-app: metrics-server
-  ports:
-  - port: 443
-    protocol: TCP
-    targetPort: 443
diff --git a/prow/config/metrics/metrics.yaml b/prow/config/metrics/metrics.yaml
new file mode 100644
index 0000000000..676b3ae7a1
--- /dev/null
+++ b/prow/config/metrics/metrics.yaml
@@ -0,0 +1,189 @@
+# Created with `curl -Ls https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.4.4/components.yaml > prow/config/metrics/metrics.yaml`
+# And special patches noted below
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  labels:
+    k8s-app: metrics-server
+  name: metrics-server
+  namespace: kube-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    k8s-app: metrics-server
+    rbac.authorization.k8s.io/aggregate-to-admin: "true"
+    rbac.authorization.k8s.io/aggregate-to-edit: "true"
+    rbac.authorization.k8s.io/aggregate-to-view: "true"
+  name: system:aggregated-metrics-reader
+rules:
+- apiGroups:
+  - metrics.k8s.io
+  resources:
+  - pods
+  - nodes
+  verbs:
+  - get
+  - list
+  - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    k8s-app: metrics-server
+  name: system:metrics-server
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  - nodes
+  - nodes/stats
+  - namespaces
+  - configmaps
+  verbs:
+  - get
+  - list
+  - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  labels:
+    k8s-app: metrics-server
+  name: metrics-server-auth-reader
+  namespace: kube-system
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: extension-apiserver-authentication-reader
+subjects:
+- kind: ServiceAccount
+  name: metrics-server
+  namespace: kube-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    k8s-app: metrics-server
+  name: metrics-server:system:auth-delegator
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: system:auth-delegator
+subjects:
+- kind: ServiceAccount
+  name: metrics-server
+  namespace: kube-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  labels:
+    k8s-app: metrics-server
+  name: system:metrics-server
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: system:metrics-server
+subjects:
+- kind: ServiceAccount
+  name: metrics-server
+  namespace: kube-system
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    k8s-app: metrics-server
+  name: metrics-server
+  namespace: kube-system
+spec:
+  ports:
+  - name: https
+    port: 443
+    protocol: TCP
+    targetPort: https
+  selector:
+    k8s-app: metrics-server
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    k8s-app: metrics-server
+  name: metrics-server
+  namespace: kube-system
+spec:
+  selector:
+    matchLabels:
+      k8s-app: metrics-server
+  strategy:
+    rollingUpdate:
+      maxUnavailable: 0
+  template:
+    metadata:
+      labels:
+        k8s-app: metrics-server
+    spec:
+      containers:
+      - args:
+        - --cert-dir=/tmp
+        - --secure-port=4443
+        - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
+        - --kubelet-use-node-status-port
+        - --kubelet-insecure-tls
+        image: k8s.gcr.io/metrics-server/metrics-server:v0.4.4
+        imagePullPolicy: IfNotPresent
+        livenessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /livez
+            port: https
+            scheme: HTTPS
+          periodSeconds: 10
+        name: metrics-server
+        ports:
+        - containerPort: 4443
+          name: https
+          protocol: TCP
+        readinessProbe:
+          failureThreshold: 3
+          httpGet:
+            path: /readyz
+            port: https
+            scheme: HTTPS
+          periodSeconds: 10
+        securityContext:
+          readOnlyRootFilesystem: true
+          runAsNonRoot: true
+          runAsUser: 1000
+        volumeMounts:
+        - mountPath: /tmp
+          name: tmp-dir
+      nodeSelector:
+        kubernetes.io/os: linux
+      priorityClassName: system-cluster-critical
+      serviceAccountName: metrics-server
+      volumes:
+      - emptyDir: {}
+        name: tmp-dir
+---
+apiVersion: apiregistration.k8s.io/v1
+kind: APIService
+metadata:
+  labels:
+    k8s-app: metrics-server
+  name: v1beta1.metrics.k8s.io
+spec:
+  group: metrics.k8s.io
+  groupPriorityMinimum: 100
+  insecureSkipTLSVerify: true
+  service:
+    name: metrics-server
+    namespace: kube-system
+  version: v1beta1
+  versionPriority: 100
diff --git a/prow/config/metrics/resource-reader.yaml b/prow/config/metrics/resource-reader.yaml
deleted file mode 100644
index 4f9877203f..0000000000
--- a/prow/config/metrics/resource-reader.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: system:metrics-server
-rules:
-- apiGroups:
-  - ""
-  resources:
-  - pods
-  - nodes
-  - nodes/stats
-  verbs:
-  - get
-  - list
-  - watch
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  name: system:metrics-server
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: system:metrics-server
-subjects:
-- kind: ServiceAccount
-  name: metrics-server
-  namespace: kube-system
diff --git a/security/pkg/k8s/chiron/controller.go b/security/pkg/k8s/chiron/controller.go
index d7e97ea137..14db473d42 100644
--- a/security/pkg/k8s/chiron/controller.go
+++ b/security/pkg/k8s/chiron/controller.go
@@ -40,8 +40,6 @@ import (
 	"istio.io/pkg/log"
 )
 
-type WebhookType int
-
 /* #nosec: disable gas linter */
 const (
 	// The Istio DNS secret annotation type
diff --git a/security/pkg/nodeagent/plugin/plugin.go b/security/pkg/nodeagent/plugin/plugin.go
deleted file mode 100644
index af46ec21ae..0000000000
--- a/security/pkg/nodeagent/plugin/plugin.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright Istio 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 plugin
-
-const (
-	// GoogleTokenExchange is the name of the google token exchange plugin.
-	GoogleTokenExchange = "GoogleTokenExchange"
-)
diff --git a/security/pkg/nodeagent/sds/server.go b/security/pkg/nodeagent/sds/server.go
index 341b926411..3196106213 100644
--- a/security/pkg/nodeagent/sds/server.go
+++ b/security/pkg/nodeagent/sds/server.go
@@ -58,6 +58,7 @@ func (s *Server) UpdateCallback(resourceName string) {
 		ConfigsUpdated: map[model.ConfigKey]struct{}{
 			{Kind: gvk.Secret, Name: resourceName}: {},
 		},
+		Reason: []model.TriggerReason{model.SecretTrigger},
 	})
 }
 
diff --git a/tools/bug-report/pkg/cluster/cluster.go b/tools/bug-report/pkg/cluster/cluster.go
index 552b6aaa8b..94f7a3a4a1 100644
--- a/tools/bug-report/pkg/cluster/cluster.go
+++ b/tools/bug-report/pkg/cluster/cluster.go
@@ -79,14 +79,14 @@ func GetClusterResources(ctx context.Context, clientset *kubernetes.Clientset) (
 		if err != nil {
 			return nil, err
 		}
-		for _, p := range pods.Items {
+		for i, p := range pods.Items {
 			deployment := getOwnerDeployment(&p, replicasets.Items)
 			for _, c := range p.Spec.Containers {
 				out.insertContainer(ns.Name, deployment, p.Name, c.Name)
 			}
 			out.Labels[PodKey(p.Namespace, p.Name)] = p.Labels
 			out.Annotations[PodKey(p.Namespace, p.Name)] = p.Annotations
-			out.Pod[PodKey(p.Namespace, p.Name)] = &p
+			out.Pod[PodKey(p.Namespace, p.Name)] = &pods.Items[i]
 		}
 	}
 	if len(errs) != 0 {


Test patch:
diff --git a/pilot/pkg/networking/core/v1alpha3/cluster_test.go b/pilot/pkg/networking/core/v1alpha3/cluster_test.go
index 3f4ededaeb..1ef05a2312 100644
--- a/pilot/pkg/networking/core/v1alpha3/cluster_test.go
+++ b/pilot/pkg/networking/core/v1alpha3/cluster_test.go
@@ -1051,6 +1051,19 @@ func TestApplyOutlierDetection(t *testing.T) {
 				EnforcingSuccessRate:     &wrappers.UInt32Value{Value: 0},
 			},
 		},
+		{
+			"Local origin errors is enabled",
+			&networking.OutlierDetection{
+				SplitExternalLocalOriginErrors: true,
+				ConsecutiveLocalOriginFailures: &types.UInt32Value{Value: 10},
+			},
+			&cluster.OutlierDetection{
+				EnforcingSuccessRate:            &wrappers.UInt32Value{Value: 0},
+				SplitExternalLocalOriginErrors:  true,
+				ConsecutiveLocalOriginFailure:   &wrappers.UInt32Value{Value: 10},
+				EnforcingLocalOriginSuccessRate: &wrappers.UInt32Value{Value: 0},
+			},
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/pilot/pkg/serviceregistry/kube/controller/controller_test.go b/pilot/pkg/serviceregistry/kube/controller/controller_test.go
index 527555e185..b6602efba4 100644
--- a/pilot/pkg/serviceregistry/kube/controller/controller_test.go
+++ b/pilot/pkg/serviceregistry/kube/controller/controller_test.go
@@ -1505,8 +1505,8 @@ func updateEndpoints(controller *FakeController, name, namespace string, portNam
 
 	// Update endpoint slice as well
 	esps := make([]discovery.EndpointPort, 0)
-	for _, name := range portNames {
-		esps = append(esps, discovery.EndpointPort{Name: &name, Port: &portNum})
+	for i := range portNames {
+		esps = append(esps, discovery.EndpointPort{Name: &portNames[i], Port: &portNum})
 	}
 	endpointSlice := &discovery.EndpointSlice{
 		ObjectMeta: metaV1.ObjectMeta{
diff --git a/pkg/test/framework/components/echo/common/call.go b/pkg/test/framework/components/echo/common/call.go
index 30af3777db..ded8e2f7da 100644
--- a/pkg/test/framework/components/echo/common/call.go
+++ b/pkg/test/framework/components/echo/common/call.go
@@ -194,10 +194,10 @@ func fillInCallOptions(opts *echo.CallOptions) error {
 		} else {
 			// Look up the port.
 			found := false
-			for _, port := range targetPorts {
+			for i, port := range targetPorts {
 				if opts.PortName == port.Name {
 					found = true
-					opts.Port = &port
+					opts.Port = &targetPorts[i]
 					break
 				}
 			}
diff --git a/pkg/test/framework/scope.go b/pkg/test/framework/scope.go
index 321aa88196..799455767a 100644
--- a/pkg/test/framework/scope.go
+++ b/pkg/test/framework/scope.go
@@ -19,6 +19,7 @@ import (
 	"io"
 	"reflect"
 	"sync"
+	"time"
 
 	"github.com/hashicorp/go-multierror"
 
@@ -172,14 +173,25 @@ func (s *scope) waitForDone() {
 }
 
 func (s *scope) dump(ctx resource.Context) {
+	st := time.Now()
+	defer func() {
+		scopes.Framework.Infof("Done dumping scope: %s (%v)", s.id, time.Since(st))
+	}()
 	s.mu.Lock()
 	defer s.mu.Unlock()
 	for _, c := range s.children {
 		c.dump(ctx)
 	}
+	wg := sync.WaitGroup{}
 	for _, c := range s.resources {
 		if d, ok := c.(resource.Dumper); ok {
-			d.Dump(ctx)
+			d := d
+			wg.Add(1)
+			go func() {
+				d.Dump(ctx)
+				wg.Done()
+			}()
 		}
 	}
+	wg.Wait()
 }