Repository: istio/istio (go)

Issue:
istioctl install does not wait for install cni to become ready
istioctl should wait for install-cni container to become ready at installation time.

Pull request:
make sure daemonset has at least one pod ready
Fixes #32887 

[ ] 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. 

[ ] 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/galley/pkg/config/analysis/msg/messages.gen.go b/galley/pkg/config/analysis/msg/messages.gen.go
index 1017a0dc7b..3951b3a847 100755
--- a/galley/pkg/config/analysis/msg/messages.gen.go
+++ b/galley/pkg/config/analysis/msg/messages.gen.go
@@ -119,7 +119,7 @@ var (
 
 	// VirtualServiceIneffectiveMatch defines a diag.MessageType for message "VirtualServiceIneffectiveMatch".
 	// Description: A VirtualService rule match duplicates a match in a previous rule.
-	VirtualServiceIneffectiveMatch = diag.NewMessageType(diag.Info, "IST0131", "VirtualService rule %v match %v is not used (duplicates a match in rule %v).")
+	VirtualServiceIneffectiveMatch = diag.NewMessageType(diag.Info, "IST0131", "VirtualService rule %v match %v is not used (duplicate/overlapping match in rule %v).")
 
 	// VirtualServiceHostNotFoundInGateway defines a diag.MessageType for message "VirtualServiceHostNotFoundInGateway".
 	// Description: Host defined in VirtualService not found in Gateway.
diff --git a/galley/pkg/config/analysis/msg/messages.yaml b/galley/pkg/config/analysis/msg/messages.yaml
index 6c883f844d..f866b2daca 100644
--- a/galley/pkg/config/analysis/msg/messages.yaml
+++ b/galley/pkg/config/analysis/msg/messages.yaml
@@ -343,7 +343,7 @@ messages:
     code: IST0131
     level: Info
     description: "A VirtualService rule match duplicates a match in a previous rule."
-    template: "VirtualService rule %v match %v is not used (duplicates a match in rule %v)."
+    template: "VirtualService rule %v match %v is not used (duplicate/overlapping match in rule %v)."
     url: "https://istio.io/latest/docs/reference/config/analysis/ist0131/"
     args:
       - name: ruleno
diff --git a/operator/pkg/helmreconciler/wait.go b/operator/pkg/helmreconciler/wait.go
index f76cc91f03..63105113b0 100644
--- a/operator/pkg/helmreconciler/wait.go
+++ b/operator/pkg/helmreconciler/wait.go
@@ -28,7 +28,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/fields"
 	"k8s.io/apimachinery/pkg/labels"
-	"k8s.io/apimachinery/pkg/util/intstr"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
@@ -279,18 +278,19 @@ func daemonsetsReady(daemonsets []*appsv1.DaemonSet) (bool, []string) {
 			notReady = append(notReady, "DaemonSet/"+ds.Namespace+"/"+ds.Name)
 		}
 		if ds.Spec.UpdateStrategy.Type == appsv1.RollingUpdateDaemonSetStrategyType {
-			maxUnavailable, err := intstr.GetValueFromIntOrPercent(
-				ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable,
-				int(ds.Status.DesiredNumberScheduled),
-				true)
-			if err != nil {
-				// set max unavailable to the number of desired replicas if the value is invalid
-				maxUnavailable = int(ds.Status.DesiredNumberScheduled)
-			}
-			expectedReady := int(ds.Status.DesiredNumberScheduled) - maxUnavailable
-			if !(int(ds.Status.NumberReady) >= expectedReady) {
+			if ds.Status.DesiredNumberScheduled <= 0 {
+				// If DesiredNumberScheduled less then or equal 0, there some cases:
+				// 1) daemenset is just created
+				// 2) daemonset desired no pod
+				// 3) somebody changed it manually
+				// All the case is not a ready signal
+				scope.Infof("DaemonSet is not ready: %s/%s. Initializing, no pods is running",
+					ds.Namespace, ds.Name)
+				notReady = append(notReady, "DaemonSet/"+ds.Namespace+"/"+ds.Name)
+			} else if ds.Status.NumberReady < ds.Status.DesiredNumberScheduled {
+				// Make sure every node has a ready pod
 				scope.Infof("DaemonSet is not ready: %s/%s. %d out of %d expected pods are ready",
-					ds.Namespace, ds.Name, ds.Status.NumberReady, expectedReady)
+					ds.Namespace, ds.Name, ds.Status.NumberReady, ds.Status.UpdatedNumberScheduled)
 				notReady = append(notReady, "DaemonSet/"+ds.Namespace+"/"+ds.Name)
 			}
 		}
diff --git a/pilot/pkg/serviceregistry/kube/controller/namespacecontroller.go b/pilot/pkg/serviceregistry/kube/controller/namespacecontroller.go
index 6750b03340..3c1feb8376 100644
--- a/pilot/pkg/serviceregistry/kube/controller/namespacecontroller.go
+++ b/pilot/pkg/serviceregistry/kube/controller/namespacecontroller.go
@@ -25,6 +25,7 @@ import (
 	"k8s.io/client-go/tools/cache"
 
 	"istio.io/istio/pkg/kube"
+	"istio.io/istio/pkg/kube/inject"
 	"istio.io/istio/pkg/queue"
 	"istio.io/istio/security/pkg/k8s"
 )
@@ -144,6 +145,13 @@ func (nc *NamespaceController) insertDataForNamespace(ns string) error {
 // On namespace change, update the config map.
 // If terminating, this will be skipped
 func (nc *NamespaceController) namespaceChange(ns *v1.Namespace) error {
+	// skip special kubernetes system namespaces
+	for _, namespace := range inject.IgnoredNamespaces {
+		if ns.Name == namespace {
+			return nil
+		}
+	}
+
 	if ns.Status.Phase != v1.NamespaceTerminating {
 		return nc.insertDataForNamespace(ns.Name)
 	}
diff --git a/pkg/config/validation/validation.go b/pkg/config/validation/validation.go
index d85ea01ccf..4872400afa 100644
--- a/pkg/config/validation/validation.go
+++ b/pkg/config/validation/validation.go
@@ -66,6 +66,9 @@ const (
 	// UnixAddressPrefix is the prefix used to indicate an address is for a Unix Domain socket. It is used in
 	// ServiceEntry.Endpoint.Address message.
 	UnixAddressPrefix = "unix://"
+
+	matchExact  = "exact:"
+	matchPrefix = "prefix:"
 )
 
 var (
@@ -130,6 +133,18 @@ type AnalysisAwareError struct {
 	Parameters []interface{}
 }
 
+type OverlappingForHTTPRoute struct {
+	RouteStr         string
+	MatchStr         string
+	Prefix           string
+	MatchPort        uint32
+	MatchMethod      string
+	MatchAuthority   string
+	MatchHeaders     map[string]string
+	MatchQueryParams map[string]string
+	MatchNonHeaders  map[string]string
+}
+
 var _ error = Validation{}
 
 // WrapError turns an error into a Validation
@@ -1953,7 +1968,7 @@ var ValidateVirtualService = registerValidateFunc("ValidateVirtualService",
 		warnIneffective := func(ruleno, matchno, dupno string) {
 			errs = appendValidation(errs, WrapWarning(&AnalysisAwareError{
 				Type:       "VirtualServiceIneffectiveMatch",
-				Msg:        fmt.Sprintf("virtualService rule %v match %v is not used (duplicates a match in rule %v)", ruleno, matchno, dupno),
+				Msg:        fmt.Sprintf("virtualService rule %v match %v is not used (duplicate/overlapping match in rule %v)", ruleno, matchno, dupno),
 				Parameters: []interface{}{ruleno, matchno, dupno},
 			}))
 		}
@@ -1965,10 +1980,175 @@ var ValidateVirtualService = registerValidateFunc("ValidateVirtualService",
 		return errs.Unwrap()
 	})
 
+func assignExactOrPrefix(exact, prefix string) string {
+	if exact == "" {
+		if prefix != "" {
+			return matchPrefix + prefix
+		}
+	} else {
+		return matchExact + exact
+	}
+	return ""
+}
+
+// genMatchHTTPRoutes build the match rules into struct OverlappingForHTTPRoute
+// based on particular HTTPMatchRequest, according to comments on https://github.com/istio/istio/pull/32701
+// only support Match's port, method, authority, headers, query params and nonheaders for now.
+func genMatchHTTPRoutes(route *networking.HTTPRoute, match *networking.HTTPMatchRequest,
+	rulen, matchn int) (matchHTTPRoutes *OverlappingForHTTPRoute) {
+	// skip current match if no match field for current route
+	if match == nil {
+		return nil
+	}
+	// skip current match if no URI field
+	if match.Uri == nil {
+		return nil
+	}
+	// store all httproute with prefix match uri
+	tmpPrefix := match.Uri.GetPrefix()
+	if tmpPrefix != "" {
+		// set Method
+		methodExact := match.Method.GetExact()
+		methodPrefix := match.Method.GetPrefix()
+		methodMatch := assignExactOrPrefix(methodExact, methodPrefix)
+		// if no method information, it should be GET by default
+		if methodMatch == "" {
+			methodMatch = matchExact + "GET"
+		}
+
+		// set Authority
+		authorityExact := match.Authority.GetExact()
+		authorityPrefix := match.Authority.GetPrefix()
+		authorityMatch := assignExactOrPrefix(authorityExact, authorityPrefix)
+
+		// set Headers
+		headerMap := make(map[string]string)
+		for hkey, hvalue := range match.Headers {
+			hvalueExact := hvalue.GetExact()
+			hvaluePrefix := hvalue.GetPrefix()
+			hvalueMatch := assignExactOrPrefix(hvalueExact, hvaluePrefix)
+			headerMap[hkey] = hvalueMatch
+		}
+
+		// set QueryParams
+		QPMap := make(map[string]string)
+		for qpkey, qpvalue := range match.QueryParams {
+			qpvalueExact := qpvalue.GetExact()
+			qpvaluePrefix := qpvalue.GetPrefix()
+			qpvalueMatch := assignExactOrPrefix(qpvalueExact, qpvaluePrefix)
+			QPMap[qpkey] = qpvalueMatch
+		}
+
+		// set WithoutHeaders
+		noHeaderMap := make(map[string]string)
+		for nhkey, nhvalue := range match.WithoutHeaders {
+			nhvalueExact := nhvalue.GetExact()
+			nhvaluePrefix := nhvalue.GetPrefix()
+			nhvalueMatch := assignExactOrPrefix(nhvalueExact, nhvaluePrefix)
+			noHeaderMap[nhkey] = nhvalueMatch
+		}
+
+		matchHTTPRoutes = &OverlappingForHTTPRoute{
+			routeName(route, rulen),
+			requestName(match, matchn),
+			tmpPrefix,
+			match.Port,
+			methodMatch,
+			authorityMatch,
+			headerMap,
+			QPMap,
+			noHeaderMap,
+		}
+		return
+	}
+	return nil
+}
+
+// coveredValidation validate the overlapping match between two instance of OverlappingForHTTPRoute
+func coveredValidation(vA, vB *OverlappingForHTTPRoute) bool {
+	// check the URI overlapping match, such as vB.Prefix is '/debugs' and vA.Prefix is '/debug'
+	if strings.HasPrefix(vB.Prefix, vA.Prefix) {
+		// check the port field
+		if vB.MatchPort != vA.MatchPort {
+			return false
+		}
+
+		// check the match method
+		if strings.Compare(vA.MatchMethod, vB.MatchMethod) != 0 {
+			if !strings.HasPrefix(vA.MatchMethod, vB.MatchMethod) {
+				return false
+			}
+		}
+
+		// check the match authority
+		if strings.Compare(vA.MatchAuthority, vB.MatchAuthority) != 0 {
+			if !strings.HasPrefix(vA.MatchAuthority, vB.MatchAuthority) {
+				return false
+			}
+		}
+
+		// check the match Headers
+		vAHeaderLen := len(vA.MatchHeaders)
+		vBHeaderLen := len(vB.MatchHeaders)
+		if vAHeaderLen != vBHeaderLen {
+			return false
+		}
+		for hdKey, hdValue := range vA.MatchHeaders {
+			vBhdValue, ok := vB.MatchHeaders[hdKey]
+			if !ok {
+				return false
+			} else if strings.Compare(hdValue, vBhdValue) != 0 {
+				if !strings.HasPrefix(hdValue, vBhdValue) {
+					return false
+				}
+			}
+		}
+
+		// check the match QueryParams
+		vAQPLen := len(vA.MatchQueryParams)
+		vBQPLen := len(vB.MatchQueryParams)
+		if vAQPLen != vBQPLen {
+			return false
+		}
+		for qpKey, qpValue := range vA.MatchQueryParams {
+			vBqpValue, ok := vB.MatchQueryParams[qpKey]
+			if !ok {
+				return false
+			} else if strings.Compare(qpValue, vBqpValue) != 0 {
+				if !strings.HasPrefix(qpValue, vBqpValue) {
+					return false
+				}
+			}
+		}
+
+		// check the match NonHeaders
+		vANonHDLen := len(vA.MatchNonHeaders)
+		vBNonHDLen := len(vB.MatchNonHeaders)
+		if vANonHDLen != vBNonHDLen {
+			return false
+		}
+		for nhKey, nhValue := range vA.MatchNonHeaders {
+			vBnhValue, ok := vB.MatchNonHeaders[nhKey]
+			if !ok {
+				return false
+			} else if strings.Compare(nhValue, vBnhValue) != 0 {
+				if !strings.HasPrefix(nhValue, vBnhValue) {
+					return false
+				}
+			}
+		}
+	} else {
+		// no URI overlapping match
+		return false
+	}
+	return true
+}
+
 func analyzeUnreachableHTTPRules(routes []*networking.HTTPRoute,
 	reportUnreachable func(ruleno, reason string), reportIneffective func(ruleno, matchno, dupno string)) {
 	matchesEncountered := make(map[string]int)
 	emptyMatchEncountered := -1
+	var matchHTTPRoutes []*OverlappingForHTTPRoute
 	for rulen, route := range routes {
 		if route == nil {
 			continue
@@ -1987,14 +2167,49 @@ func analyzeUnreachableHTTPRules(routes []*networking.HTTPRoute,
 			if ok {
 				reportIneffective(routeName(route, rulen), requestName(match, matchn), routeName(routes[dupn], dupn))
 				duplicateMatches++
+				// no need to handle for totally duplicated match rules
+				continue
 			} else {
 				matchesEncountered[asJSON(match)] = rulen
 			}
+			// build the match rules into struct OverlappingForHTTPRoute based on current match
+			matchHTTPRoute := genMatchHTTPRoutes(route, match, rulen, matchn)
+			if matchHTTPRoute != nil {
+				matchHTTPRoutes = append(matchHTTPRoutes, matchHTTPRoute)
+			}
 		}
 		if duplicateMatches == len(route.Match) {
 			reportUnreachable(routeName(route, rulen), "all matches used by prior rules")
 		}
 	}
+
+	// at least 2 prefix matched routes for overlapping match validation
+	if len(matchHTTPRoutes) > 1 {
+		// check the overlapping match from the first prefix information
+		for routeIndex, routePrefix := range matchHTTPRoutes {
+			for rIndex := routeIndex + 1; rIndex < len(matchHTTPRoutes); rIndex++ {
+				// exclude the duplicate-match cases which have been validated above
+				if strings.Compare(matchHTTPRoutes[rIndex].Prefix, routePrefix.Prefix) == 0 {
+					continue
+				}
+				// Valid from A to B for matchHTTPRoutes
+				isAtoBCover := coveredValidation(routePrefix, matchHTTPRoutes[rIndex])
+				if isAtoBCover {
+					prefixMatchA := matchHTTPRoutes[rIndex].MatchStr + " of prefix " + matchHTTPRoutes[rIndex].Prefix
+					prefixMatchB := routePrefix.MatchStr + " of prefix " + routePrefix.Prefix + " on " + routePrefix.RouteStr
+					reportIneffective(matchHTTPRoutes[rIndex].RouteStr, prefixMatchA, prefixMatchB)
+				}
+
+				// Valid from B to A for matchHTTPRoutes
+				isBtoACover := coveredValidation(matchHTTPRoutes[rIndex], routePrefix)
+				if isBtoACover {
+					prefixMatchA := routePrefix.MatchStr + " of prefix " + routePrefix.Prefix
+					prefixMatchB := matchHTTPRoutes[rIndex].MatchStr + " of prefix " + matchHTTPRoutes[rIndex].Prefix + " on " + matchHTTPRoutes[rIndex].RouteStr
+					reportIneffective(routePrefix.RouteStr, prefixMatchA, prefixMatchB)
+				}
+			}
+		}
+	}
 }
 
 // NOTE: This method identical to analyzeUnreachableHTTPRules.
diff --git a/pkg/kube/client.go b/pkg/kube/client.go
index ae346536f8..acc021dae6 100644
--- a/pkg/kube/client.go
+++ b/pkg/kube/client.go
@@ -464,12 +464,17 @@ func (c *client) RunAndWait(stop <-chan struct{}) {
 		// WaitForCacheSync will virtually never be synced on the first call, as its called immediately after Start()
 		// This triggers a 100ms delay per call, which is often called 2-3 times in a test, delaying tests.
 		// Instead, we add an aggressive sync polling
-		fastWaitForCacheSync(c.kubeInformer)
-		fastWaitForCacheSyncDynamic(c.dynamicInformer)
-		fastWaitForCacheSyncDynamic(c.metadataInformer)
-		fastWaitForCacheSync(c.istioInformer)
-		fastWaitForCacheSync(c.gatewayapiInformer)
-		_ = wait.PollImmediate(time.Microsecond, wait.ForeverTestTimeout, func() (bool, error) {
+		fastWaitForCacheSync(stop, c.kubeInformer)
+		fastWaitForCacheSyncDynamic(stop, c.dynamicInformer)
+		fastWaitForCacheSyncDynamic(stop, c.metadataInformer)
+		fastWaitForCacheSync(stop, c.istioInformer)
+		fastWaitForCacheSync(stop, c.gatewayapiInformer)
+		_ = wait.PollImmediate(time.Microsecond*100, wait.ForeverTestTimeout, func() (bool, error) {
+			select {
+			case <-stop:
+				return false, fmt.Errorf("channel closed")
+			default:
+			}
 			if c.informerWatchesPending.Load() == 0 {
 				return true, nil
 			}
@@ -503,10 +508,15 @@ type dynamicInformerSync interface {
 
 // Wait for cache sync immediately, rather than with 100ms delay which slows tests
 // See https://github.com/kubernetes/kubernetes/issues/95262#issuecomment-703141573
-func fastWaitForCacheSync(informerFactory reflectInformerSync) {
+func fastWaitForCacheSync(stop <-chan struct{}, informerFactory reflectInformerSync) {
 	returnImmediately := make(chan struct{})
 	close(returnImmediately)
-	_ = wait.PollImmediate(time.Microsecond, wait.ForeverTestTimeout, func() (bool, error) {
+	_ = wait.PollImmediate(time.Microsecond*100, wait.ForeverTestTimeout, func() (bool, error) {
+		select {
+		case <-stop:
+			return false, fmt.Errorf("channel closed")
+		default:
+		}
 		for _, synced := range informerFactory.WaitForCacheSync(returnImmediately) {
 			if !synced {
 				return false, nil
@@ -516,10 +526,15 @@ func fastWaitForCacheSync(informerFactory reflectInformerSync) {
 	})
 }
 
-func fastWaitForCacheSyncDynamic(informerFactory dynamicInformerSync) {
+func fastWaitForCacheSyncDynamic(stop <-chan struct{}, informerFactory dynamicInformerSync) {
 	returnImmediately := make(chan struct{})
 	close(returnImmediately)
-	_ = wait.PollImmediate(time.Microsecond, wait.ForeverTestTimeout, func() (bool, error) {
+	_ = wait.PollImmediate(time.Microsecond*100, wait.ForeverTestTimeout, func() (bool, error) {
+		select {
+		case <-stop:
+			return false, fmt.Errorf("channel closed")
+		default:
+		}
 		for _, synced := range informerFactory.WaitForCacheSync(returnImmediately) {
 			if !synced {
 				return false, nil
diff --git a/pkg/kube/inject/initializer.go b/pkg/kube/inject/initializer.go
index 7396ce599c..1f5b5dbcf0 100644
--- a/pkg/kube/inject/initializer.go
+++ b/pkg/kube/inject/initializer.go
@@ -25,7 +25,7 @@ import (
 	"istio.io/istio/pkg/config/constants"
 )
 
-var ignoredNamespaces = []string{
+var IgnoredNamespaces = []string{
 	constants.KubeSystemNamespace,
 	constants.KubePublicNamespace,
 	constants.KubeNodeLeaseNamespace,
diff --git a/pkg/kube/inject/inject.go b/pkg/kube/inject/inject.go
index 5afb8cc075..417609d18b 100644
--- a/pkg/kube/inject/inject.go
+++ b/pkg/kube/inject/inject.go
@@ -669,7 +669,7 @@ func IntoObject(injector Injector, sidecarTemplate Templates, valuesConfig strin
 		return nil, err
 	}
 	if patchBytes == nil {
-		if !injectRequired(ignoredNamespaces, &Config{Policy: InjectionPolicyEnabled}, &pod.Spec, pod.ObjectMeta) {
+		if !injectRequired(IgnoredNamespaces, &Config{Policy: InjectionPolicyEnabled}, &pod.Spec, pod.ObjectMeta) {
 			warningHandler(fmt.Sprintf("===> Skipping injection because %q has sidecar injection disabled\n", name))
 			return out, nil
 		}
diff --git a/pkg/kube/inject/webhook.go b/pkg/kube/inject/webhook.go
index 1cbe5c680b..e1af6c8d42 100644
--- a/pkg/kube/inject/webhook.go
+++ b/pkg/kube/inject/webhook.go
@@ -722,7 +722,7 @@ func (wh *Webhook) inject(ar *kube.AdmissionReview, path string) *kube.Admission
 	log.Debugf("OldObject: %v", string(req.OldObject.Raw))
 
 	wh.mu.RLock()
-	if !injectRequired(ignoredNamespaces, wh.Config, &pod.Spec, pod.ObjectMeta) {
+	if !injectRequired(IgnoredNamespaces, wh.Config, &pod.Spec, pod.ObjectMeta) {
 		log.Infof("Skipping %s/%s due to policy check", pod.ObjectMeta.Namespace, podName)
 		totalSkippedInjections.Increment()
 		wh.mu.RUnlock()


Test patch:
diff --git a/galley/pkg/config/analysis/analyzers/analyzers_test.go b/galley/pkg/config/analysis/analyzers/analyzers_test.go
index 0eb28966ba..63d132e131 100644
--- a/galley/pkg/config/analysis/analyzers/analyzers_test.go
+++ b/galley/pkg/config/analysis/analyzers/analyzers_test.go
@@ -415,6 +415,7 @@ var testGrid = []testCase{
 		name: "dupmatches",
 		inputFiles: []string{
 			"testdata/virtualservice_dupmatches.yaml",
+			"testdata/virtualservice_overlappingmatches.yaml",
 		},
 		analyzer: schemaValidation.CollectionValidationAnalyzer(collections.IstioNetworkingV1Alpha3Virtualservices),
 		expected: []message{
@@ -431,6 +432,11 @@ var testGrid = []testCase{
 			{msg.VirtualServiceUnreachableRule, "VirtualService tls-routing.none"},
 			{msg.VirtualServiceIneffectiveMatch, "VirtualService tls-routing-almostmatch.none"},
 			{msg.VirtualServiceIneffectiveMatch, "VirtualService tls-routing.none"},
+
+			{msg.VirtualServiceIneffectiveMatch, "VirtualService non-method-get"},
+			{msg.VirtualServiceIneffectiveMatch, "VirtualService overlapping-in-single-match"},
+			{msg.VirtualServiceIneffectiveMatch, "VirtualService overlapping-in-two-matches"},
+			{msg.VirtualServiceIneffectiveMatch, "VirtualService overlapping-mathes-with-different-methods"},
 		},
 	},
 	{
diff --git a/galley/pkg/config/analysis/analyzers/testdata/virtualservice_overlappingmatches.yaml b/galley/pkg/config/analysis/analyzers/testdata/virtualservice_overlappingmatches.yaml
new file mode 100644
index 0000000000..437c466307
--- /dev/null
+++ b/galley/pkg/config/analysis/analyzers/testdata/virtualservice_overlappingmatches.yaml
@@ -0,0 +1,121 @@
+apiVersion: networking.istio.io/v1alpha3
+kind: VirtualService
+metadata:
+  name: non-method-get
+spec:
+  hosts:
+  - sample.baz.svc.cluster.local
+  http:
+  - name: "send product to sample.foo"
+    match:
+    - uri:
+        prefix: "/api/v1/product"
+    - uri:
+        prefix: "/api/v1/products"
+      method:
+        exact: GET
+    route:
+    - destination:
+        host: sample.foo.svc.cluster.local
+---
+apiVersion: networking.istio.io/v1alpha3
+kind: VirtualService
+metadata:
+  name: uri-with-prefix-exact 
+spec:
+  hosts:
+  - sample.baz.svc.cluster.local
+  http:
+  - name: "send product to sample.foo"
+    match:
+    - uri:
+        prefix: "/"
+    - uri:
+        exact: "/"
+      method:
+        exact: GET
+    route:
+    - destination:
+        host: sample.foo.svc.cluster.local
+---
+apiVersion: networking.istio.io/v1alpha3
+kind: VirtualService
+metadata:
+  name: overlapping-in-single-match
+spec:
+  hosts:
+  - sample.baz.svc.cluster.local
+  http:
+  - name: "send product to sample.foo"
+    match:
+    - uri:
+        prefix: "/api/v1/product"
+      method:
+        exact: GET
+    - uri:
+        prefix: "/api/v1/products"
+      method:
+        exact: GET
+    route:
+    - destination:
+        host: sample.foo.svc.cluster.local
+---
+apiVersion: networking.istio.io/v1alpha3
+kind: VirtualService
+metadata:
+  name: overlapping-in-two-matches
+spec:
+  hosts:
+  - sample.baz.svc.cluster.local
+  http:
+  - name: "send product to sample.foo"
+    match:
+    - uri:
+        prefix: "/api/v1/product"
+      method:
+        exact: GET
+    route:
+    - destination:
+        host: sample.foo.svc.cluster.local
+  - name: "send products to sample.bar"
+    match:
+    - uri:
+        prefix: "/api/v1/products"
+      method:
+        exact: GET
+    route:
+    - destination:
+        host: sample.bar.svc.cluster.local
+        subset: v1
+---
+apiVersion: networking.istio.io/v1alpha3
+kind: VirtualService
+metadata:
+  name: overlapping-mathes-with-different-methods
+spec:
+  hosts:
+  - sample.baz.svc.cluster.local
+  http:
+  - name: "send product to sample.foo"
+    match:
+    - uri:
+        prefix: "/api/v1/prod"
+      method:
+        exact: GET
+    route:
+    - destination:
+        host: sample.foo.svc.cluster.local
+  - name: "send products to sample.bar"
+    match:
+    - uri:
+        prefix: "/api/v1/product"
+      method:
+        exact: GET
+    - uri:
+        prefix: "/api/v1/products"
+      method:
+        exact: POST
+    route:
+    - destination:
+        host: sample.bar.svc.cluster.local
+        subset: v1
\ No newline at end of file
diff --git a/pilot/pkg/serviceregistry/kube/controller/namespacecontroller_test.go b/pilot/pkg/serviceregistry/kube/controller/namespacecontroller_test.go
index b9cdfe632e..695e011521 100644
--- a/pilot/pkg/serviceregistry/kube/controller/namespacecontroller_test.go
+++ b/pilot/pkg/serviceregistry/kube/controller/namespacecontroller_test.go
@@ -27,6 +27,7 @@ import (
 	listerv1 "k8s.io/client-go/listers/core/v1"
 
 	"istio.io/istio/pkg/kube"
+	"istio.io/istio/pkg/kube/inject"
 	"istio.io/istio/pkg/test/util/retry"
 	"istio.io/istio/security/pkg/k8s"
 )
@@ -57,6 +58,11 @@ func TestNamespaceController(t *testing.T) {
 
 	deleteConfigMap(t, client, "foo")
 	expectConfigMap(t, nc.configmapLister, "foo", testdata)
+
+	for _, namespace := range inject.IgnoredNamespaces {
+		createNamespace(t, client, namespace, testdata)
+		expectConfigMapNotExist(t, nc.configmapLister, namespace)
+	}
 }
 
 func deleteConfigMap(t *testing.T, client kubernetes.Interface, ns string) {
@@ -101,3 +107,15 @@ func expectConfigMap(t *testing.T, client listerv1.ConfigMapLister, ns string, d
 		return nil
 	}, retry.Timeout(time.Second*2))
 }
+
+func expectConfigMapNotExist(t *testing.T, client listerv1.ConfigMapLister, ns string) {
+	t.Helper()
+	err := retry.Until(func() bool {
+		_, err := client.ConfigMaps(ns).Get(CACertNamespaceConfigMap)
+		return err == nil
+	}, retry.Timeout(time.Second*2))
+
+	if err == nil {
+		t.Fatalf("%s namespace should not have istio-ca-root-cert configmap.", ns)
+	}
+}
diff --git a/pkg/kube/inject/webhook_test.go b/pkg/kube/inject/webhook_test.go
index 61c404d12e..0a0e5f7033 100644
--- a/pkg/kube/inject/webhook_test.go
+++ b/pkg/kube/inject/webhook_test.go
@@ -462,7 +462,7 @@ func TestInjectRequired(t *testing.T) {
 	}
 
 	for _, c := range cases {
-		if got := injectRequired(ignoredNamespaces, c.config, c.podSpec, c.meta); got != c.want {
+		if got := injectRequired(IgnoredNamespaces, c.config, c.podSpec, c.meta); got != c.want {
 			t.Errorf("injectRequired(%v, %v) got %v want %v", c.config, c.meta, got, c.want)
 		}
 	}