Repository: linkerd/linkerd2
Pull request #4058 (2020-02-14 17:05:37)

bin/helm-build automatically updates version in values.yaml
Have the Helm charts building script (`bin/helm-build`) update the
linkerd version in the `values.yaml` files according to the tagged
version, thus removing the need of doing this manually on every release.

This is akin to the update we do in `version.go` at CLI build time.

Note that `shellcheck` is issuing some warnings about this script, but
that's on code that was already there, so that will be handled in an
followup PR.

alpeb: Still needs a bit more work to avoid breaking the CLI :grimacing: 
alpeb: Alright, ready again for review :+1: 

Patch:
diff --git a/BUILD.md b/BUILD.md
index d76d3ed8d..b5c453952 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -394,6 +394,10 @@ chart consists of the Linkerd proxy specification, which is used by the proxy
 injector to inject the proxy container. Both charts depend on the partials
 subchart which can be found in the [`charts/partials`](charts/partials) folder.
 
+Note that the `charts/linkerd2/values.yaml` file contains a placeholder
+`{version}` that you need to replace with an appropriate string (like
+`edge-20.2.2`) before proceeding.
+
 During development, please use the [`bin/helm`](bin/helm) wrapper script to
 invoke the Helm commands. For example,
 
diff --git a/bin/helm-build b/bin/helm-build
index 401895781..e5679f1c7 100755
--- a/bin/helm-build
+++ b/bin/helm-build
@@ -2,8 +2,18 @@
 
 set -e
 
+setValues() {
+    sed -i "s/$1/$2/" charts/linkerd2/values.yaml
+    sed -i "s/$1/$2/" charts/linkerd2-cni/values.yaml
+}
+
+showErr() {
+  printf "Error on exit:\n  Exit code: %d\n  Failed command: \"%s\"\n" $? "$BASH_COMMAND"
+  setValues $fullVersion "{version}"
+}
+
 # trap the last failed command
-trap 'printf "Error on exit:\n  Exit code: $?\n  Failed command: \"$BASH_COMMAND\"\n"' ERR
+trap 'showErr' ERR
 
 bindir=$( cd "${BASH_SOURCE[0]%/*}" && pwd )
 rootdir=$( cd "$bindir"/.. && pwd )
@@ -27,10 +37,18 @@ if [ "$1" = package ]; then
         echo 'Version tag is malformed'
         exit 1
     fi
+    fullVersion=${BASH_REMATCH[0]}
     repo=${BASH_REMATCH[1]}
     version=${BASH_REMATCH[2]}
+
+    # set version in Values files
+    setValues "{version}" $fullVersion
+
     "$bindir"/helm --version $version --app-version $tag -d "$rootdir"/target/helm package "$rootdir"/charts/linkerd2
     "$bindir"/helm --version $version --app-version $tag -d "$rootdir"/target/helm package "$rootdir"/charts/linkerd2-cni
     mv "$rootdir"/target/helm/index-pre.yaml "$rootdir"/target/helm/index-pre-$version.yaml
     "$bindir"/helm repo index --url "https://helm.linkerd.io/$repo/" --merge "$rootdir"/target/helm/index-pre-$version.yaml "$rootdir"/target/helm
+
+    # restore version in Values files
+    setValues $fullVersion "{version}"
 fi
diff --git a/charts/linkerd2-cni/values.yaml b/charts/linkerd2-cni/values.yaml
index adcc92b11..31ebb294d 100644
--- a/charts/linkerd2-cni/values.yaml
+++ b/charts/linkerd2-cni/values.yaml
@@ -6,7 +6,7 @@ ignoreInboundPorts: ""
 ignoreOutboundPorts: ""
 createdByAnnotation: linkerd.io/created-by
 cniPluginImage:   "gcr.io/linkerd-io/cni-plugin"
-cniPluginVersion: stable-2.7.0
+cniPluginVersion: {version}
 logLevel:         info
 portsToRedirect:  ""
 proxyUID:         2102
diff --git a/charts/linkerd2/values.yaml b/charts/linkerd2/values.yaml
index f8b609205..a0048698f 100644
--- a/charts/linkerd2/values.yaml
+++ b/charts/linkerd2/values.yaml
@@ -11,7 +11,7 @@ global:
   controlPlaneTracing: false
 
   # control plane version. See Proxy section for proxy version
-  linkerdVersion: &linkerd_version stable-2.7.0
+  linkerdVersion: &linkerd_version {version}
 
   namespace: linkerd
 
diff --git a/cli/cmd/root.go b/cli/cmd/root.go
index f54c868e4..e2881a899 100644
--- a/cli/cmd/root.go
+++ b/cli/cmd/root.go
@@ -92,7 +92,7 @@ var RootCmd = &cobra.Command{
 }
 
 func init() {
-	RootCmd.PersistentFlags().StringVarP(&controlPlaneNamespace, "linkerd-namespace", "l", defaultNamespace, "Namespace in which Linkerd is installed [$LINKERD_NAMESPACE]")
+	RootCmd.PersistentFlags().StringVarP(&controlPlaneNamespace, "linkerd-namespace", "L", defaultNamespace, "Namespace in which Linkerd is installed [$LINKERD_NAMESPACE]")
 	RootCmd.PersistentFlags().StringVarP(&cniNamespace, "cni-namespace", "", defaultCNINamespace, "Namespace in which the Linkerd CNI plugin is installed")
 	RootCmd.PersistentFlags().StringVar(&kubeconfigPath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests")
 	RootCmd.PersistentFlags().StringVar(&kubeContext, "context", "", "Name of the kubeconfig context to use")
diff --git a/cli/cmd/stat.go b/cli/cmd/stat.go
index 15ce29e66..fdefa4729 100644
--- a/cli/cmd/stat.go
+++ b/cli/cmd/stat.go
@@ -25,6 +25,7 @@ type statOptions struct {
 	fromNamespace string
 	fromResource  string
 	allNamespaces bool
+	labelSelector string
 }
 
 type indexedResults struct {
@@ -41,6 +42,7 @@ func newStatOptions() *statOptions {
 		fromNamespace:   "",
 		fromResource:    "",
 		allNamespaces:   false,
+		labelSelector:   "",
 	}
 }
 
@@ -181,7 +183,7 @@ If no resource name is specified, displays stats about all resources of the spec
 	cmd.PersistentFlags().StringVar(&options.fromNamespace, "from-namespace", options.fromNamespace, "Sets the namespace used from lookup the \"--from\" resource; by default the current \"--namespace\" is used")
 	cmd.PersistentFlags().BoolVarP(&options.allNamespaces, "all-namespaces", "A", options.allNamespaces, "If present, returns stats across all namespaces, ignoring the \"--namespace\" flag")
 	cmd.PersistentFlags().StringVarP(&options.outputFormat, "output", "o", options.outputFormat, "Output format; one of: \"table\" or \"json\" or \"wide\"")
-
+	cmd.PersistentFlags().StringVarP(&options.labelSelector, "selector", "l", options.labelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='")
 	return cmd
 }
 
@@ -667,6 +669,7 @@ func buildStatSummaryRequests(resources []string, options *statOptions) ([]*pb.S
 			FromType:      fromRes.Type,
 			FromNamespace: options.fromNamespace,
 			TCPStats:      true,
+			LabelSelector: options.labelSelector,
 		}
 
 		req, err := util.BuildStatSummaryRequest(requestParams)
diff --git a/cli/cmd/top.go b/cli/cmd/top.go
index 1538c57a9..0708ee3c6 100644
--- a/cli/cmd/top.go
+++ b/cli/cmd/top.go
@@ -251,8 +251,9 @@ func newTopTable() *topTable {
 }
 
 const (
-	headerHeight  = 3
+	headerHeight  = 4
 	columnSpacing = 2
+	xOffset       = 5
 )
 
 func newTopOptions() *topOptions {
@@ -407,17 +408,17 @@ func getTrafficByResourceFromAPI(k8sAPI *k8s.KubernetesAPI, req *pb.TapByResourc
 	//       processEvents() && renderTable()
 	closing := make(chan struct{}, 1)
 	done := make(chan struct{})
+	horizontalScroll := make(chan int)
 
-	go pollInput(closing)
+	go pollInput(done, horizontalScroll)
 	go recvEvents(reader, eventCh, closing)
 	go processEvents(eventCh, requestCh, done)
 
 	go func() {
 		<-closing
-		close(done)
 	}()
 
-	renderTable(table, requestCh, done)
+	renderTable(table, requestCh, done, horizontalScroll)
 
 	return nil
 }
@@ -483,20 +484,29 @@ func processEvents(eventCh <-chan pb.TapEvent, requestCh chan<- topRequest, done
 	}
 }
 
-func pollInput(closing chan<- struct{}) {
+func pollInput(done chan<- struct{}, horizontalScroll chan int) {
 	for {
 		switch ev := termbox.PollEvent(); ev.Type {
 		case termbox.EventKey:
 			if ev.Ch == 'q' || ev.Key == termbox.KeyCtrlC {
-				closing <- struct{}{}
+				close(done)
 				return
 			}
+			if ev.Ch == 'a' || ev.Key == termbox.KeyArrowLeft {
+				horizontalScroll <- xOffset
+			}
+			if ev.Ch == 'd' || ev.Key == termbox.KeyArrowRight {
+				horizontalScroll <- -xOffset
+			}
 		}
 	}
 }
 
-func renderTable(table *topTable, requestCh <-chan topRequest, done <-chan struct{}) {
+func renderTable(table *topTable, requestCh <-chan topRequest, done <-chan struct{}, horizontalScroll chan int) {
+	scrollpos := 0
 	ticker := time.NewTicker(100 * time.Millisecond)
+	width, _ := termbox.Size()
+	tablewidth := table.tableWidthCalc()
 
 	for {
 		select {
@@ -506,10 +516,16 @@ func renderTable(table *topTable, requestCh <-chan topRequest, done <-chan struc
 			table.insert(req)
 		case <-ticker.C:
 			termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
+			width, _ = termbox.Size()
 			table.adjustColumnWidths()
-			table.renderHeaders()
-			table.renderBody()
+			tablewidth = table.tableWidthCalc()
+			table.renderHeaders(scrollpos)
+			table.renderBody(scrollpos)
 			termbox.Flush()
+		case offset := <-horizontalScroll:
+			if (offset > 0 && scrollpos < 0) || (offset < 0 && scrollpos > (width-tablewidth)) {
+				scrollpos = scrollpos + offset
+			}
 		}
 	}
 }
@@ -614,9 +630,10 @@ func stripPort(address string) string {
 	return strings.Split(address, ":")[0]
 }
 
-func (t *topTable) renderHeaders() {
+func (t *topTable) renderHeaders(scrollpos int) {
 	tbprint(0, 0, "(press q to quit)")
-	x := 0
+	tbprint(0, 1, "(press a/LeftArrowKey to scroll left, d/RightArrowKey to scroll right)")
+	x := scrollpos
 	for _, col := range t.columns {
 		if !col.display {
 			continue
@@ -630,6 +647,14 @@ func (t *topTable) renderHeaders() {
 	}
 }
 
+func (t *topTable) tableWidthCalc() int {
+	tablewidth := 0
+	for i := range t.columns {
+		tablewidth = tablewidth + t.columns[i].width + columnSpacing
+	}
+	return tablewidth - columnSpacing
+}
+
 func (t *topTable) adjustColumnWidths() {
 	for i, col := range t.columns {
 		if !col.flexible {
@@ -645,13 +670,13 @@ func (t *topTable) adjustColumnWidths() {
 	}
 }
 
-func (t *topTable) renderBody() {
+func (t *topTable) renderBody(scrollpos int) {
 	sort.SliceStable(t.rows, func(i, j int) bool {
 		return t.rows[i].count > t.rows[j].count
 	})
 
 	for i, row := range t.rows {
-		x := 0
+		x := scrollpos
 
 		for _, col := range t.columns {
 			if !col.display {
diff --git a/cli/cmd/upgrade.go b/cli/cmd/upgrade.go
index 19470eb24..8ad243f62 100644
--- a/cli/cmd/upgrade.go
+++ b/cli/cmd/upgrade.go
@@ -27,11 +27,11 @@ import (
 )
 
 const (
-	okMessage              = "You're on your way to upgrading Linkerd!"
-	controlPlaneMessage    = "Don't forget to run `linkerd upgrade control-plane`!"
-	visitMessage           = "Visit this URL for further instructions: https://linkerd.io/upgrade/#nextsteps"
-	failMessage            = "For troubleshooting help, visit: https://linkerd.io/upgrade/#troubleshooting\n"
-	trustRootChangeMessage = "Rotating the trust anchors will affect existing proxies\nSee https://linkerd.io/2/tasks/rotating_identity_certificates/ for more information"
+	okMessage                = "You're on your way to upgrading Linkerd!"
+	controlPlaneMessage      = "Don't forget to run `linkerd upgrade control-plane`!"
+	visitMessage             = "Visit this URL for further instructions: https://linkerd.io/upgrade/#nextsteps"
+	failMessage              = "For troubleshooting help, visit: https://linkerd.io/upgrade/#troubleshooting\n"
+	trustAnchorChangeMessage = "Rotating the trust anchors will affect existing proxies\nSee https://linkerd.io/2/tasks/rotating_identity_certificates/ for more information"
 )
 
 type upgradeOptions struct {
@@ -67,7 +67,7 @@ func (options *upgradeOptions) upgradeOnlyFlagSet() *pflag.FlagSet {
 	)
 	flags.BoolVar(
 		&options.force, "force", options.force,
-		"Force upgrade operation even when issuer certificate does not work with the roots of all proxies",
+		"Force upgrade operation even when issuer certificate does not work with the trust anchors of all proxies",
 	)
 
 	return flags
@@ -197,7 +197,7 @@ func upgradeRunE(options *upgradeOptions, stage string, flags *pflag.FlagSet) er
 	buf.WriteTo(os.Stdout)
 
 	if options.identityOptions.trustPEMFile != "" {
-		fmt.Fprintf(os.Stderr, "\n%s %s\n", warnStatus, trustRootChangeMessage)
+		fmt.Fprintf(os.Stderr, "\n%s %s\n", warnStatus, trustAnchorChangeMessage)
 	}
 
 	fmt.Fprintf(os.Stderr, "\n%s %s\n", okStatus, okMessage)
@@ -397,10 +397,10 @@ func ensureIssuerCertWorksWithAllProxies(k kubernetes.Interface, cred *tls.Cred)
 		return err
 	}
 	for _, pod := range meshedPods {
-		roots, err := tls.DecodePEMCertPool(pod.Anchors)
+		anchors, err := tls.DecodePEMCertPool(pod.Anchors)
 
-		if roots != nil {
-			err = cred.Verify(roots, "", time.Time{})
+		if anchors != nil {
+			err = cred.Verify(anchors, "", time.Time{})
 		}
 
 		if err != nil {
@@ -409,7 +409,7 @@ func ensureIssuerCertWorksWithAllProxies(k kubernetes.Interface, cred *tls.Cred)
 	}
 
 	if len(problematicPods) > 0 {
-		errorMessageHeader := "You are attempting to use an issuer certificate which does not validate against the trust roots of the following pods:"
+		errorMessageHeader := "You are attempting to use an issuer certificate which does not validate against the trust anchors of the following pods:"
 		errorMessageFooter := "These pods do not have the current trust bundle and must be restarted.  Use the --force flag to proceed anyway (this will likely prevent those pods from sending or receiving traffic)."
 		return fmt.Errorf("%s\n\t%s\n%s", errorMessageHeader, strings.Join(problematicPods, "\n\t"), errorMessageFooter)
 	}
@@ -460,7 +460,7 @@ func (options *upgradeOptions) fetchIdentityValues(k kubernetes.Interface, idctx
 
 	cred, err := issuerData.VerifyAndBuildCreds("")
 	if err != nil {
-		return nil, fmt.Errorf("issuer certificate does not work with the provided roots: %s\nFor more information: https://linkerd.io/2/tasks/rotating_identity_certificates/", err)
+		return nil, fmt.Errorf("issuer certificate does not work with the provided anchors: %s\nFor more information: https://linkerd.io/2/tasks/rotating_identity_certificates/", err)
 	}
 	issuerData.Expiry = &cred.Crt.Certificate.NotAfter
 
@@ -546,8 +546,8 @@ func verifyWebhookTLS(value *charts.TLS, webhook string) error {
 	if err != nil {
 		return err
 	}
-	roots := crt.CertPool()
-	if err := crt.Verify(roots, webhookCommonName(webhook), time.Time{}); err != nil {
+	anchors := crt.CertPool()
+	if err := crt.Verify(anchors, webhookCommonName(webhook), time.Time{}); err != nil {
 		return err
 	}
 
diff --git a/controller/api/destination/watcher/endpoints_watcher.go b/controller/api/destination/watcher/endpoints_watcher.go
index 7e3c1da2d..875c99370 100644
--- a/controller/api/destination/watcher/endpoints_watcher.go
+++ b/controller/api/destination/watcher/endpoints_watcher.go
@@ -194,7 +194,20 @@ func (ew *EndpointsWatcher) addService(obj interface{}) {
 }
 
 func (ew *EndpointsWatcher) deleteService(obj interface{}) {
-	service := obj.(*corev1.Service)
+	service, ok := obj.(*corev1.Service)
+	if !ok {
+		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
+		if !ok {
+			ew.log.Errorf("couldn't get object from DeletedFinalStateUnknown %#v", obj)
+			return
+		}
+		service, ok = tombstone.Obj.(*corev1.Service)
+		if !ok {
+			ew.log.Errorf("DeletedFinalStateUnknown contained object that is not a Service %#v", obj)
+			return
+		}
+	}
+
 	if service.Namespace == kubeSystem {
 		return
 	}
@@ -225,7 +238,20 @@ func (ew *EndpointsWatcher) addEndpoints(obj interface{}) {
 }
 
 func (ew *EndpointsWatcher) deleteEndpoints(obj interface{}) {
-	endpoints := obj.(*corev1.Endpoints)
+	endpoints, ok := obj.(*corev1.Endpoints)
+	if !ok {
+		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
+		if !ok {
+			ew.log.Errorf("couldn't get object from DeletedFinalStateUnknown %#v", obj)
+			return
+		}
+		endpoints, ok = tombstone.Obj.(*corev1.Endpoints)
+		if !ok {
+			ew.log.Errorf("DeletedFinalStateUnknown contained object that is not an Endpoints %#v", obj)
+			return
+		}
+	}
+
 	if endpoints.Namespace == kubeSystem {
 		return
 	}
diff --git a/controller/api/destination/watcher/ip_watcher.go b/controller/api/destination/watcher/ip_watcher.go
index a2e0722e4..a38aa1330 100644
--- a/controller/api/destination/watcher/ip_watcher.go
+++ b/controller/api/destination/watcher/ip_watcher.go
@@ -116,7 +116,20 @@ func (iw *IPWatcher) addService(obj interface{}) {
 }
 
 func (iw *IPWatcher) deleteService(obj interface{}) {
-	service := obj.(*corev1.Service)
+	service, ok := obj.(*corev1.Service)
+	if !ok {
+		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
+		if !ok {
+			iw.log.Errorf("couldn't get object from DeletedFinalStateUnknown %#v", obj)
+			return
+		}
+		service, ok = tombstone.Obj.(*corev1.Service)
+		if !ok {
+			iw.log.Errorf("DeletedFinalStateUnknown contained object that is not a Service %#v", obj)
+			return
+		}
+	}
+
 	if service.Namespace == kubeSystem {
 		return
 	}
@@ -141,7 +154,20 @@ func (iw *IPWatcher) addPod(obj interface{}) {
 }
 
 func (iw *IPWatcher) deletePod(obj interface{}) {
-	pod := obj.(*corev1.Pod)
+	pod, ok := obj.(*corev1.Pod)
+	if !ok {
+		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
+		if !ok {
+			iw.log.Errorf("couldn't get object from DeletedFinalStateUnknown %#v", obj)
+			return
+		}
+		pod, ok = tombstone.Obj.(*corev1.Pod)
+		if !ok {
+			iw.log.Errorf("DeletedFinalStateUnknown contained object that is not a Pod %#v", obj)
+			return
+		}
+	}
+
 	if pod.Namespace == kubeSystem {
 		return
 	}
@@ -300,7 +326,6 @@ func (ss *serviceSubscriptions) updatePod(podSet PodSet) {
 func (ss *serviceSubscriptions) deletePod() {
 	ss.Lock()
 	defer ss.Unlock()
-
 	for listener, port := range ss.listeners {
 		listener.NoEndpoints(true) // Clear out previous endpoints.
 		listener.Add(singletonAddress(ss.clusterIP, port))
diff --git a/controller/api/destination/watcher/profile_watcher.go b/controller/api/destination/watcher/profile_watcher.go
index 6176431f5..4b4c33a9d 100644
--- a/controller/api/destination/watcher/profile_watcher.go
+++ b/controller/api/destination/watcher/profile_watcher.go
@@ -108,7 +108,20 @@ func (pw *ProfileWatcher) updateProfile(old interface{}, new interface{}) {
 }
 
 func (pw *ProfileWatcher) deleteProfile(obj interface{}) {
-	profile := obj.(*sp.ServiceProfile)
+	profile, ok := obj.(*sp.ServiceProfile)
+	if !ok {
+		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
+		if !ok {
+			pw.log.Errorf("couldn't get object from DeletedFinalStateUnknown %#v", obj)
+			return
+		}
+		profile, ok = tombstone.Obj.(*sp.ServiceProfile)
+		if !ok {
+			pw.log.Errorf("DeletedFinalStateUnknown contained object that is not a ServiceProfile %#v", obj)
+			return
+		}
+	}
+
 	id := ProfileID{
 		Namespace: profile.Namespace,
 		Name:      profile.Name,
diff --git a/controller/api/destination/watcher/traffic_split_watcher.go b/controller/api/destination/watcher/traffic_split_watcher.go
index b563e6950..3b6c832d8 100644
--- a/controller/api/destination/watcher/traffic_split_watcher.go
+++ b/controller/api/destination/watcher/traffic_split_watcher.go
@@ -108,7 +108,20 @@ func (tsw *TrafficSplitWatcher) updateTrafficSplit(old interface{}, new interfac
 }
 
 func (tsw *TrafficSplitWatcher) deleteTrafficSplit(obj interface{}) {
-	split := obj.(*ts.TrafficSplit)
+	split, ok := obj.(*ts.TrafficSplit)
+	if !ok {
+		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
+		if !ok {
+			tsw.log.Errorf("couldn't get object from DeletedFinalStateUnknown %#v", obj)
+			return
+		}
+		split, ok = tombstone.Obj.(*ts.TrafficSplit)
+		if !ok {
+			tsw.log.Errorf("DeletedFinalStateUnknown contained object that is not a TrafficSplit %#v", obj)
+			return
+		}
+	}
+
 	id := ServiceID{
 		Name:      split.Spec.Service,
 		Namespace: split.Namespace,
diff --git a/controller/api/public/stat_summary.go b/controller/api/public/stat_summary.go
index c0400f049..4bf9f60fb 100644
--- a/controller/api/public/stat_summary.go
+++ b/controller/api/public/stat_summary.go
@@ -159,7 +159,13 @@ func statSummaryError(req *pb.StatSummaryRequest, message string) *pb.StatSummar
 
 func (s *grpcServer) getKubernetesObjectStats(req *pb.StatSummaryRequest) (map[rKey]k8sStat, error) {
 	requestedResource := req.GetSelector().GetResource()
-	objects, err := s.k8sAPI.GetObjects(requestedResource.Namespace, requestedResource.Type, requestedResource.Name)
+
+	labelSelector, err := getLabelSelector(req)
+	if err != nil {
+		return nil, err
+	}
+
+	objects, err := s.k8sAPI.GetObjects(requestedResource.Namespace, requestedResource.Type, requestedResource.Name, labelSelector)
 	if err != nil {
 		return nil, err
 	}
@@ -258,14 +264,20 @@ func (s *grpcServer) k8sResourceQuery(ctx context.Context, req *pb.StatSummaryRe
 	return resourceResult{res: &rsp, err: nil}
 }
 
-func (s *grpcServer) getTrafficSplits(res *pb.Resource) ([]*v1alpha1.TrafficSplit, error) {
+func (s *grpcServer) getTrafficSplits(req *pb.StatSummaryRequest) ([]*v1alpha1.TrafficSplit, error) {
 	var err error
 	var trafficSplits []*v1alpha1.TrafficSplit
 
+	res := req.GetSelector().GetResource()
+	labelSelector, err := getLabelSelector(req)
+	if err != nil {
+		return nil, err
+	}
+
 	if res.GetNamespace() == "" {
-		trafficSplits, err = s.k8sAPI.TS().Lister().List(labels.Everything())
+		trafficSplits, err = s.k8sAPI.TS().Lister().List(labelSelector)
 	} else if res.GetName() == "" {
-		trafficSplits, err = s.k8sAPI.TS().Lister().TrafficSplits(res.GetNamespace()).List(labels.Everything())
+		trafficSplits, err = s.k8sAPI.TS().Lister().TrafficSplits(res.GetNamespace()).List(labelSelector)
 	} else {
 		var ts *v1alpha1.TrafficSplit
 		ts, err = s.k8sAPI.TS().Lister().TrafficSplits(res.GetNamespace()).Get(res.GetName())
@@ -276,7 +288,7 @@ func (s *grpcServer) getTrafficSplits(res *pb.Resource) ([]*v1alpha1.TrafficSpli
 }
 
 func (s *grpcServer) trafficSplitResourceQuery(ctx context.Context, req *pb.StatSummaryRequest) resourceResult {
-	tss, err := s.getTrafficSplits(req.GetSelector().GetResource())
+	tss, err := s.getTrafficSplits(req)
 
 	if err != nil {
 		return resourceResult{res: nil, err: err}
@@ -675,3 +687,15 @@ func checkContainerErrors(containerStatuses []corev1.ContainerStatus) []*pb.PodE
 	}
 	return errors
 }
+
+func getLabelSelector(req *pb.StatSummaryRequest) (labels.Selector, error) {
+	labelSelector := labels.Everything()
+	if s := req.GetSelector().GetLabelSelector(); s != "" {
+		var err error
+		labelSelector, err = labels.Parse(s)
+		if err != nil {
+			return nil, fmt.Errorf("invalid label selector \"%s\": %s", s, err)
+		}
+	}
+	return labelSelector, nil
+}
diff --git a/controller/api/public/top_routes.go b/controller/api/public/top_routes.go
index d8a11a765..dcd5e34f0 100644
--- a/controller/api/public/top_routes.go
+++ b/controller/api/public/top_routes.go
@@ -61,7 +61,7 @@ func (s *grpcServer) TopRoutes(ctx context.Context, req *pb.TopRoutesRequest) (*
 	}
 
 	// Non-authority resource
-	objects, err := s.k8sAPI.GetObjects(targetResource.Namespace, targetResource.Type, targetResource.Name)
+	objects, err := s.k8sAPI.GetObjects(targetResource.Namespace, targetResource.Type, targetResource.Name, labels.Everything())
 	if err != nil {
 		return nil, err
 	}
@@ -135,7 +135,7 @@ func (s *grpcServer) topRoutesFor(ctx context.Context, req *pb.TopRoutesRequest,
 	} else {
 		// Non-authority resource.
 		// Lookup individual resource objects.
-		objects, err := s.k8sAPI.GetObjects(requestedResource.Namespace, requestedResource.Type, requestedResource.Name)
+		objects, err := s.k8sAPI.GetObjects(requestedResource.Namespace, requestedResource.Type, requestedResource.Name, labels.Everything())
 		if err != nil {
 			return nil, err
 		}
diff --git a/controller/api/util/api_utils.go b/controller/api/util/api_utils.go
index 83f5ab920..67321497d 100644
--- a/controller/api/util/api_utils.go
+++ b/controller/api/util/api_utils.go
@@ -79,6 +79,7 @@ type StatsSummaryRequestParams struct {
 	FromName      string
 	SkipStats     bool
 	TCPStats      bool
+	LabelSelector string
 }
 
 // EdgesRequestParams contains parameters that are used to build
@@ -185,6 +186,7 @@ func BuildStatSummaryRequest(p StatsSummaryRequestParams) (*pb.StatSummaryReques
 				Name:      p.ResourceName,
 				Type:      resourceType,
 			},
+			LabelSelector: p.LabelSelector,
 		},
 		TimeWindow: window,
 		SkipStats:  p.SkipStats,
diff --git a/controller/k8s/api.go b/controller/k8s/api.go
index b0a74eff3..da9cdf25c 100644
--- a/controller/k8s/api.go
+++ b/controller/k8s/api.go
@@ -367,31 +367,32 @@ func (api *API) CJ() batchv1beta1informers.CronJobInformer {
 	return api.cj
 }
 
-// GetObjects returns a list of Kubernetes objects, given a namespace, type, and name.
+// GetObjects returns a list of Kubernetes objects, given a namespace, type, name and label selector.
 // If namespace is an empty string, match objects in all namespaces.
 // If name is an empty string, match all objects of the given type.
-func (api *API) GetObjects(namespace, restype, name string) ([]runtime.Object, error) {
+// If label selector is an empty string, match all labels.
+func (api *API) GetObjects(namespace, restype, name string, label labels.Selector) ([]runtime.Object, error) {
 	switch restype {
 	case k8s.Namespace:
-		return api.getNamespaces(name)
+		return api.getNamespaces(name, label)
 	case k8s.CronJob:
-		return api.getCronjobs(namespace, name)
+		return api.getCronjobs(namespace, name, label)
 	case k8s.DaemonSet:
-		return api.getDaemonsets(namespace, name)
+		return api.getDaemonsets(namespace, name, label)
 	case k8s.Deployment:
-		return api.getDeployments(namespace, name)
+		return api.getDeployments(namespace, name, label)
 	case k8s.Job:
-		return api.getJobs(namespace, name)
+		return api.getJobs(namespace, name, label)
 	case k8s.Pod:
-		return api.getPods(namespace, name)
+		return api.getPods(namespace, name, label)
 	case k8s.ReplicationController:
-		return api.getRCs(namespace, name)
+		return api.getRCs(namespace, name, label)
 	case k8s.ReplicaSet:
-		return api.getReplicasets(namespace, name)
+		return api.getReplicasets(namespace, name, label)
 	case k8s.Service:
 		return api.getServices(namespace, name)
 	case k8s.StatefulSet:
-		return api.getStatefulsets(namespace, name)
+		return api.getStatefulsets(namespace, name, label)
 	default:
 		return nil, status.Errorf(codes.Unimplemented, "unimplemented resource type: %s", restype)
 	}
@@ -632,12 +633,12 @@ func GetNamespaceOf(obj runtime.Object) (string, error) {
 
 // getNamespaces returns the namespace matching the specified name. If no name
 // is given, it returns all namespaces.
-func (api *API) getNamespaces(name string) ([]runtime.Object, error) {
+func (api *API) getNamespaces(name string, labelSelector labels.Selector) ([]runtime.Object, error) {
 	var namespaces []*corev1.Namespace
 
 	if name == "" {
 		var err error
-		namespaces, err = api.NS().Lister().List(labels.Everything())
+		namespaces, err = api.NS().Lister().List(labelSelector)
 		if err != nil {
 			return nil, err
 		}
@@ -657,14 +658,14 @@ func (api *API) getNamespaces(name string) ([]runtime.Object, error) {
 	return objects, nil
 }
 
-func (api *API) getDeployments(namespace, name string) ([]runtime.Object, error) {
+func (api *API) getDeployments(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
 	var err error
 	var deploys []*appsv1.Deployment
 
 	if namespace == "" {
-		deploys, err = api.Deploy().Lister().List(labels.Everything())
+		deploys, err = api.Deploy().Lister().List(labelSelector)
 	} else if name == "" {
-		deploys, err = api.Deploy().Lister().Deployments(namespace).List(labels.Everything())
+		deploys, err = api.Deploy().Lister().Deployments(namespace).List(labelSelector)
 	} else {
 		var deploy *appsv1.Deployment
 		deploy, err = api.Deploy().Lister().Deployments(namespace).Get(name)
@@ -683,14 +684,14 @@ func (api *API) getDeployments(namespace, name string) ([]runtime.Object, error)
 	return objects, nil
 }
 
-func (api *API) getPods(namespace, name string) ([]runtime.Object, error) {
+func (api *API) getPods(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
 	var err error
 	var pods []*corev1.Pod
 
 	if namespace == "" {
-		pods, err = api.Pod().Lister().List(labels.Everything())
+		pods, err = api.Pod().Lister().List(labelSelector)
 	} else if name == "" {
-		pods, err = api.Pod().Lister().Pods(namespace).List(labels.Everything())
+		pods, err = api.Pod().Lister().Pods(namespace).List(labelSelector)
 	} else {
 		var pod *corev1.Pod
 		pod, err = api.Pod().Lister().Pods(namespace).Get(name)
@@ -712,14 +713,14 @@ func (api *API) getPods(namespace, name string) ([]runtime.Object, error) {
 	return objects, nil
 }
 
-func (api *API) getRCs(namespace, name string) ([]runtime.Object, error) {
+func (api *API) getRCs(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
 	var err error
 	var rcs []*corev1.ReplicationController
 
 	if namespace == "" {
-		rcs, err = api.RC().Lister().List(labels.Everything())
+		rcs, err = api.RC().Lister().List(labelSelector)
 	} else if name == "" {
-		rcs, err = api.RC().Lister().ReplicationControllers(namespace).List(labels.Everything())
+		rcs, err = api.RC().Lister().ReplicationControllers(namespace).List(labelSelector)
 	} else {
 		var rc *corev1.ReplicationController
 		rc, err = api.RC().Lister().ReplicationControllers(namespace).Get(name)
@@ -738,14 +739,14 @@ func (api *API) getRCs(namespace, name string) ([]runtime.Object, error) {
 	return objects, nil
 }
 
-func (api *API) getDaemonsets(namespace, name string) ([]runtime.Object, error) {
+func (api *API) getDaemonsets(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
 	var err error
 	var daemonsets []*appsv1.DaemonSet
 
 	if namespace == "" {
-		daemonsets, err = api.DS().Lister().List(labels.Everything())
+		daemonsets, err = api.DS().Lister().List(labelSelector)
 	} else if name == "" {
-		daemonsets, err = api.DS().Lister().DaemonSets(namespace).List(labels.Everything())
+		daemonsets, err = api.DS().Lister().DaemonSets(namespace).List(labelSelector)
 	} else {
 		var ds *appsv1.DaemonSet
 		ds, err = api.DS().Lister().DaemonSets(namespace).Get(name)
@@ -764,14 +765,14 @@ func (api *API) getDaemonsets(namespace, name string) ([]runtime.Object, error)
 	return objects, nil
 }
 
-func (api *API) getStatefulsets(namespace, name string) ([]runtime.Object, error) {
+func (api *API) getStatefulsets(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
 	var err error
 	var statefulsets []*appsv1.StatefulSet
 
 	if namespace == "" {
-		statefulsets, err = api.SS().Lister().List(labels.Everything())
+		statefulsets, err = api.SS().Lister().List(labelSelector)
 	} else if name == "" {
-		statefulsets, err = api.SS().Lister().StatefulSets(namespace).List(labels.Everything())
+		statefulsets, err = api.SS().Lister().StatefulSets(namespace).List(labelSelector)
 	} else {
 		var ss *appsv1.StatefulSet
 		ss, err = api.SS().Lister().StatefulSets(namespace).Get(name)
@@ -790,14 +791,14 @@ func (api *API) getStatefulsets(namespace, name string) ([]runtime.Object, error
 	return objects, nil
 }
 
-func (api *API) getJobs(namespace, name string) ([]runtime.Object, error) {
+func (api *API) getJobs(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
 	var err error
 	var jobs []*batchv1.Job
 
 	if namespace == "" {
-		jobs, err = api.Job().Lister().List(labels.Everything())
+		jobs, err = api.Job().Lister().List(labelSelector)
 	} else if name == "" {
-		jobs, err = api.Job().Lister().Jobs(namespace).List(labels.Everything())
+		jobs, err = api.Job().Lister().Jobs(namespace).List(labelSelector)
 	} else {
 		var job *batchv1.Job
 		job, err = api.Job().Lister().Jobs(namespace).Get(name)
@@ -831,14 +832,14 @@ func (api *API) getServices(namespace, name string) ([]runtime.Object, error) {
 	return objects, nil
 }
 
-func (api *API) getCronjobs(namespace, name string) ([]runtime.Object, error) {
+func (api *API) getCronjobs(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
 	var err error
 	var cronjobs []*batchv1beta1.CronJob
 
 	if namespace == "" {
-		cronjobs, err = api.CJ().Lister().List(labels.Everything())
+		cronjobs, err = api.CJ().Lister().List(labelSelector)
 	} else if name == "" {
-		cronjobs, err = api.CJ().Lister().CronJobs(namespace).List(labels.Everything())
+		cronjobs, err = api.CJ().Lister().CronJobs(namespace).List(labelSelector)
 	} else {
 		var cronjob *batchv1beta1.CronJob
 		cronjob, err = api.CJ().Lister().CronJobs(namespace).Get(name)
@@ -856,14 +857,14 @@ func (api *API) getCronjobs(namespace, name string) ([]runtime.Object, error) {
 	return objects, nil
 }
 
-func (api *API) getReplicasets(namespace, name string) ([]runtime.Object, error) {
+func (api *API) getReplicasets(namespace, name string, labelSelector labels.Selector) ([]runtime.Object, error) {
 	var err error
 	var replicasets []*appsv1.ReplicaSet
 
 	if namespace == "" {
-		replicasets, err = api.RS().Lister().List(labels.Everything())
+		replicasets, err = api.RS().Lister().List(labelSelector)
 	} else if name == "" {
-		replicasets, err = api.RS().Lister().ReplicaSets(namespace).List(labels.Everything())
+		replicasets, err = api.RS().Lister().ReplicaSets(namespace).List(labelSelector)
 	} else {
 		var replicaset *appsv1.ReplicaSet
 		replicaset, err = api.RS().Lister().ReplicaSets(namespace).Get(name)
diff --git a/controller/proxy-injector/webhook.go b/controller/proxy-injector/webhook.go
index b769a1e25..dc274862d 100644
--- a/controller/proxy-injector/webhook.go
+++ b/controller/proxy-injector/webhook.go
@@ -4,6 +4,8 @@ import (
 	"fmt"
 	"strings"
 
+	"k8s.io/apimachinery/pkg/labels"
+
 	pb "github.com/linkerd/linkerd2/controller/gen/config"
 	"github.com/linkerd/linkerd2/controller/k8s"
 	"github.com/linkerd/linkerd2/pkg/config"
@@ -67,7 +69,7 @@ func Inject(api *k8s.API,
 	var parent *runtime.Object
 	ownerKind := ""
 	if ownerRef := resourceConfig.GetOwnerRef(); ownerRef != nil {
-		objs, err := api.GetObjects(request.Namespace, ownerRef.Kind, ownerRef.Name)
+		objs, err := api.GetObjects(request.Namespace, ownerRef.Kind, ownerRef.Name, labels.Everything())
 		if err != nil {
 			log.Warnf("couldn't retrieve parent object %s-%s-%s; error: %s", request.Namespace, ownerRef.Kind, ownerRef.Name, err)
 		} else if len(objs) == 0 {
diff --git a/controller/tap/server.go b/controller/tap/server.go
index b1a26ffe7..dab120eaa 100644
--- a/controller/tap/server.go
+++ b/controller/tap/server.go
@@ -7,6 +7,8 @@ import (
 	"time"
 	"unicode/utf8"
 
+	"k8s.io/apimachinery/pkg/labels"
+
 	httpPb "github.com/linkerd/linkerd2-proxy-api/go/http_types"
 	proxy "github.com/linkerd/linkerd2-proxy-api/go/tap"
 	apiUtil "github.com/linkerd/linkerd2/controller/api/util"
@@ -64,7 +66,7 @@ func (s *GRPCTapServer) TapByResource(req *public.TapByResourceRequest, stream p
 		req.MaxRps = defaultMaxRps
 	}
 
-	objects, err := s.k8sAPI.GetObjects(res.GetNamespace(), res.GetType(), res.GetName())
+	objects, err := s.k8sAPI.GetObjects(res.GetNamespace(), res.GetType(), res.GetName(), labels.Everything())
 	if err != nil {
 		return apiUtil.GRPCError(err)
 	}
diff --git a/pkg/charts/charts.go b/pkg/charts/charts.go
index 8fe289f51..ef20038fd 100644
--- a/pkg/charts/charts.go
+++ b/pkg/charts/charts.go
@@ -3,14 +3,18 @@ package charts
 import (
 	"bytes"
 	"path"
+	"strings"
 
 	"github.com/linkerd/linkerd2/pkg/charts/static"
+	"github.com/linkerd/linkerd2/pkg/version"
 	"k8s.io/helm/pkg/chartutil"
 	helmChart "k8s.io/helm/pkg/proto/hapi/chart"
 	"k8s.io/helm/pkg/renderutil"
 	"k8s.io/helm/pkg/timeconv"
 )
 
+const versionPlaceholder = "{version}"
+
 // Chart holds the necessary info to render a Helm chart
 type Chart struct {
 	Name      string
@@ -122,3 +126,10 @@ func FilesReader(dir string, files []*chartutil.BufferedFile) error {
 	}
 	return nil
 }
+
+// InsertVersion returns the chart values file contents passed in
+// with the version placeholder replaced with the current version
+func InsertVersion(data []byte) []byte {
+	dataWithVersion := strings.Replace(string(data), versionPlaceholder, version.Version, 1)
+	return []byte(dataWithVersion)
+}
diff --git a/pkg/charts/cni/values.go b/pkg/charts/cni/values.go
index 27583e52b..0d7d2400c 100644
--- a/pkg/charts/cni/values.go
+++ b/pkg/charts/cni/values.go
@@ -57,7 +57,7 @@ func readDefaults(chartDir string) (*Values, error) {
 		return nil, err
 	}
 	values := Values{}
-	if err := yaml.Unmarshal(file.Data, &values); err != nil {
+	if err := yaml.Unmarshal(charts.InsertVersion(file.Data), &values); err != nil {
 		return nil, err
 	}
 	return &values, nil
diff --git a/pkg/charts/linkerd2/values.go b/pkg/charts/linkerd2/values.go
index 8e5675c7a..0c4ccc08f 100644
--- a/pkg/charts/linkerd2/values.go
+++ b/pkg/charts/linkerd2/values.go
@@ -250,7 +250,7 @@ func readDefaults(chartDir string, ha bool) (*Values, error) {
 	values := Values{}
 	for _, valuesFile := range valuesFiles {
 		var v Values
-		if err := yaml.Unmarshal(valuesFile.Data, &v); err != nil {
+		if err := yaml.Unmarshal(charts.InsertVersion(valuesFile.Data), &v); err != nil {
 			return nil, err
 		}
 
diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go
index c8d3a688b..d23906714 100644
--- a/pkg/healthcheck/healthcheck.go
+++ b/pkg/healthcheck/healthcheck.go
@@ -343,7 +343,7 @@ type HealthChecker struct {
 	linkerdConfig    *configPb.All
 	uuid             string
 	issuerCert       *tls.Cred
-	roots            []*x509.Certificate
+	trustAnchors     []*x509.Certificate
 	cniDaemonSet     *appsv1.DaemonSet
 }
 
@@ -879,58 +879,58 @@ func (hc *HealthChecker) allCategories() []category {
 					hintAnchor:  "l5d-identity-cert-config-valid",
 					fatal:       true,
 					check: func(context.Context) (err error) {
-						hc.issuerCert, hc.roots, err = hc.checkCertificatesConfig()
+						hc.issuerCert, hc.trustAnchors, err = hc.checkCertificatesConfig()
 						return
 					},
 				},
 				{
-					description: "trust roots are using supported crypto algorithm",
-					hintAnchor:  "l5d-identity-roots-use-supported-crypto",
+					description: "trust anchors are using supported crypto algorithm",
+					hintAnchor:  "l5d-identity-trustAnchors-use-supported-crypto",
 					fatal:       true,
 					check: func(context.Context) error {
-						var invalidRoots []string
-						for _, root := range hc.roots {
-							if err := issuercerts.CheckCertAlgoRequirements(root); err != nil {
-								invalidRoots = append(invalidRoots, fmt.Sprintf("* %v %s %s", root.SerialNumber, root.Subject.CommonName, err))
+						var invalidAnchors []string
+						for _, anchor := range hc.trustAnchors {
+							if err := issuercerts.CheckCertAlgoRequirements(anchor); err != nil {
+								invalidAnchors = append(invalidAnchors, fmt.Sprintf("* %v %s %s", anchor.SerialNumber, anchor.Subject.CommonName, err))
 							}
 						}
-						if len(invalidRoots) > 0 {
-							return fmt.Errorf("Invalid roots:\n\t%s", strings.Join(invalidRoots, "\n\t"))
+						if len(invalidAnchors) > 0 {
+							return fmt.Errorf("Invalid trustAnchors:\n\t%s", strings.Join(invalidAnchors, "\n\t"))
 						}
 						return nil
 					},
 				},
 				{
-					description: "trust roots are within their validity period",
-					hintAnchor:  "l5d-identity-roots-are-time-valid",
+					description: "trust anchors are within their validity period",
+					hintAnchor:  "l5d-identity-trustAnchors-are-time-valid",
 					fatal:       true,
 					check: func(ctx context.Context) error {
-						var expiredRoots []string
-						for _, root := range hc.roots {
-							if err := issuercerts.CheckCertValidityPeriod(root); err != nil {
-								expiredRoots = append(expiredRoots, fmt.Sprintf("* %v %s %s", root.SerialNumber, root.Subject.CommonName, err))
+						var expiredAnchors []string
+						for _, anchor := range hc.trustAnchors {
+							if err := issuercerts.CheckCertValidityPeriod(anchor); err != nil {
+								expiredAnchors = append(expiredAnchors, fmt.Sprintf("* %v %s %s", anchor.SerialNumber, anchor.Subject.CommonName, err))
 							}
 						}
-						if len(expiredRoots) > 0 {
-							return fmt.Errorf("Invalid roots:\n\t%s", strings.Join(expiredRoots, "\n\t"))
+						if len(expiredAnchors) > 0 {
+							return fmt.Errorf("Invalid anchors:\n\t%s", strings.Join(expiredAnchors, "\n\t"))
 						}
 
 						return nil
 					},
 				},
 				{
-					description: "trust roots are valid for at least 60 days",
-					hintAnchor:  "l5d-identity-roots-not-expiring-soon",
+					description: "trust anchors are valid for at least 60 days",
+					hintAnchor:  "l5d-identity-trustAnchors-not-expiring-soon",
 					warning:     true,
 					check: func(ctx context.Context) error {
-						var expiringRoots []string
-						for _, root := range hc.roots {
-							if err := issuercerts.CheckExpiringSoon(root); err != nil {
-								expiringRoots = append(expiringRoots, fmt.Sprintf("* %v %s %s", root.SerialNumber, root.Subject.CommonName, err))
+						var expiringAnchors []string
+						for _, anchor := range hc.trustAnchors {
+							if err := issuercerts.CheckExpiringSoon(anchor); err != nil {
+								expiringAnchors = append(expiringAnchors, fmt.Sprintf("* %v %s %s", anchor.SerialNumber, anchor.Subject.CommonName, err))
 							}
 						}
-						if len(expiringRoots) > 0 {
-							return fmt.Errorf("Roots expiring soon:\n\t%s", strings.Join(expiringRoots, "\n\t"))
+						if len(expiringAnchors) > 0 {
+							return fmt.Errorf("Anchors expiring soon:\n\t%s", strings.Join(expiringAnchors, "\n\t"))
 						}
 						return nil
 					},
@@ -969,10 +969,10 @@ func (hc *HealthChecker) allCategories() []category {
 					},
 				},
 				{
-					description: "issuer cert is issued by the trust root",
-					hintAnchor:  "l5d-identity-issuer-cert-issued-by-trust-root",
+					description: "issuer cert is issued by the trust anchor",
+					hintAnchor:  "l5d-identity-issuer-cert-issued-by-trust-anchor",
 					check: func(ctx context.Context) error {
-						return hc.issuerCert.Verify(tls.CertificatesToPool(hc.roots), hc.issuerIdentity(), time.Time{})
+						return hc.issuerCert.Verify(tls.CertificatesToPool(hc.trustAnchors), hc.issuerIdentity(), time.Time{})
 					},
 				},
 			},
@@ -1400,7 +1400,7 @@ func (hc *HealthChecker) checkCertificatesConfig() (*tls.Cred, []*x509.Certifica
 		data, err = issuercerts.FetchIssuerData(hc.kubeAPI, idctx.TrustAnchorsPem, hc.ControlPlaneNamespace)
 	} else {
 		data, err = issuercerts.FetchExternalIssuerData(hc.kubeAPI, hc.ControlPlaneNamespace)
-		// ensure trust trustRoots in config matches whats in the secret
+		// ensure trust anchors in config matches whats in the secret
 		if data != nil && strings.TrimSpace(idctx.TrustAnchorsPem) != strings.TrimSpace(data.TrustAnchors) {
 			errFormat := "IdentityContext.TrustAnchorsPem does not match %s in %s"
 			err = fmt.Errorf(errFormat, k8s.IdentityIssuerTrustAnchorsNameExternal, k8s.IdentityIssuerSecretName)
@@ -1416,12 +1416,12 @@ func (hc *HealthChecker) checkCertificatesConfig() (*tls.Cred, []*x509.Certifica
 		return nil, nil, err
 	}
 
-	roots, err := tls.DecodePEMCertificates(data.TrustAnchors)
+	anchors, err := tls.DecodePEMCertificates(data.TrustAnchors)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	return issuerCreds, roots, nil
+	return issuerCreds, anchors, nil
 }
 
 // FetchLinkerdConfigMap retrieves the `linkerd-config` ConfigMap from
@@ -1617,7 +1617,7 @@ func (hc *HealthChecker) checkPodSecurityPolicies(shouldExist bool) error {
 	return checkResources("PodSecurityPolicies", objects, []string{fmt.Sprintf("linkerd-%s-control-plane", hc.ControlPlaneNamespace)}, shouldExist)
 }
 
-// MeshedPodIdentityData contains meshed pod details + root anchors of the proxy
+// MeshedPodIdentityData contains meshed pod details + trust anchors of the proxy
 type MeshedPodIdentityData struct {
 	Name      string
 	Namespace string
diff --git a/pkg/issuercerts/issuercerts.go b/pkg/issuercerts/issuercerts.go
index 50262d7df..c5ded8d3a 100644
--- a/pkg/issuercerts/issuercerts.go
+++ b/pkg/issuercerts/issuercerts.go
@@ -18,7 +18,7 @@ import (
 const keyMissingError = "key %s containing the %s needs to exist in secret %s if --identity-external-issuer=%v"
 const expirationWarningThresholdInDays = 60
 
-// IssuerCertData holds the root cert data used by the CA
+// IssuerCertData holds the trust anchors cert data used by the CA
 type IssuerCertData struct {
 	TrustAnchors string
 	IssuerCrt    string
@@ -164,12 +164,12 @@ func (ic *IssuerCertData) VerifyAndBuildCreds(dnsName string) (*tls.Cred, error)
 		return nil, fmt.Errorf("issuer cert is not a CA")
 	}
 
-	roots, err := tls.DecodePEMCertPool(ic.TrustAnchors)
+	anchors, err := tls.DecodePEMCertPool(ic.TrustAnchors)
 	if err != nil {
 		return nil, err
 	}
 
-	if err := creds.Verify(roots, dnsName, time.Time{}); err != nil {
+	if err := creds.Verify(anchors, dnsName, time.Time{}); err != nil {
 		return nil, err
 	}
 


Test patch:
diff --git a/cli/cmd/upgrade_test.go b/cli/cmd/upgrade_test.go
index 5e52be12f..b43a5deca 100644
--- a/cli/cmd/upgrade_test.go
+++ b/cli/cmd/upgrade_test.go
@@ -1015,7 +1015,7 @@ spec:
 			},
 			"upgrade_overwrite_issuer.golden",
 			// use Errorf here to stop the linter complaining that our error message is capitalized...
-			fmt.Errorf("%s", "You are attempting to use an issuer certificate which does not validate against the trust roots of the following pods:\n\t* some-namespace/backend-wrong-anchors\nThese pods do not have the current trust bundle and must be restarted.  Use the --force flag to proceed anyway (this will likely prevent those pods from sending or receiving traffic)."),
+			fmt.Errorf("%s", "You are attempting to use an issuer certificate which does not validate against the trust anchors of the following pods:\n\t* some-namespace/backend-wrong-anchors\nThese pods do not have the current trust bundle and must be restarted.  Use the --force flag to proceed anyway (this will likely prevent those pods from sending or receiving traffic)."),
 			func(options *upgradeOptions) {
 				options.identityOptions.crtPEMFile = filepath.Join("testdata", "valid-crt.pem")
 				options.identityOptions.keyPEMFile = filepath.Join("testdata", "valid-key.pem")
diff --git a/controller/api/destination/watcher/endpoints_watcher_test.go b/controller/api/destination/watcher/endpoints_watcher_test.go
index eb3f9942f..6bd966e95 100644
--- a/controller/api/destination/watcher/endpoints_watcher_test.go
+++ b/controller/api/destination/watcher/endpoints_watcher_test.go
@@ -6,6 +6,8 @@ import (
 	"testing"
 
 	"github.com/linkerd/linkerd2/controller/k8s"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	logging "github.com/sirupsen/logrus"
 )
@@ -500,3 +502,118 @@ status:
 		})
 	}
 }
+
+func TestEndpointsWatcherDeletion(t *testing.T) {
+	k8sConfigs := []string{`
+apiVersion: v1
+kind: Service
+metadata:
+  name: name1
+  namespace: ns
+spec:
+  type: LoadBalancer
+  ports:
+  - port: 8989`,
+		`
+apiVersion: v1
+kind: Endpoints
+metadata:
+  name: name1
+  namespace: ns
+subsets:
+- addresses:
+  - ip: 172.17.0.12
+    targetRef:
+      kind: Pod
+      name: name1-1
+      namespace: ns
+  ports:
+  - port: 8989`,
+		`
+apiVersion: v1
+kind: Pod
+metadata:
+  name: name1-1
+  namespace: ns
+status:
+  phase: Running
+  podIP: 172.17.0.12`}
+
+	for _, tt := range []struct {
+		serviceType      string
+		k8sConfigs       []string
+		id               ServiceID
+		hostname         string
+		port             Port
+		objectToDelete   interface{}
+		deletingServices bool
+	}{
+		{
+			serviceType:    "can delete endpoints",
+			k8sConfigs:     k8sConfigs,
+			id:             ServiceID{Name: "name1", Namespace: "ns"},
+			port:           8989,
+			hostname:       "name1-1",
+			objectToDelete: &corev1.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "name1", Namespace: "ns"}},
+		},
+		{
+			serviceType:    "can delete endpoints when wrapped in a DeletedFinalStateUnknown",
+			k8sConfigs:     k8sConfigs,
+			id:             ServiceID{Name: "name1", Namespace: "ns"},
+			port:           8989,
+			hostname:       "name1-1",
+			objectToDelete: &corev1.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "name1", Namespace: "ns"}},
+		},
+		{
+			serviceType:      "can delete services",
+			k8sConfigs:       k8sConfigs,
+			id:               ServiceID{Name: "name1", Namespace: "ns"},
+			port:             8989,
+			hostname:         "name1-1",
+			objectToDelete:   &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "name1", Namespace: "ns"}},
+			deletingServices: true,
+		},
+		{
+			serviceType:      "can delete services when wrapped in a DeletedFinalStateUnknown",
+			k8sConfigs:       k8sConfigs,
+			id:               ServiceID{Name: "name1", Namespace: "ns"},
+			port:             8989,
+			hostname:         "name1-1",
+			objectToDelete:   &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "name1", Namespace: "ns"}},
+			deletingServices: true,
+		},
+	} {
+
+		tt := tt // pin
+		t.Run("subscribes listener to "+tt.serviceType, func(t *testing.T) {
+			k8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)
+			if err != nil {
+				t.Fatalf("NewFakeAPI returned an error: %s", err)
+			}
+
+			watcher := NewEndpointsWatcher(k8sAPI, logging.WithField("test", t.Name()))
+
+			k8sAPI.Sync()
+
+			listener := newBufferingEndpointListener()
+
+			err = watcher.Subscribe(tt.id, tt.port, tt.hostname, listener)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			if tt.deletingServices {
+				watcher.deleteService(tt.objectToDelete)
+
+			} else {
+				watcher.deleteEndpoints(tt.objectToDelete)
+			}
+
+			if !listener.noEndpointsCalled {
+				t.Fatal("Expected NoEndpoints to be Called")
+			}
+		})
+
+	}
+
+}
diff --git a/controller/api/destination/watcher/ip_watcher_test.go b/controller/api/destination/watcher/ip_watcher_test.go
index 5bdc61416..43f9b420e 100644
--- a/controller/api/destination/watcher/ip_watcher_test.go
+++ b/controller/api/destination/watcher/ip_watcher_test.go
@@ -4,9 +4,13 @@ import (
 	"sort"
 	"testing"
 
+	"k8s.io/client-go/tools/cache"
+
 	"github.com/linkerd/linkerd2/controller/k8s"
 
 	logging "github.com/sirupsen/logrus"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 func TestIPWatcher(t *testing.T) {
@@ -460,3 +464,132 @@ status:
 		})
 	}
 }
+
+func TestIPWatcherDeletion(t *testing.T) {
+
+	podK8sConfig := []string{
+		`
+apiVersion: v1
+kind: Pod
+metadata:
+  name: name1-1
+  namespace: ns
+  ownerReferences:
+  - kind: ReplicaSet
+    name: rs-1
+status:
+  phase: Running
+  podIP: 172.17.0.12`,
+	}
+
+	serviceK8sConfig := []string{
+		`
+apiVersion: v1
+kind: Service
+metadata:
+  name: name1
+  namespace: ns
+spec:
+  type: LoadBalancer
+  clusterIP: 192.168.210.92
+  ports:
+  - port: 8989`,
+		`
+apiVersion: v1
+kind: Endpoints
+metadata:
+  name: name1
+  namespace: ns
+subsets:
+- addresses:
+  - ip: 172.17.0.12
+    targetRef:
+      kind: Pod
+      name: name1-1
+      namespace: ns
+`,
+		`
+apiVersion: v1
+kind: Pod
+metadata:
+  name: name1-1
+  namespace: ns
+  ownerReferences:
+  - kind: ReplicaSet
+    name: rs-1
+status:
+  phase: Running
+  podIP: 172.17.0.12`,
+	}
+
+	for _, tt := range []struct {
+		description    string
+		k8sConfigs     []string
+		host           string
+		port           Port
+		objectToDelete interface{}
+		deletingPod    bool
+	}{
+		{
+			description:    "can delete pods",
+			k8sConfigs:     podK8sConfig,
+			host:           "172.17.0.12",
+			port:           8989,
+			objectToDelete: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "name1-1", Namespace: "ns"}, Status: corev1.PodStatus{PodIP: "172.17.0.12"}},
+			deletingPod:    true,
+		},
+		{
+			description:    "can delete pods wrapped in a DeletedFinalStateUnknown",
+			k8sConfigs:     podK8sConfig,
+			host:           "172.17.0.12",
+			port:           8989,
+			objectToDelete: cache.DeletedFinalStateUnknown{Obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "name1-1", Namespace: "ns"}, Status: corev1.PodStatus{PodIP: "172.17.0.12"}}},
+			deletingPod:    true,
+		},
+		{
+			description:    "can delete services",
+			k8sConfigs:     serviceK8sConfig,
+			host:           "192.168.210.92",
+			port:           8989,
+			objectToDelete: &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "name1", Namespace: "ns"}, Spec: corev1.ServiceSpec{ClusterIP: "192.168.210.92"}},
+		},
+		{
+			description:    "can delete services wrapped in a DeletedFinalStateUnknown",
+			k8sConfigs:     serviceK8sConfig,
+			host:           "192.168.210.92",
+			port:           8989,
+			objectToDelete: cache.DeletedFinalStateUnknown{Obj: &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "name1", Namespace: "ns"}, Spec: corev1.ServiceSpec{ClusterIP: "192.168.210.92"}}},
+		},
+	} {
+
+		tt := tt // pin
+		t.Run("subscribes listener to "+tt.description, func(t *testing.T) {
+			k8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)
+			if err != nil {
+				t.Fatalf("NewFakeAPI returned an error: %s", err)
+			}
+
+			endpoints := NewEndpointsWatcher(k8sAPI, logging.WithField("test", t.Name()))
+			watcher := NewIPWatcher(k8sAPI, endpoints, logging.WithField("test", t.Name()))
+
+			k8sAPI.Sync()
+
+			listener := newBufferingEndpointListener()
+
+			err = watcher.Subscribe(tt.host, tt.port, listener)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			if tt.deletingPod {
+				watcher.deletePod(tt.objectToDelete)
+			} else {
+				watcher.deleteService(tt.objectToDelete)
+			}
+
+			if !listener.noEndpointsCalled {
+				t.Fatal("Expected NoEndpoints to be Called")
+			}
+		})
+	}
+}
diff --git a/controller/api/destination/watcher/profile_watcher_test.go b/controller/api/destination/watcher/profile_watcher_test.go
index c54cc1db9..e908ce6ac 100644
--- a/controller/api/destination/watcher/profile_watcher_test.go
+++ b/controller/api/destination/watcher/profile_watcher_test.go
@@ -3,21 +3,41 @@ package watcher
 import (
 	"testing"
 
+	"k8s.io/client-go/tools/cache"
+
 	sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
 	"github.com/linkerd/linkerd2/controller/k8s"
 	logging "github.com/sirupsen/logrus"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-func TestProfileWatcher(t *testing.T) {
-	for _, tt := range []struct {
-		name             string
-		k8sConfigs       []string
-		id               ProfileID
-		expectedProfiles []*sp.ServiceProfileSpec
-	}{
-		{
-			name: "service profile",
-			k8sConfigs: []string{`
+var testServiceProfile = sp.ServiceProfile{
+	ObjectMeta: metav1.ObjectMeta{
+		Name:      "foobar.ns.svc.cluster.local",
+		Namespace: "linkerd",
+	},
+	Spec: sp.ServiceProfileSpec{
+		Routes: []*sp.RouteSpec{
+			{
+				Condition: &sp.RequestMatch{
+					PathRegex: "/x/y/z",
+				},
+				ResponseClasses: []*sp.ResponseClass{
+					{
+						Condition: &sp.ResponseMatch{
+							Status: &sp.Range{
+								Min: 500,
+							},
+						},
+						IsFailure: true,
+					},
+				},
+			},
+		},
+	},
+}
+
+var testServiceProfileResource = `
 apiVersion: linkerd.io/v1alpha2
 kind: ServiceProfile
 metadata:
@@ -31,29 +51,21 @@ spec:
     - condition:
         status:
           min: 500
-      isFailure: true`,
-			},
-			id: ProfileID{Name: "foobar.ns.svc.cluster.local", Namespace: "linkerd"},
+      isFailure: true`
+
+func TestProfileWatcherUpdates(t *testing.T) {
+	for _, tt := range []struct {
+		name             string
+		k8sConfigs       []string
+		id               ProfileID
+		expectedProfiles []*sp.ServiceProfileSpec
+	}{
+		{
+			name:       "service profile",
+			k8sConfigs: []string{testServiceProfileResource},
+			id:         ProfileID{Name: testServiceProfile.Name, Namespace: testServiceProfile.Namespace},
 			expectedProfiles: []*sp.ServiceProfileSpec{
-				{
-					Routes: []*sp.RouteSpec{
-						{
-							Condition: &sp.RequestMatch{
-								PathRegex: "/x/y/z",
-							},
-							ResponseClasses: []*sp.ResponseClass{
-								{
-									Condition: &sp.ResponseMatch{
-										Status: &sp.Range{
-											Min: 500,
-										},
-									},
-									IsFailure: true,
-								},
-							},
-						},
-					},
-				},
+				&testServiceProfile.Spec,
 			},
 		},
 		{
@@ -94,3 +106,46 @@ spec:
 		})
 	}
 }
+
+func TestProfileWatcherDeletes(t *testing.T) {
+	for _, tt := range []struct {
+		name           string
+		k8sConfigs     []string
+		id             ProfileID
+		objectToDelete interface{}
+	}{
+		{
+			name:           "can delete service profiles",
+			k8sConfigs:     []string{testServiceProfileResource},
+			id:             ProfileID{Name: testServiceProfile.Name, Namespace: testServiceProfile.Namespace},
+			objectToDelete: &testServiceProfile,
+		},
+		{
+			name:           "can delete service profiles wrapped in a DeletedFinalStateUnknown",
+			k8sConfigs:     []string{testServiceProfileResource},
+			id:             ProfileID{Name: testServiceProfile.Name, Namespace: testServiceProfile.Namespace},
+			objectToDelete: cache.DeletedFinalStateUnknown{Obj: &testServiceProfile},
+		},
+	} {
+		tt := tt // pin
+		t.Run(tt.name, func(t *testing.T) {
+			k8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)
+			if err != nil {
+				t.Fatalf("NewFakeAPI returned an error: %s", err)
+			}
+
+			watcher := NewProfileWatcher(k8sAPI, logging.WithField("test", t.Name()))
+			k8sAPI.Sync()
+
+			listener := NewDeletingProfileListener()
+
+			watcher.Subscribe(tt.id, listener)
+
+			watcher.deleteProfile(tt.objectToDelete)
+
+			if listener.NumDeletes != 1 {
+				t.Fatalf("Expected to get 1 deletes but got %v", listener.NumDeletes)
+			}
+		})
+	}
+}
diff --git a/controller/api/destination/watcher/test_util.go b/controller/api/destination/watcher/test_util.go
index ca8f825d4..1b31ca266 100644
--- a/controller/api/destination/watcher/test_util.go
+++ b/controller/api/destination/watcher/test_util.go
@@ -8,6 +8,27 @@ import (
 	sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
 )
 
+// DeletingProfileListener implements ProfileUpdateListener and registers
+// deletions. Useful for unit testing
+type DeletingProfileListener struct {
+	NumDeletes int
+}
+
+// NewDeletingProfileListener creates a new NewDeletingProfileListener.
+func NewDeletingProfileListener() *DeletingProfileListener {
+	return &DeletingProfileListener{
+		NumDeletes: 0,
+	}
+}
+
+// Update registers a deletion
+func (dpl *DeletingProfileListener) Update(profile *sp.ServiceProfile) {
+	if profile == nil {
+		dpl.NumDeletes = dpl.NumDeletes + 1
+
+	}
+}
+
 // BufferingProfileListener implements ProfileUpdateListener and stores updates
 // in a slice.  Useful for unit tests.
 type BufferingProfileListener struct {
diff --git a/controller/api/destination/watcher/traffic_split_watcher_test.go b/controller/api/destination/watcher/traffic_split_watcher_test.go
index 4c1eed9c7..8b2a4c741 100644
--- a/controller/api/destination/watcher/traffic_split_watcher_test.go
+++ b/controller/api/destination/watcher/traffic_split_watcher_test.go
@@ -3,10 +3,13 @@ package watcher
 import (
 	"testing"
 
+	"k8s.io/client-go/tools/cache"
+
 	ts "github.com/deislabs/smi-sdk-go/pkg/apis/split/v1alpha1"
 	"github.com/linkerd/linkerd2/controller/k8s"
 	logging "github.com/sirupsen/logrus"
 	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 type bufferingTrafficSplitListener struct {
@@ -23,16 +26,25 @@ func (btsl *bufferingTrafficSplitListener) UpdateTrafficSplit(split *ts.TrafficS
 	btsl.splits = append(btsl.splits, split)
 }
 
-func TestTrafficSplitWatcher(t *testing.T) {
-	for _, tt := range []struct {
-		name           string
-		k8sConfigs     []string
-		service        ServiceID
-		expectedSplits []*ts.TrafficSplitSpec
-	}{
-		{
-			name: "traffic split",
-			k8sConfigs: []string{`
+type deletingTrafficSplitListener struct {
+	NumDeletes int
+}
+
+func newDeletingTrafficSplitListener() *deletingTrafficSplitListener {
+	return &deletingTrafficSplitListener{
+		NumDeletes: 0,
+	}
+}
+
+func (dpl *deletingTrafficSplitListener) UpdateTrafficSplit(ts *ts.TrafficSplit) {
+	if ts == nil {
+		dpl.NumDeletes = dpl.NumDeletes + 1
+
+	}
+}
+
+var (
+	testTrafficSplitResource = `
 apiVersion: split.smi-spec.io/v1alpha1
 kind: TrafficSplit
 metadata:
@@ -44,27 +56,44 @@ spec:
   - service: foo-v1
     weight: 500m
   - service: foo-v2
-    weight: 500m`,
+    weight: 500m`
+
+	testTrafficSplit = ts.TrafficSplit{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "split",
+			Namespace: "ns",
+		},
+		Spec: ts.TrafficSplitSpec{
+			Service: "foo",
+			Backends: []ts.TrafficSplitBackend{
+				{
+					Service: "foo-v1",
+					Weight:  resource.MustParse("500m"),
+				},
+				{
+					Service: "foo-v2",
+					Weight:  resource.MustParse("500m"),
+				},
 			},
+		},
+	}
+)
+
+func TestTrafficSplitWatcher(t *testing.T) {
+	for _, tt := range []struct {
+		name           string
+		k8sConfigs     []string
+		service        ServiceID
+		expectedSplits []*ts.TrafficSplitSpec
+	}{
+		{
+			name:       "traffic split",
+			k8sConfigs: []string{testTrafficSplitResource},
 			service: ServiceID{
 				Name:      "foo",
 				Namespace: "ns",
 			},
-			expectedSplits: []*ts.TrafficSplitSpec{
-				{
-					Service: "foo",
-					Backends: []ts.TrafficSplitBackend{
-						{
-							Service: "foo-v1",
-							Weight:  resource.MustParse("500m"),
-						},
-						{
-							Service: "foo-v2",
-							Weight:  resource.MustParse("500m"),
-						},
-					},
-				},
-			},
+			expectedSplits: []*ts.TrafficSplitSpec{&testTrafficSplit.Spec},
 		},
 		{
 			name:       "no traffic split",
@@ -107,3 +136,52 @@ spec:
 		})
 	}
 }
+
+func TestTrafficSplitWatcherDelete(t *testing.T) {
+	for _, tt := range []struct {
+		name           string
+		k8sConfigs     []string
+		service        ServiceID
+		objectToDelete interface{}
+	}{
+		{
+			name:       "can delete a traffic splits",
+			k8sConfigs: []string{testTrafficSplitResource},
+			service: ServiceID{
+				Name:      "foo",
+				Namespace: "ns",
+			},
+			objectToDelete: &testTrafficSplit,
+		},
+		{
+			name:       "can delete a traffic splits wrapped in a DeletedFinalStateUnknown",
+			k8sConfigs: []string{testTrafficSplitResource},
+			service: ServiceID{
+				Name:      "foo",
+				Namespace: "ns",
+			},
+			objectToDelete: cache.DeletedFinalStateUnknown{Obj: &testTrafficSplit},
+		},
+	} {
+		tt := tt // pin
+		t.Run(tt.name, func(t *testing.T) {
+			k8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)
+			if err != nil {
+				t.Fatalf("NewFakeAPI returned an error: %s", err)
+			}
+
+			watcher := NewTrafficSplitWatcher(k8sAPI, logging.WithField("test", t.Name()))
+
+			k8sAPI.Sync()
+
+			listener := newDeletingTrafficSplitListener()
+
+			watcher.Subscribe(tt.service, listener)
+
+			watcher.deleteTrafficSplit(tt.objectToDelete)
+			if listener.NumDeletes != 1 {
+				t.Fatalf("Expected to get 1 deletes but got %v", listener.NumDeletes)
+			}
+		})
+	}
+}
diff --git a/controller/k8s/api_test.go b/controller/k8s/api_test.go
index f81174e16..8a316969f 100644
--- a/controller/k8s/api_test.go
+++ b/controller/k8s/api_test.go
@@ -7,6 +7,8 @@ import (
 	"sort"
 	"testing"
 
+	"k8s.io/apimachinery/pkg/labels"
+
 	"github.com/linkerd/linkerd2/pkg/k8s"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
@@ -265,7 +267,7 @@ metadata:
 				t.Fatalf("newAPI error: %s", err)
 			}
 
-			pods, err := api.GetObjects(exp.namespace, exp.resType, exp.name)
+			pods, err := api.GetObjects(exp.namespace, exp.resType, exp.name, labels.Everything())
 			if err != nil || exp.err != nil {
 				if (err == nil && exp.err != nil) ||
 					(err != nil && exp.err == nil) ||
@@ -327,7 +329,7 @@ status:
 					t.Fatalf("newAPI error: %s", err)
 				}
 
-				pods, err := api.GetObjects(exp.namespace, exp.resType, exp.name)
+				pods, err := api.GetObjects(exp.namespace, exp.resType, exp.name, labels.Everything())
 				if err != nil {
 					t.Fatalf("api.GetObjects() unexpected error %s", err)
 				}
@@ -384,7 +386,7 @@ status:
 					t.Fatalf("newAPI error: %s", err)
 				}
 
-				pods, err := api.GetObjects(exp.namespace, exp.resType, exp.name)
+				pods, err := api.GetObjects(exp.namespace, exp.resType, exp.name, labels.Everything())
 				if err != nil {
 					t.Fatalf("api.GetObjects() unexpected error %s", err)
 				}
diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go
index 0ce06776e..777ea8faa 100644
--- a/pkg/healthcheck/healthcheck_test.go
+++ b/pkg/healthcheck/healthcheck_test.go
@@ -2475,7 +2475,7 @@ func runIdentityCheckTestCase(t *testing.T, testID int, testDescription string,
 		_, hc.linkerdConfig, _ = hc.checkLinkerdConfigConfigMap()
 
 		if testDescription != "certificate config is valid" {
-			hc.issuerCert, hc.roots, _ = hc.checkCertificatesConfig()
+			hc.issuerCert, hc.trustAnchors, _ = hc.checkCertificatesConfig()
 		}
 
 		if err != nil {
@@ -2607,22 +2607,22 @@ func TestLinkerdIdentityCheckCertValidity(t *testing.T) {
 		expectedOutput   []string
 	}{
 		{
-			checkerToTest:    "trust roots are within their validity period",
-			checkDescription: "fails when the only root cert is not valid yet",
+			checkerToTest:    "trust anchors are within their validity period",
+			checkDescription: "fails when the only anchor is not valid yet",
 			lifespan: &lifeSpan{
 				starts: time.Date(2100, 1, 1, 1, 1, 1, 1, time.UTC),
 				ends:   time.Date(2101, 1, 1, 1, 1, 1, 1, time.UTC),
 			},
-			expectedOutput: []string{"linkerd-identity-test-cat trust roots are within their validity period: Invalid roots:\n\t* 1 identity.linkerd.cluster.local not valid before: 2100-01-01T01:00:51Z"},
+			expectedOutput: []string{"linkerd-identity-test-cat trust anchors are within their validity period: Invalid anchors:\n\t* 1 identity.linkerd.cluster.local not valid before: 2100-01-01T01:00:51Z"},
 		},
 		{
-			checkerToTest:    "trust roots are within their validity period",
-			checkDescription: "fails when the only root cert is expired",
+			checkerToTest:    "trust anchors are within their validity period",
+			checkDescription: "fails when the only trust anchor is expired",
 			lifespan: &lifeSpan{
 				starts: time.Date(1989, 1, 1, 1, 1, 1, 1, time.UTC),
 				ends:   time.Date(1990, 1, 1, 1, 1, 1, 1, time.UTC),
 			},
-			expectedOutput: []string{"linkerd-identity-test-cat trust roots are within their validity period: Invalid roots:\n\t* 1 identity.linkerd.cluster.local not valid anymore. Expired on 1990-01-01T01:01:11Z"},
+			expectedOutput: []string{"linkerd-identity-test-cat trust anchors are within their validity period: Invalid anchors:\n\t* 1 identity.linkerd.cluster.local not valid anymore. Expired on 1990-01-01T01:01:11Z"},
 		},
 		{
 			checkerToTest:    "issuer cert is within its validity period",
@@ -2654,11 +2654,11 @@ func TestLinkerdIdentityCheckCertValidity(t *testing.T) {
 }
 
 func TestLinkerdIdentityCheckWrongDns(t *testing.T) {
-	expectedOutput := []string{"linkerd-identity-test-cat issuer cert is issued by the trust root: x509: certificate is valid for wrong.linkerd.cluster.local, not identity.linkerd.cluster.local"}
+	expectedOutput := []string{"linkerd-identity-test-cat issuer cert is issued by the trust anchor: x509: certificate is valid for wrong.linkerd.cluster.local, not identity.linkerd.cluster.local"}
 	issuerData := createIssuerData("wrong.linkerd.cluster.local", time.Now().AddDate(-1, 0, 0), time.Now().AddDate(1, 0, 0))
 	fakeConfigMap := getFakeConfigMap(k8s.IdentityIssuerSchemeLinkerd, issuerData)
 	fakeSecret := getFakeSecret(k8s.IdentityIssuerSchemeLinkerd, issuerData)
-	runIdentityCheckTestCase(t, 0, "fails when cert dns is wrong", "issuer cert is issued by the trust root", fakeConfigMap, fakeSecret, expectedOutput)
+	runIdentityCheckTestCase(t, 0, "fails when cert dns is wrong", "issuer cert is issued by the trust anchor", fakeConfigMap, fakeSecret, expectedOutput)
 
 }
 
diff --git a/test/testdata/check.golden b/test/testdata/check.golden
index c22570ce1..18861f3b9 100644
--- a/test/testdata/check.golden
+++ b/test/testdata/check.golden
@@ -32,13 +32,13 @@ linkerd-config
 linkerd-identity
 ----------------
 √ certificate config is valid
-√ trust roots are using supported crypto algorithm
-√ trust roots are within their validity period
-√ trust roots are valid for at least 60 days
+√ trust anchors are using supported crypto algorithm
+√ trust anchors are within their validity period
+√ trust anchors are valid for at least 60 days
 √ issuer cert is using supported crypto algorithm
 √ issuer cert is within its validity period
 √ issuer cert is valid for at least 60 days
-√ issuer cert is issued by the trust root
+√ issuer cert is issued by the trust anchor
 
 linkerd-api
 -----------
diff --git a/test/testdata/check.proxy.golden b/test/testdata/check.proxy.golden
index 2e6a15469..368aaf55b 100644
--- a/test/testdata/check.proxy.golden
+++ b/test/testdata/check.proxy.golden
@@ -32,13 +32,13 @@ linkerd-config
 linkerd-identity
 ----------------
 √ certificate config is valid
-√ trust roots are using supported crypto algorithm
-√ trust roots are within their validity period
-√ trust roots are valid for at least 60 days
+√ trust anchors are using supported crypto algorithm
+√ trust anchors are within their validity period
+√ trust anchors are valid for at least 60 days
 √ issuer cert is using supported crypto algorithm
 √ issuer cert is within its validity period
 √ issuer cert is valid for at least 60 days
-√ issuer cert is issued by the trust root
+√ issuer cert is issued by the trust anchor
 
 linkerd-identity-data-plane
 ---------------------------