Repository: kiali/kiali
Pull request #7477 (2024-06-26 18:06:32)

Autodetect istiod deployment name
### Describe the change

Autodetect istiod deployment name instead of relying on the config value.

Also removes the individual istiod pods from the istio status. If istiod pods are unhealthy it will still reflect in the status since this comes from the deployment status. If Kiali can't port forward to the istiod pods then it will update the status as unreachable. Moves the check for kiali connecting to the istiod pods to the `istio.Discovery.Mesh` method.

### Steps to test the PR

1. Deploy kiali and istio
2. Set `external_services.istio.deployment_name = ""`
3. Ensure kiali still works

Test Multiple revisions with sail:

1. Deploy istio with sail: https://github.com/istio-ecosystem/sail-operator?tab=readme-ov-file#deploying-the-operator
2. Deploy `Istio` resource: 
    ```
    # Namespace must exist prior to creating istio resource
    kubectl get ns istio-system || kubectl create ns istio-system
    kubectl apply -f - <<EOF
    apiVersion: operator.istio.io/v1alpha1
    kind: Istio
    metadata:
      name: default
    spec:
      version: v1.22.0
      namespace: istio-system
      updateStrategy:
        type: RevisionBased
        inactiveRevisionDeletionGracePeriodSeconds: 3000
    EOF
    ```
3. Deploy kiali operator and kiali
    ```
    make build build-ui cluster-push operator-create kiali-create
    ```
4. Ensure kiali works
5. Change the version of the `Istio` resource
6. Navigate to the mesh page in kiali and verify you see both istiods, one for each revision

### Automation testing

Unit testing included

### Issue reference

Relates to #7177 

Operator PR: https://github.com/kiali/kiali-operator/pull/784



Patch:
diff --git a/business/controlplane_monitor.go b/business/controlplane_monitor.go
index a9d57c10b..0c1224e18 100644
--- a/business/controlplane_monitor.go
+++ b/business/controlplane_monitor.go
@@ -11,8 +11,6 @@ import (
 	"sync"
 	"time"
 
-	corev1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/util/wait"
 
 	"github.com/kiali/kiali/config"
@@ -28,8 +26,6 @@ import (
 // out portforwarding and polling.
 type ControlPlaneMonitor interface {
 	PollIstiodForProxyStatus(ctx context.Context)
-	CanConnectToIstiod(client kubernetes.ClientInterface) (kubernetes.IstioComponentStatus, error)
-	CanConnectToIstiodForRevision(client kubernetes.ClientInterface, revision string) (kubernetes.IstioComponentStatus, error)
 	// RefreshIstioCache should update the kiali cache's istio related stores.
 	RefreshIstioCache(ctx context.Context) error
 }
@@ -97,6 +93,15 @@ func (p *controlPlaneMonitor) RefreshIstioCache(ctx context.Context) error {
 		interval := p.pollingInterval / 2
 
 		for _, controlPlane := range controlPlanes {
+			if controlPlane.Status != kubernetes.ComponentHealthy {
+				log.Warningf("Skipping controlplane [%s] in cluster [%s] because it is not healthy.", controlPlane.Revision, cluster)
+				if controlPlane.Status == kubernetes.ComponentUnreachable {
+					log.Warningf("unable to proxy Istiod pods. " +
+						"Make sure your Kubernetes API server has access to the Istio control plane through 8080 port")
+				}
+				continue
+			}
+
 			pstatus, err := p.getProxyStatusWithRetry(ctx, interval, client, controlPlane.Revision, controlPlane.IstiodNamespace)
 			if err != nil {
 				log.Warningf("Unable to get proxy status from istiod for revision: [%s] and cluster: [%s]. Proxy status may be stale: %s", controlPlane.Revision, client.ClusterInfo().Name, err)
@@ -110,12 +115,22 @@ func (p *controlPlaneMonitor) RefreshIstioCache(ctx context.Context) error {
 		if len(controlPlanes) > 0 {
 			// Since it doesn't matter what revision we choose, just choose the first one.
 			controlPlane := controlPlanes[0]
+			if controlPlane.Status != kubernetes.ComponentHealthy {
+				log.Warningf("After choosing first revision - Skipping controlplane [%s] in cluster [%s] because it is not healthy.", controlPlane.Revision, cluster)
+				if controlPlane.Status == kubernetes.ComponentUnreachable {
+					log.Warningf("After choosing first revision - unable to proxy Istiod pods. " +
+						"Make sure your Kubernetes API server has access to the Istio control plane through 8080 port")
+				}
+				continue
+			}
+
 			status := &kubernetes.RegistryStatus{}
 			services, err := p.getServicesWithRetry(ctx, interval, client, controlPlane.Revision, controlPlane.IstiodNamespace)
 			if err != nil {
 				log.Warningf("Unable to get registry services from istiod for revision: [%s] and cluster: [%s]. Registry services may be stale: %s", controlPlane.Revision, client.ClusterInfo().Name, err)
 				continue
 			}
+
 			status.Services = services
 			registryStatus[cluster] = status
 		}
@@ -231,30 +246,14 @@ func getRequest(url string) ([]byte, error) {
 }
 
 func (p *controlPlaneMonitor) getIstiodDebugStatus(client kubernetes.ClientInterface, revision string, namespace string, debugPath string) (map[string][]byte, error) {
-	// Check if the kube-api has proxy access to pods in the istio-system
-	// https://github.com/kiali/kiali/issues/3494#issuecomment-772486224
-	status, err := p.canConnectToIstiodForRevision(client, revision, namespace)
+	kubeCache, err := p.cache.GetKubeCache(client.ClusterInfo().Name)
 	if err != nil {
-		return nil, fmt.Errorf("unable to connect to Istiod pods on cluster [%s] for revision [%s]: %s", client.ClusterInfo().Name, revision, err.Error())
-	}
-
-	istiodReachable := false
-	for _, istiodStatus := range status {
-		if istiodStatus.Status != kubernetes.ComponentUnreachable {
-			istiodReachable = true
-			break
-		}
-	}
-	if !istiodReachable {
-		return nil, fmt.Errorf("unable to proxy Istiod pods. " +
-			"Make sure your Kubernetes API server has access to the Istio control plane through 8080 port")
+		return nil, err
 	}
 
-	var healthyIstiods kubernetes.IstioComponentStatus
-	for _, istiod := range status {
-		if istiod.Status == kubernetes.ComponentHealthy {
-			healthyIstiods = append(healthyIstiods, istiod)
-		}
+	healthyIstiods, err := istio.GetHealthyIstiodPods(kubeCache, revision, namespace)
+	if err != nil {
+		return nil, err
 	}
 
 	wg := sync.WaitGroup{}
@@ -305,116 +304,6 @@ func (p *controlPlaneMonitor) getIstiodDebugStatus(client kubernetes.ClientInter
 	}
 }
 
-// CanConnectToIstiod checks if Kiali can reach the istiod pod(s) via port
-// fowarding through the k8s api server or via http if the registry is
-// configured with a remote url. An error does not indicate that istiod
-// cannot be reached. The kubernetes.IstioComponentStatus must be checked.
-func (p *controlPlaneMonitor) CanConnectToIstiodForRevision(client kubernetes.ClientInterface, revision string) (kubernetes.IstioComponentStatus, error) {
-	return p.canConnectToIstiodForRevision(client, revision, p.conf.IstioNamespace)
-}
-
-// CanConnectToIstiod checks if Kiali can reach the istiod pod(s) via port
-// fowarding through the k8s api server or via http if the registry is
-// configured with a remote url. An error does not indicate that istiod
-// cannot be reached. The kubernetes.IstioComponentStatus must be checked.
-func (p *controlPlaneMonitor) canConnectToIstiodForRevision(client kubernetes.ClientInterface, revision string, namespace string) (kubernetes.IstioComponentStatus, error) {
-	if p.conf.ExternalServices.Istio.Registry != nil && p.conf.ExternalServices.Istio.Registry.IstiodURL != "" {
-		istiodURL := p.conf.ExternalServices.Istio.Registry.IstiodURL
-		// Being able to hit /debug doesn't necessarily mean we are authorized to hit the others.
-		url := joinURL(istiodURL, "/debug")
-		if _, err := getRequest(url); err != nil {
-			log.Warningf("Kiali can't connect to remote Istiod: %s", err)
-			return kubernetes.IstioComponentStatus{{Name: istiodURL, Status: kubernetes.ComponentUnreachable, IsCore: true}}, nil
-		}
-		return kubernetes.IstioComponentStatus{{Name: istiodURL, Status: kubernetes.ComponentHealthy, IsCore: true}}, nil
-	}
-
-	kubeCache, err := p.cache.GetKubeCache(client.ClusterInfo().Name)
-	if err != nil {
-		return nil, err
-	}
-
-	podLabels := map[string]string{
-		"app":                     "istiod",
-		models.IstioRevisionLabel: revision,
-	}
-	istiods, err := kubeCache.GetPods(namespace, labels.Set(podLabels).String())
-	if err != nil {
-		return nil, err
-	}
-
-	healthyIstiods := make([]*corev1.Pod, 0, len(istiods))
-	for i, istiod := range istiods {
-		if istiod.Status.Phase == corev1.PodRunning {
-			healthyIstiods = append(healthyIstiods, &istiods[i])
-		}
-	}
-
-	wg := sync.WaitGroup{}
-	wg.Add(len(healthyIstiods))
-	syncChan := make(chan kubernetes.ComponentStatus, len(healthyIstiods))
-
-	for _, istiod := range healthyIstiods {
-		go func(name, namespace string) {
-			defer wg.Done()
-
-			status := kubernetes.ComponentHealthy
-			// The 8080 port is not accessible from outside of the pod. However, it is used for kubernetes to do the live probes.
-			// Using the proxy method to make sure that K8s API has access to the Istio Control Plane namespace.
-			// By proxying one Istiod, we ensure that the following connection is allowed:
-			// Kiali -> K8s API (proxy) -> istiod
-			// This scenario is not obvious for private clusters (like GKE private cluster)
-			_, err := client.ForwardGetRequest(namespace, name, 8080, "/ready")
-			if err != nil {
-				log.Warningf("Unable to get ready status of istiod: %s/%s. Err: %s", namespace, name, err)
-				status = kubernetes.ComponentUnreachable
-			}
-
-			syncChan <- kubernetes.ComponentStatus{
-				Name:      name,
-				Namespace: namespace,
-				Status:    status,
-				IsCore:    true,
-			}
-		}(istiod.Name, istiod.Namespace)
-	}
-
-	wg.Wait()
-	close(syncChan)
-	ics := kubernetes.IstioComponentStatus{}
-	for componentStatus := range syncChan {
-		ics.Merge(kubernetes.IstioComponentStatus{componentStatus})
-	}
-
-	return ics, nil
-}
-
-func (p *controlPlaneMonitor) CanConnectToIstiod(client kubernetes.ClientInterface) (kubernetes.IstioComponentStatus, error) {
-	if p.conf.ExternalServices.Istio.Registry != nil && p.conf.ExternalServices.Istio.Registry.IstiodURL != "" {
-		istiodURL := p.conf.ExternalServices.Istio.Registry.IstiodURL
-		// Being able to hit /debug doesn't necessarily mean we are authorized to hit the others.
-		url := joinURL(istiodURL, "/debug")
-		if _, err := getRequest(url); err != nil {
-			log.Warningf("Kiali can't connect to remote Istiod: %s", err)
-			return kubernetes.IstioComponentStatus{{Name: istiodURL, Status: kubernetes.ComponentUnreachable, IsCore: true}}, nil
-		}
-		return kubernetes.IstioComponentStatus{{Name: istiodURL, Status: kubernetes.ComponentHealthy, IsCore: true}}, nil
-	}
-
-	kubeCache, err := p.cache.GetKubeCache(client.ClusterInfo().Name)
-	if err != nil {
-		return nil, err
-	}
-
-	// Find the rev label for the controlplane that is set in the config.
-	istiod, err := kubeCache.GetDeployment(p.conf.IstioNamespace, p.conf.ExternalServices.Istio.IstiodDeploymentName)
-	if err != nil {
-		return nil, err
-	}
-
-	return p.CanConnectToIstiodForRevision(client, istiod.Labels[models.IstioRevisionLabel])
-}
-
 func parseProxyStatus(statuses map[string][]byte) ([]*kubernetes.ProxyStatus, error) {
 	var fullStatus []*kubernetes.ProxyStatus
 	for pilot, status := range statuses {
diff --git a/business/fake_controlplane_monitor.go b/business/fake_controlplane_monitor.go
index 2b24f15b0..7ca5e967d 100644
--- a/business/fake_controlplane_monitor.go
+++ b/business/fake_controlplane_monitor.go
@@ -2,24 +2,13 @@ package business
 
 import (
 	"context"
-
-	"github.com/kiali/kiali/kubernetes"
 )
 
 // FakeControlPlaneMonitor is used for testing and implements ControlPlaneMonitor.
-type FakeControlPlaneMonitor struct {
-	status kubernetes.IstioComponentStatus
-}
+type FakeControlPlaneMonitor struct{}
 
 func (f *FakeControlPlaneMonitor) PollIstiodForProxyStatus(ctx context.Context) {}
-func (f *FakeControlPlaneMonitor) CanConnectToIstiod(client kubernetes.ClientInterface) (kubernetes.IstioComponentStatus, error) {
-	return f.status, nil
-}
-
-func (f *FakeControlPlaneMonitor) CanConnectToIstiodForRevision(client kubernetes.ClientInterface, revision string) (kubernetes.IstioComponentStatus, error) {
-	return f.status, nil
-}
-func (f *FakeControlPlaneMonitor) RefreshIstioCache(ctx context.Context) error { return nil }
+func (f *FakeControlPlaneMonitor) RefreshIstioCache(ctx context.Context) error  { return nil }
 
 // Interface guard
 var _ ControlPlaneMonitor = &FakeControlPlaneMonitor{}
diff --git a/business/istio_status.go b/business/istio_status.go
index e36d706a4..ce1427abb 100644
--- a/business/istio_status.go
+++ b/business/istio_status.go
@@ -16,33 +16,48 @@ import (
 	"github.com/kiali/kiali/util/httputil"
 )
 
-func NewIstioStatusService(userClients map[string]kubernetes.ClientInterface, businessLayer *Layer, cpm ControlPlaneMonitor) IstioStatusService {
+func NewIstioStatusService(
+	conf *config.Config,
+	homeClusterSAClient kubernetes.ClientInterface,
+	userClients map[string]kubernetes.ClientInterface,
+	tracing *TracingService,
+	workloads *WorkloadService,
+	discovery meshDiscovery,
+) IstioStatusService {
 	return IstioStatusService{
+		conf:                conf,
+		discovery:           discovery,
+		homeClusterSAClient: homeClusterSAClient,
+		tracing:             tracing,
 		userClients:         userClients,
-		businessLayer:       businessLayer,
-		controlPlaneMonitor: cpm,
+		workloads:           workloads,
 	}
 }
 
 // SvcService deals with fetching istio/kubernetes services related content and convert to kiali model
 type IstioStatusService struct {
+	conf                *config.Config
+	discovery           meshDiscovery
+	homeClusterSAClient kubernetes.ClientInterface
+	tracing             *TracingService
 	userClients         map[string]kubernetes.ClientInterface
-	businessLayer       *Layer
-	controlPlaneMonitor ControlPlaneMonitor
+	workloads           *WorkloadService
 }
 
-func (iss *IstioStatusService) GetStatus(ctx context.Context, cluster string) (kubernetes.IstioComponentStatus, error) {
+func (iss *IstioStatusService) GetStatus(ctx context.Context) (kubernetes.IstioComponentStatus, error) {
 	var end observability.EndFunc
 	ctx, end = observability.StartSpan(ctx, "GetStatus",
 		observability.Attribute("package", "business"),
 	)
 	defer end()
 
-	if !config.Get().ExternalServices.Istio.ComponentStatuses.Enabled || !config.Get().ExternalServices.Istio.IstioAPIEnabled {
+	if !iss.conf.ExternalServices.Istio.ComponentStatuses.Enabled || !iss.conf.ExternalServices.Istio.IstioAPIEnabled {
 		return kubernetes.IstioComponentStatus{}, nil
 	}
 
-	ics, err := iss.getIstioComponentStatus(ctx, cluster)
+	// TODO: Multi-primary and external controlplane support.
+	// Right now this only gets the status of the control plane in the home cluster.
+	ics, err := iss.getIstioComponentStatus(ctx, iss.conf.KubernetesConfig.ClusterName)
 	if err != nil {
 		return nil, err
 	}
@@ -62,16 +77,24 @@ func (iss *IstioStatusService) getIstioComponentStatus(ctx context.Context, clus
 		return kubernetes.IstioComponentStatus{}, err
 	}
 
-	k8s, ok := iss.userClients[cluster]
-	if !ok {
-		return kubernetes.IstioComponentStatus{}, fmt.Errorf("Cluster %s doesn't exist ", cluster)
-	}
-
-	istiodStatus, err := iss.controlPlaneMonitor.CanConnectToIstiod(k8s)
+	mesh, err := iss.discovery.Mesh(ctx)
 	if err != nil {
 		return kubernetes.IstioComponentStatus{}, err
 	}
 
+	var istiodStatus kubernetes.IstioComponentStatus
+	for _, cp := range mesh.ControlPlanes {
+		if cp.Cluster.Name == cluster {
+			istiodStatus = append(istiodStatus, kubernetes.ComponentStatus{
+				Cluster:   cp.Cluster.Name,
+				Name:      cp.IstiodName,
+				Namespace: cp.IstiodNamespace,
+				Status:    cp.Status,
+				IsCore:    true,
+			})
+		}
+	}
+
 	return deploymentStatus.Merge(istiodStatus), nil
 }
 
@@ -81,7 +104,7 @@ func (iss *IstioStatusService) getComponentNamespacesWorkloads(ctx context.Conte
 	nss := map[string]bool{}
 	wls := make([]*models.Workload, 0)
 
-	comNs := getComponentNamespaces()
+	comNs := getComponentNamespaces(iss.conf)
 
 	wlChan := make(chan []*models.Workload, len(comNs))
 	errChan := make(chan error, len(comNs))
@@ -95,7 +118,7 @@ func (iss *IstioStatusService) getComponentNamespacesWorkloads(ctx context.Conte
 				defer wg.Done()
 				var wls models.Workloads
 				var err error
-				wls, err = iss.businessLayer.Workload.fetchWorkloadsFromCluster(ctx, cluster, n, "")
+				wls, err = iss.workloads.fetchWorkloadsFromCluster(ctx, cluster, n, "")
 				wliChan <- wls
 				errChan <- err
 			}(ctx, n, wlChan, errChan)
@@ -121,14 +144,14 @@ func (iss *IstioStatusService) getComponentNamespacesWorkloads(ctx context.Conte
 	return wls, nil
 }
 
-func getComponentNamespaces() []string {
+func getComponentNamespaces(conf *config.Config) []string {
 	nss := make([]string, 0)
 
 	// By default, add the istio control plane namespace
-	nss = append(nss, config.Get().IstioNamespace)
+	nss = append(nss, conf.IstioNamespace)
 
 	// Adding Istio Components namespaces
-	externalServices := config.Get().ExternalServices
+	externalServices := conf.ExternalServices
 	for _, cmp := range externalServices.Istio.ComponentStatuses.Components {
 		if cmp.Namespace != "" {
 			nss = append(nss, cmp.Namespace)
@@ -138,9 +161,9 @@ func getComponentNamespaces() []string {
 	return nss
 }
 
-func istioCoreComponents() map[string]config.ComponentStatus {
+func istioCoreComponents(conf *config.Config) map[string]config.ComponentStatus {
 	components := map[string]config.ComponentStatus{}
-	cs := config.Get().ExternalServices.Istio.ComponentStatuses
+	cs := conf.ExternalServices.Istio.ComponentStatuses
 	for _, c := range cs.Components {
 		components[c.AppLabel] = c
 	}
@@ -148,7 +171,7 @@ func istioCoreComponents() map[string]config.ComponentStatus {
 }
 
 func (iss *IstioStatusService) getStatusOf(workloads []*models.Workload) (kubernetes.IstioComponentStatus, error) {
-	statusComponents := istioCoreComponents()
+	statusComponents := istioCoreComponents(iss.conf)
 	isc := kubernetes.IstioComponentStatus{}
 	cf := map[string]bool{}
 	mcf := map[string]int{}
@@ -176,9 +199,10 @@ func (iss *IstioStatusService) getStatusOf(workloads []*models.Workload) (kubern
 		status := GetWorkloadStatus(*workload)
 		// Add status
 		isc = append(isc, kubernetes.ComponentStatus{
-			Name:   workload.Name,
-			Status: status,
-			IsCore: stat.IsCore,
+			Cluster: workload.Cluster,
+			Name:    workload.Name,
+			Status:  status,
+			IsCore:  stat.IsCore,
 		})
 
 	}
@@ -225,7 +249,7 @@ func (iss *IstioStatusService) getAddonComponentStatus() kubernetes.IstioCompone
 	wg.Add(4)
 
 	staChan := make(chan kubernetes.IstioComponentStatus, 4)
-	extServices := config.Get().ExternalServices
+	extServices := iss.conf.ExternalServices
 
 	// https://github.com/kiali/kiali/issues/6966 - use the well-known Prom healthy endpoint
 	if extServices.Prometheus.HealthCheckUrl == "" {
@@ -234,8 +258,8 @@ func (iss *IstioStatusService) getAddonComponentStatus() kubernetes.IstioCompone
 
 	ics := kubernetes.IstioComponentStatus{}
 
-	go getAddonStatus("prometheus", true, extServices.Prometheus.IsCore, &extServices.Prometheus.Auth, extServices.Prometheus.URL, extServices.Prometheus.HealthCheckUrl, staChan, &wg)
-	go getAddonStatus("grafana", extServices.Grafana.Enabled, extServices.Grafana.IsCore, &extServices.Grafana.Auth, extServices.Grafana.InClusterURL, extServices.Grafana.HealthCheckUrl, staChan, &wg)
+	go iss.getAddonStatus("prometheus", true, extServices.Prometheus.IsCore, &extServices.Prometheus.Auth, extServices.Prometheus.URL, extServices.Prometheus.HealthCheckUrl, staChan, &wg)
+	go iss.getAddonStatus("grafana", extServices.Grafana.Enabled, extServices.Grafana.IsCore, &extServices.Grafana.Auth, extServices.Grafana.InClusterURL, extServices.Grafana.HealthCheckUrl, staChan, &wg)
 	go iss.getTracingStatus("tracing", extServices.Tracing.Enabled, extServices.Tracing.IsCore, staChan, &wg)
 
 	// Custom dashboards may use the main Prometheus config
@@ -243,7 +267,7 @@ func (iss *IstioStatusService) getAddonComponentStatus() kubernetes.IstioCompone
 	if customProm.URL == "" {
 		customProm = extServices.Prometheus
 	}
-	go getAddonStatus("custom dashboards", extServices.CustomDashboards.Enabled, extServices.CustomDashboards.IsCore, &customProm.Auth, customProm.URL, customProm.HealthCheckUrl, staChan, &wg)
+	go iss.getAddonStatus("custom dashboards", extServices.CustomDashboards.Enabled, extServices.CustomDashboards.IsCore, &customProm.Auth, customProm.URL, customProm.HealthCheckUrl, staChan, &wg)
 
 	wg.Wait()
 
@@ -255,7 +279,7 @@ func (iss *IstioStatusService) getAddonComponentStatus() kubernetes.IstioCompone
 	return ics
 }
 
-func getAddonStatus(name string, enabled bool, isCore bool, auth *config.Auth, url string, healthCheckUrl string, staChan chan<- kubernetes.IstioComponentStatus, wg *sync.WaitGroup) {
+func (iss *IstioStatusService) getAddonStatus(name string, enabled bool, isCore bool, auth *config.Auth, url string, healthCheckUrl string, staChan chan<- kubernetes.IstioComponentStatus, wg *sync.WaitGroup) {
 	defer wg.Done()
 
 	// When the addOn is disabled, don't perform any check
@@ -269,11 +293,7 @@ func getAddonStatus(name string, enabled bool, isCore bool, auth *config.Auth, u
 	}
 
 	if auth.UseKialiToken {
-		token, _, err := kubernetes.GetKialiTokenForHomeCluster()
-		if err != nil {
-			log.Errorf("Could not read the Kiali Service Account token: %v", err)
-		}
-		auth.Token = token
+		auth.Token = iss.homeClusterSAClient.GetToken()
 	}
 
 	status := kubernetes.ComponentHealthy
@@ -302,7 +322,7 @@ func (iss *IstioStatusService) getTracingStatus(name string, enabled bool, isCor
 
 	status := kubernetes.ComponentHealthy
 
-	accessible, err := iss.businessLayer.Tracing.GetStatus()
+	accessible, err := iss.tracing.GetStatus()
 	if !accessible {
 		log.Errorf("Error fetching availability of the tracing service: %v", err)
 		status = kubernetes.ComponentUnreachable
diff --git a/business/istio_validations.go b/business/istio_validations.go
index 214d9dc89..ee217cd27 100644
--- a/business/istio_validations.go
+++ b/business/istio_validations.go
@@ -648,15 +648,20 @@ func (in *IstioValidationsService) isGatewayToNamespace() bool {
 }
 
 func (in *IstioValidationsService) isPolicyAllowAny() bool {
-	allowAny := false
-	if in.businessLayer != nil {
-		if otp, err := in.businessLayer.Mesh.OutboundTrafficPolicy(); err == nil {
-			if otp.Mode == "" || otp.Mode == AllowAny {
-				return true
-			}
+	mesh, err := in.discovery.Mesh(context.TODO())
+	if err != nil {
+		log.Errorf("Error getting mesh config: %s", err)
+		return false
+	}
+
+	// TODO: Multi-primary support
+	for _, controlPlane := range mesh.ControlPlanes {
+		if controlPlane.Cluster.IsKialiHome {
+			return controlPlane.Config.OutboundTrafficPolicy.Mode == AllowAny || controlPlane.Config.OutboundTrafficPolicy.Mode == ""
 		}
 	}
-	return allowAny
+
+	return false
 }
 
 func checkExportTo(exportToNs string, namespace string, ownNs string, allNamespaces models.Namespaces) bool {
diff --git a/business/layer.go b/business/layer.go
index e365d906f..16028ce69 100644
--- a/business/layer.go
+++ b/business/layer.go
@@ -116,7 +116,6 @@ func newLayer(
 	temporaryLayer.App = NewAppService(temporaryLayer, conf, prom, grafana, userClients)
 	temporaryLayer.Health = HealthService{prom: prom, businessLayer: temporaryLayer, userClients: userClients}
 	temporaryLayer.IstioConfig = IstioConfigService{config: *conf, userClients: userClients, kialiCache: cache, businessLayer: temporaryLayer, controlPlaneMonitor: poller}
-	temporaryLayer.IstioStatus = NewIstioStatusService(userClients, temporaryLayer, poller)
 	temporaryLayer.IstioCerts = NewIstioCertsService(conf, discovery, userClients[homeClusterName])
 	temporaryLayer.Namespace = NewNamespaceService(userClients, kialiSAClients, cache, conf, discovery)
 	temporaryLayer.Mesh = NewMeshService(kialiSAClients, cache, temporaryLayer.Namespace, conf, discovery)
@@ -130,6 +129,7 @@ func newLayer(
 	temporaryLayer.Workload = *NewWorkloadService(userClients, prom, cache, temporaryLayer, conf, grafana)
 
 	temporaryLayer.Tracing = NewTracingService(conf, traceClient, &temporaryLayer.Svc, &temporaryLayer.Workload)
+	temporaryLayer.IstioStatus = NewIstioStatusService(conf, kialiSAClients[homeClusterName], userClients, &temporaryLayer.Tracing, &temporaryLayer.Workload, discovery)
 	return temporaryLayer
 }
 
diff --git a/business/mesh.go b/business/mesh.go
index 68554e575..d035b913e 100644
--- a/business/mesh.go
+++ b/business/mesh.go
@@ -1,16 +1,11 @@
 package business
 
 import (
-	"context"
 	"fmt"
 
-	"k8s.io/apimachinery/pkg/api/errors"
-	"k8s.io/apimachinery/pkg/api/resource"
-
 	"github.com/kiali/kiali/config"
 	"github.com/kiali/kiali/kubernetes"
 	"github.com/kiali/kiali/kubernetes/cache"
-	"github.com/kiali/kiali/log"
 	"github.com/kiali/kiali/models"
 )
 
@@ -58,58 +53,6 @@ func NewMeshService(
 	}
 }
 
-func (in *MeshService) OutboundTrafficPolicy() (*models.OutboundPolicy, error) {
-	mesh, err := in.discovery.Mesh(context.TODO())
-	if err != nil {
-		return nil, err
-	}
-
-	// TODO: Support multi-primary
-	policy := &models.OutboundPolicy{}
-	for _, cp := range mesh.ControlPlanes {
-		if cp.Cluster.IsKialiHome {
-			policy = &cp.Config.OutboundTrafficPolicy
-			break
-		}
-	}
-
-	if policy.Mode == "" {
-		policy.Mode = AllowAny
-	}
-
-	return policy, nil
-}
-
-func (in *MeshService) IstiodResourceThresholds() (*models.IstiodThresholds, error) {
-	istioDeploymentConfig := in.conf.ExternalServices.Istio.IstiodDeploymentName
-	homeClusterCache, err := in.kialiCache.GetKubeCache(in.conf.KubernetesConfig.ClusterName)
-	if err != nil {
-		return nil, err
-	}
-
-	istioDeployment, err := homeClusterCache.GetDeployment(in.conf.IstioNamespace, istioDeploymentConfig)
-	if err != nil {
-		if errors.IsNotFound(err) {
-			log.Debugf("Istiod deployment [%s] not found in namespace [%s]", istioDeploymentConfig, in.conf.IstioNamespace)
-		}
-		return nil, err
-	}
-
-	thresholds := models.IstiodThresholds{}
-	deploymentContainers := istioDeployment.Spec.Template.Spec.Containers
-	// Assuming that the first container is the istiod container.
-	if len(deploymentContainers) > 0 {
-		if memoryLimit := deploymentContainers[0].Resources.Limits.Memory(); memoryLimit != nil {
-			thresholds.Memory = float64(memoryLimit.ScaledValue(resource.Mega))
-		}
-		if cpuLimit := deploymentContainers[0].Resources.Limits.Cpu(); cpuLimit != nil {
-			thresholds.CPU = cpuLimit.AsApproximateFloat64()
-		}
-	}
-
-	return &thresholds, nil
-}
-
 func (in *MeshService) CanaryUpgradeStatus() (*models.CanaryUpgradeStatus, error) {
 	upgrade := in.conf.ExternalServices.Istio.IstioCanaryRevision.Upgrade
 	current := in.conf.ExternalServices.Istio.IstioCanaryRevision.Current
diff --git a/config/config.go b/config/config.go
index 685090c5f..b62cfc7a8 100644
--- a/config/config.go
+++ b/config/config.go
@@ -711,7 +711,7 @@ func NewConfig() (c *Config) {
 				IstioInjectionAnnotation:          "sidecar.istio.io/inject",
 				IstioSidecarInjectorConfigMapName: "istio-sidecar-injector",
 				IstioSidecarAnnotation:            "sidecar.istio.io/status",
-				IstiodDeploymentName:              "istiod",
+				IstiodDeploymentName:              "",
 				IstiodPodMonitoringPort:           15014,
 				IstiodPollingIntervalSeconds:      20,
 				RootNamespace:                     "istio-system",
diff --git a/frontend/src/components/Nav/Masthead/Masthead.tsx b/frontend/src/components/Nav/Masthead/Masthead.tsx
index 4ba826420..8beb2f094 100644
--- a/frontend/src/components/Nav/Masthead/Masthead.tsx
+++ b/frontend/src/components/Nav/Masthead/Masthead.tsx
@@ -38,7 +38,7 @@ export const MastheadItems: React.FC = () => {
               <ThemeSwitch />
             </FlexItem>
             <FlexItem>
-              <IstioStatus cluster={homeCluster?.name} location={'masthead'} />
+              <IstioStatus location={'masthead'} />
             </FlexItem>
             <FlexItem style={{ marginRight: 0 }}>
               <MessageCenterTrigger />
diff --git a/frontend/src/components/VirtualList/Renderers.tsx b/frontend/src/components/VirtualList/Renderers.tsx
index 963fc5adc..7de3305cf 100644
--- a/frontend/src/components/VirtualList/Renderers.tsx
+++ b/frontend/src/components/VirtualList/Renderers.tsx
@@ -18,7 +18,7 @@ import { Health } from '../../types/Health';
 import { NamespaceInfo } from '../../types/NamespaceInfo';
 import { NamespaceMTLSStatus } from '../MTls/NamespaceMTLSStatus';
 import { ValidationSummary } from '../Validations/ValidationSummary';
-import { OverviewCardSparklineCharts } from '../../pages/Overview/OverviewCardSparklineCharts';
+import { OverviewCardDataPlaneNamespace } from '../../pages/Overview/OverviewCardDataPlaneNamespace';
 import { OverviewToolbar } from '../../pages/Overview/OverviewToolbar';
 import { StatefulFiltersRef } from '../Filters/StatefulFilters';
 import { IstioObjectLink, GetIstioObjectUrl, infoStyle } from '../Link/IstioObjectLink';
@@ -222,15 +222,12 @@ export const status: Renderer<NamespaceInfo> = (ns: NamespaceInfo) => {
           />
         )}
 
-        <OverviewCardSparklineCharts
+        <OverviewCardDataPlaneNamespace
           key={`${ns.name}_chart`}
-          name={ns.name}
-          annotations={ns.annotations}
           duration={FilterHelper.currentDuration()}
           direction={OverviewToolbar.currentDirectionType()}
           metrics={ns.metrics}
           errorMetrics={ns.errorMetrics}
-          controlPlaneMetrics={ns.controlPlaneMetrics}
         />
       </Td>
     );
diff --git a/frontend/src/pages/Mesh/target/TargetPanelControlPlane.tsx b/frontend/src/pages/Mesh/target/TargetPanelControlPlane.tsx
index 6c4953b7d..b5eb50acb 100644
--- a/frontend/src/pages/Mesh/target/TargetPanelControlPlane.tsx
+++ b/frontend/src/pages/Mesh/target/TargetPanelControlPlane.tsx
@@ -10,7 +10,7 @@ import {
 } from './TargetPanelCommon';
 import { Title, TitleSizes } from '@patternfly/react-core';
 import { serverConfig } from 'config';
-import { CanaryUpgradeStatus, OutboundTrafficPolicy } from 'types/IstioObjects';
+import { CanaryUpgradeStatus } from 'types/IstioObjects';
 import { NamespaceInfo, NamespaceStatus } from 'types/NamespaceInfo';
 import { isRemoteCluster } from 'pages/Overview/OverviewCardControlPlaneNamespace';
 import { DirectionType } from 'pages/Overview/OverviewToolbar';
@@ -18,7 +18,7 @@ import { ControlPlaneNamespaceStatus } from 'pages/Overview/ControlPlaneNamespac
 import { PromisesRegistry } from 'utils/CancelablePromises';
 import { TLSInfo } from 'components/Overview/TLSInfo';
 import { CanaryUpgradeProgress } from 'pages/Overview/CanaryUpgradeProgress';
-import { OverviewCardSparklineCharts } from 'pages/Overview/OverviewCardSparklineCharts';
+import { OverviewCardControlPlaneNamespace } from 'pages/Overview/OverviewCardControlPlaneNamespace';
 import * as API from '../../../services/Api';
 import { IstioMetricsOptions } from 'types/MetricsOptions';
 import { computePrometheusRateParams } from 'services/Prometheus';
@@ -26,7 +26,6 @@ import { ApiError } from 'types/Api';
 import { DEGRADED, FAILURE, HEALTHY, Health, NOT_READY } from 'types/Health';
 import * as AlertUtils from '../../../utils/AlertUtils';
 import { MessageType } from 'types/MessageCenter';
-import { IstiodResourceThresholds } from 'types/IstioStatus';
 import { TLSStatus, nsWideMTLSStatus } from 'types/TLSStatus';
 import * as FilterHelper from '../../../components/FilterList/FilterHelper';
 import { NodeData } from '../MeshElems';
@@ -48,11 +47,9 @@ type TargetPanelControlPlaneState = {
   controlPlaneMetrics?: ControlPlaneMetricsMap;
   controlPlaneNode?: Node<NodeModel, any>;
   errorMetrics?: Metric[];
-  istiodResourceThresholds?: IstiodResourceThresholds;
   loading: boolean;
   metrics?: Metric[];
   nsInfo?: NamespaceInfo;
-  outboundPolicyMode?: OutboundTrafficPolicy;
   status?: NamespaceStatus;
   tlsStatus?: TLSStatus;
 };
@@ -62,10 +59,8 @@ const defaultState: TargetPanelControlPlaneState = {
   controlPlaneMetrics: undefined,
   controlPlaneNode: undefined,
   errorMetrics: undefined,
-  istiodResourceThresholds: undefined,
   loading: false,
   nsInfo: undefined,
-  outboundPolicyMode: undefined,
   status: undefined,
   tlsStatus: undefined
 };
@@ -123,8 +118,7 @@ export class TargetPanelControlPlane extends React.Component<
     const data = this.state.controlPlaneNode?.getData() as NodeData;
 
     // Controlplane infradata is structured: {config: configuration, revision: string}
-    const config = data.infraData?.config;
-    const revision = data.infraData?.revision;
+    const { config, revision } = data.infraData;
 
     return (
       <div
@@ -140,7 +134,7 @@ export class TargetPanelControlPlane extends React.Component<
           <MeshMTLSStatus cluster={data.cluster} revision={revision} />
 
           <ControlPlaneNamespaceStatus
-            outboundTrafficPolicy={this.state.outboundPolicyMode}
+            outboundTrafficPolicy={config.OutboundTrafficPolicy}
             namespace={nsInfo}
           ></ControlPlaneNamespaceStatus>
 
@@ -222,9 +216,7 @@ export class TargetPanelControlPlane extends React.Component<
           .registerAll(`promises-${data.cluster}:${data.namespace}`, [
             this.fetchCanariesStatus(),
             this.fetchHealthStatus(),
-            this.fetchIstiodResourceThresholds(),
             this.fetchMetrics(),
-            this.fetchOutboundTrafficPolicyMode(),
             this.fetchTLS()
           ])
           .then(_ => {
@@ -310,20 +302,6 @@ export class TargetPanelControlPlane extends React.Component<
       .catch(err => this.handleApiError('Could not fetch namespace health', err));
   };
 
-  private fetchIstiodResourceThresholds = async (): Promise<void> => {
-    if (!this.isControlPlane()) {
-      return Promise.resolve();
-    }
-
-    return API.getIstiodResourceThresholds()
-      .then(response => {
-        this.setState({ istiodResourceThresholds: response.data });
-      })
-      .catch(error => {
-        AlertUtils.addError('Error fetching Istiod resource thresholds.', error, 'default', MessageType.ERROR);
-      });
-  };
-
   private fetchMetrics = async (): Promise<void> => {
     const rateParams = computePrometheusRateParams(this.props.duration, 10);
     const options: IstioMetricsOptions = {
@@ -387,20 +365,6 @@ export class TargetPanelControlPlane extends React.Component<
       .catch(err => this.handleApiError('Could not fetch namespace TLS status', err));
   };
 
-  private fetchOutboundTrafficPolicyMode = async (): Promise<void> => {
-    if (!this.isControlPlane()) {
-      return Promise.resolve();
-    }
-
-    return API.getOutboundTrafficPolicyMode()
-      .then(response => {
-        this.setState({ outboundPolicyMode: { mode: response.data.mode } });
-      })
-      .catch(error => {
-        AlertUtils.addError('Error fetching Mesh OutboundTrafficPolicy.Mode.', error, 'default', MessageType.ERROR);
-      });
-  };
-
   private isControlPlane = (): boolean => {
     const data = this.state.controlPlaneNode!.getData() as NodeData;
     return data.namespace === serverConfig.istioNamespace;
@@ -413,18 +377,17 @@ export class TargetPanelControlPlane extends React.Component<
   private renderCharts = (): React.ReactNode => {
     if (this.state.status) {
       const data = this.state.controlPlaneNode!.getData() as NodeData;
+      const { thresholds } = data.infraData;
 
       return (
-        <OverviewCardSparklineCharts
+        <OverviewCardControlPlaneNamespace
           key={data.namespace}
-          name={data.namespace}
-          annotations={this.state.nsInfo!.annotations}
-          duration={this.props.duration}
-          direction={direction}
-          metrics={this.state.metrics}
-          errorMetrics={this.state.errorMetrics}
-          controlPlaneMetrics={this.state.controlPlaneMetrics}
-          istiodResourceThresholds={this.state.istiodResourceThresholds}
+          pilotLatency={this.state.controlPlaneMetrics?.istiod_proxy_time}
+          istiodContainerMemory={this.state.controlPlaneMetrics?.istiod_container_mem}
+          istiodContainerCpu={this.state.controlPlaneMetrics?.istiod_container_cpu}
+          istiodProcessMemory={this.state.controlPlaneMetrics?.istiod_process_mem}
+          istiodProcessCpu={this.state.controlPlaneMetrics?.istiod_process_cpu}
+          istiodResourceThresholds={thresholds}
         />
       );
     }
diff --git a/frontend/src/pages/Mesh/target/TargetPanelDataPlaneNamespace.tsx b/frontend/src/pages/Mesh/target/TargetPanelDataPlaneNamespace.tsx
index 395bbd98f..458c67e72 100644
--- a/frontend/src/pages/Mesh/target/TargetPanelDataPlaneNamespace.tsx
+++ b/frontend/src/pages/Mesh/target/TargetPanelDataPlaneNamespace.tsx
@@ -16,7 +16,7 @@ import { AmbientBadge } from 'components/Ambient/AmbientBadge';
 import { PFColors } from 'components/Pf/PfColors';
 import { ValidationSummaryLink } from 'components/Link/ValidationSummaryLink';
 import { ValidationSummary } from 'components/Validations/ValidationSummary';
-import { OverviewCardSparklineCharts } from 'pages/Overview/OverviewCardSparklineCharts';
+import { OverviewCardDataPlaneNamespace } from 'pages/Overview/OverviewCardDataPlaneNamespace';
 import * as API from '../../../services/Api';
 import { IstioMetricsOptions } from 'types/MetricsOptions';
 import { computePrometheusRateParams } from 'services/Prometheus';
@@ -26,7 +26,6 @@ import { history } from '../../../app/History';
 import * as AlertUtils from '../../../utils/AlertUtils';
 import { OverviewStatus } from 'pages/Overview/OverviewStatus';
 import { switchType } from 'pages/Overview/OverviewHelper';
-import { IstiodResourceThresholds } from 'types/IstioStatus';
 import { TLSStatus } from 'types/TLSStatus';
 import * as FilterHelper from '../../../components/FilterList/FilterHelper';
 import { panelBodyStyle, panelHeadingStyle } from 'pages/Graph/SummaryPanelStyle';
@@ -44,7 +43,6 @@ type TargetPanelDataPlaneNamespaceProps = Omit<TargetPanelCommonProps, 'target'>
 type TargetPanelDataPlaneNamespaceState = {
   errorMetricsInbound?: Metric[];
   errorMetricsOutbound?: Metric[];
-  istiodResourceThresholds?: IstiodResourceThresholds;
   loading: boolean;
   metricsInbound?: Metric[];
   metricsOutbound?: Metric[];
@@ -57,7 +55,6 @@ type TargetPanelDataPlaneNamespaceState = {
 const defaultState: TargetPanelDataPlaneNamespaceState = {
   errorMetricsInbound: undefined,
   errorMetricsOutbound: undefined,
-  istiodResourceThresholds: undefined,
   loading: false,
   metricsInbound: undefined,
   metricsOutbound: undefined,
@@ -458,15 +455,12 @@ export class TargetPanelDataPlaneNamespace extends React.Component<
       const namespace = this.props.targetNamespace;
 
       return (
-        <OverviewCardSparklineCharts
+        <OverviewCardDataPlaneNamespace
           key={`${namespace}-${direction}`}
-          name={namespace}
-          annotations={this.state.nsInfo!.annotations}
           duration={this.props.duration}
           direction={direction}
           metrics={direction === 'inbound' ? this.state.metricsInbound : this.state.metricsOutbound}
           errorMetrics={direction === 'inbound' ? this.state.errorMetricsInbound : this.state.errorMetricsOutbound}
-          istiodResourceThresholds={this.state.istiodResourceThresholds}
         />
       );
     }
diff --git a/frontend/src/pages/Mesh/target/TargetPanelNamespace.tsx b/frontend/src/pages/Mesh/target/TargetPanelNamespace.tsx
index 0beac1739..2d474df18 100644
--- a/frontend/src/pages/Mesh/target/TargetPanelNamespace.tsx
+++ b/frontend/src/pages/Mesh/target/TargetPanelNamespace.tsx
@@ -5,7 +5,7 @@ import { TargetPanelCommonProps, shouldRefreshData, targetPanelHR, targetPanelSt
 import { PFBadge, PFBadges } from 'components/Pf/PfBadges';
 import { Card, CardBody, CardHeader, Label, Title, TitleSizes, Tooltip, TooltipPosition } from '@patternfly/react-core';
 import { Paths, serverConfig } from 'config';
-import { CanaryUpgradeStatus, OutboundTrafficPolicy, ValidationStatus } from 'types/IstioObjects';
+import { CanaryUpgradeStatus, ValidationStatus } from 'types/IstioObjects';
 import { OverviewNamespaceAction, OverviewNamespaceActions } from 'pages/Overview/OverviewNamespaceActions';
 import { NamespaceInfo, NamespaceStatus } from 'types/NamespaceInfo';
 import { isRemoteCluster } from 'pages/Overview/OverviewCardControlPlaneNamespace';
@@ -22,7 +22,7 @@ import { AmbientBadge } from 'components/Ambient/AmbientBadge';
 import { PFColors } from 'components/Pf/PfColors';
 import { ValidationSummaryLink } from 'components/Link/ValidationSummaryLink';
 import { ValidationSummary } from 'components/Validations/ValidationSummary';
-import { OverviewCardSparklineCharts } from 'pages/Overview/OverviewCardSparklineCharts';
+import { OverviewCardDataPlaneNamespace } from 'pages/Overview/OverviewCardDataPlaneNamespace';
 import * as API from '../../../services/Api';
 import { IstioMetricsOptions } from 'types/MetricsOptions';
 import { computePrometheusRateParams } from 'services/Prometheus';
@@ -33,7 +33,6 @@ import * as AlertUtils from '../../../utils/AlertUtils';
 import { MessageType } from 'types/MessageCenter';
 import { OverviewStatus } from 'pages/Overview/OverviewStatus';
 import { switchType } from 'pages/Overview/OverviewHelper';
-import { IstiodResourceThresholds } from 'types/IstioStatus';
 import { TLSStatus } from 'types/TLSStatus';
 import * as FilterHelper from '../../../components/FilterList/FilterHelper';
 import { ControlPlaneMetricsMap, Metric } from 'types/Metrics';
@@ -46,11 +45,9 @@ type TargetPanelNamespaceState = {
   canaryUpgradeStatus?: CanaryUpgradeStatus;
   controlPlaneMetrics?: ControlPlaneMetricsMap;
   errorMetrics?: Metric[];
-  istiodResourceThresholds?: IstiodResourceThresholds;
   loading: boolean;
   metrics?: Metric[];
   nsInfo?: NamespaceInfo;
-  outboundPolicyMode?: OutboundTrafficPolicy;
   status?: NamespaceStatus;
   targetCluster?: string;
   targetNamespace?: string;
@@ -62,10 +59,8 @@ const defaultState: TargetPanelNamespaceState = {
   canaryUpgradeStatus: undefined,
   controlPlaneMetrics: undefined,
   errorMetrics: undefined,
-  istiodResourceThresholds: undefined,
   loading: false,
   nsInfo: undefined,
-  outboundPolicyMode: undefined,
   status: undefined,
   targetNode: undefined,
   tlsStatus: undefined
@@ -681,7 +676,6 @@ export class TargetPanelNamespace extends React.Component<TargetPanelNamespacePr
           .registerAll(`promises-${cluster}:${namespace}`, [
             this.fetchCanariesStatus(),
             this.fetchHealthStatus(),
-            this.fetchIstiodResourceThresholds(),
             this.fetchMetrics()
           ])
           .then(_ => {
@@ -766,20 +760,6 @@ export class TargetPanelNamespace extends React.Component<TargetPanelNamespacePr
       .catch(err => this.handleApiError('Could not fetch namespace health', err));
   }
 
-  private fetchIstiodResourceThresholds(): Promise<void> {
-    if (!this.isControlPlane()) {
-      return Promise.resolve();
-    }
-
-    return API.getIstiodResourceThresholds()
-      .then(response => {
-        this.setState({ istiodResourceThresholds: response.data });
-      })
-      .catch(error => {
-        AlertUtils.addError('Error fetching Istiod resource thresholds.', error, 'default', MessageType.ERROR);
-      });
-  }
-
   private fetchMetrics(): Promise<void> {
     const rateParams = computePrometheusRateParams(this.props.duration, 10);
     const options: IstioMetricsOptions = {
@@ -799,25 +779,10 @@ export class TargetPanelNamespace extends React.Component<TargetPanelNamespacePr
         const metrics: Metric[] = rs.data.request_count as Metric[];
         const errorMetrics: Metric[] = rs.data.request_error_count as Metric[];
 
-        if (this.isControlPlane()) {
-          const controlPlaneMetrics: ControlPlaneMetricsMap = {
-            istiod_proxy_time: rs.data.pilot_proxy_convergence_time,
-            istiod_container_cpu: rs.data.container_cpu_usage_seconds_total,
-            istiod_container_mem: rs.data.container_memory_working_set_bytes,
-            istiod_process_cpu: rs.data.process_cpu_seconds_total,
-            istiod_process_mem: rs.data.process_resident_memory_bytes
-          };
-          this.setState({
-            controlPlaneMetrics: controlPlaneMetrics,
-            errorMetrics: errorMetrics,
-            metrics: metrics
-          });
-        } else {
-          this.setState({
-            errorMetrics: errorMetrics,
-            metrics: metrics
-          });
-        }
+        this.setState({
+          errorMetrics: errorMetrics,
+          metrics: metrics
+        });
       })
       .catch(err => this.handleApiError('Could not fetch namespace metrics', err));
   }
@@ -836,16 +801,12 @@ export class TargetPanelNamespace extends React.Component<TargetPanelNamespacePr
       const namespace = this.state.targetNamespace!;
 
       return (
-        <OverviewCardSparklineCharts
+        <OverviewCardDataPlaneNamespace
           key={namespace}
-          name={namespace}
-          annotations={this.state.nsInfo!.annotations}
           duration={this.props.duration}
           direction={direction}
           metrics={this.state.metrics}
           errorMetrics={this.state.errorMetrics}
-          controlPlaneMetrics={this.state.controlPlaneMetrics}
-          istiodResourceThresholds={this.state.istiodResourceThresholds}
         />
       );
     }
diff --git a/frontend/src/pages/Overview/OverviewCardSparklineCharts.tsx b/frontend/src/pages/Overview/OverviewCardSparklineCharts.tsx
deleted file mode 100644
index 83b3db43d..000000000
--- a/frontend/src/pages/Overview/OverviewCardSparklineCharts.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import * as React from 'react';
-import { DirectionType } from './OverviewToolbar';
-import { serverConfig } from '../../config';
-import { ControlPlaneMetricsMap, Metric } from '../../types/Metrics';
-import { DurationInSeconds } from '../../types/Common';
-import { OverviewCardDataPlaneNamespace } from './OverviewCardDataPlaneNamespace';
-import { isRemoteCluster, OverviewCardControlPlaneNamespace } from './OverviewCardControlPlaneNamespace';
-import { IstiodResourceThresholds } from 'types/IstioStatus';
-import { connect } from 'react-redux';
-import { KialiAppState } from 'store/Store';
-
-type ReduxProps = {
-  istioAPIEnabled: boolean;
-};
-
-type Props = ReduxProps & {
-  annotations?: { [key: string]: string };
-  controlPlaneMetrics?: ControlPlaneMetricsMap;
-  direction: DirectionType;
-  duration: DurationInSeconds;
-  errorMetrics?: Metric[];
-  istiodResourceThresholds?: IstiodResourceThresholds;
-  metrics?: Metric[];
-  name: string;
-};
-
-const OverviewCardSparklineChartsComponent: React.FC<Props> = (props: Props) => {
-  return (
-    <>
-      {(props.name !== serverConfig.istioNamespace || !props.controlPlaneMetrics) && (
-        <OverviewCardDataPlaneNamespace
-          metrics={props.metrics}
-          errorMetrics={props.errorMetrics}
-          duration={props.duration}
-          direction={props.direction}
-        />
-      )}
-
-      {props.name === serverConfig.istioNamespace &&
-        props.controlPlaneMetrics &&
-        props.istioAPIEnabled &&
-        !isRemoteCluster(props.annotations) && (
-          <OverviewCardControlPlaneNamespace
-            pilotLatency={props.controlPlaneMetrics?.istiod_proxy_time}
-            istiodContainerMemory={props.controlPlaneMetrics?.istiod_container_mem}
-            istiodContainerCpu={props.controlPlaneMetrics?.istiod_container_cpu}
-            istiodProcessMemory={props.controlPlaneMetrics?.istiod_process_mem}
-            istiodProcessCpu={props.controlPlaneMetrics?.istiod_process_cpu}
-            istiodResourceThresholds={props.istiodResourceThresholds}
-          />
-        )}
-    </>
-  );
-};
-
-const mapStateToProps = (state: KialiAppState): ReduxProps => ({
-  istioAPIEnabled: state.statusState.istioEnvironment.istioAPIEnabled
-});
-
-export const OverviewCardSparklineCharts = connect(mapStateToProps)(OverviewCardSparklineChartsComponent);
diff --git a/frontend/src/pages/Overview/OverviewPage.tsx b/frontend/src/pages/Overview/OverviewPage.tsx
index 051180720..544967417 100644
--- a/frontend/src/pages/Overview/OverviewPage.tsx
+++ b/frontend/src/pages/Overview/OverviewPage.tsx
@@ -35,7 +35,7 @@ import { OverviewToolbar, OverviewDisplayMode, OverviewType, DirectionType } fro
 import { NamespaceInfo, NamespaceStatus } from '../../types/NamespaceInfo';
 import { NamespaceMTLSStatus } from '../../components/MTls/NamespaceMTLSStatus';
 import { RenderComponentScroll } from '../../components/Nav/Page';
-import { OverviewCardSparklineCharts } from './OverviewCardSparklineCharts';
+import { OverviewCardDataPlaneNamespace } from './OverviewCardDataPlaneNamespace';
 import { OverviewTrafficPolicies } from './OverviewTrafficPolicies';
 import { IstioMetricsOptions } from '../../types/MetricsOptions';
 import { computePrometheusRateParams } from '../../services/Prometheus';
@@ -61,14 +61,13 @@ import { OverviewNamespaceAction, OverviewNamespaceActions } from './OverviewNam
 import { history, HistoryManager, URLParam } from '../../app/History';
 import * as AlertUtils from '../../utils/AlertUtils';
 import { MessageType } from '../../types/MessageCenter';
-import { CanaryUpgradeStatus, OutboundTrafficPolicy, ValidationStatus } from '../../types/IstioObjects';
+import { CanaryUpgradeStatus, ValidationStatus } from '../../types/IstioObjects';
 import { GrafanaInfo, ISTIO_DASHBOARDS } from '../../types/GrafanaInfo';
 import { ExternalLink } from '../../types/Dashboards';
 import { isParentKiosk, kioskOverviewAction } from '../../components/Kiosk/KioskActions';
 import { ValidationSummaryLink } from '../../components/Link/ValidationSummaryLink';
 import { ControlPlaneBadge } from './ControlPlaneBadge';
 import { OverviewStatus } from './OverviewStatus';
-import { IstiodResourceThresholds } from 'types/IstioStatus';
 import { ControlPlaneVersionBadge } from './ControlPlaneVersionBadge';
 import { AmbientBadge } from '../../components/Ambient/AmbientBadge';
 import { PFBadge, PFBadges } from 'components/Pf/PfBadges';
@@ -136,12 +135,10 @@ type State = {
   direction: DirectionType;
   displayMode: OverviewDisplayMode;
   grafanaLinks: ExternalLink[];
-  istiodResourceThresholds: IstiodResourceThresholds;
   kind: string;
   namespaces: NamespaceInfo[];
   nsTarget: string;
   opTarget: string;
-  outboundPolicyMode: OutboundTrafficPolicy;
   showTrafficPoliciesModal: boolean;
   type: OverviewType;
 };
@@ -181,8 +178,6 @@ export class OverviewPageComponent extends React.Component<OverviewProps, State>
       clusterTarget: '',
       opTarget: '',
       grafanaLinks: [],
-      istiodResourceThresholds: { memory: 0, cpu: 0 },
-      outboundPolicyMode: {},
       canaryUpgradeStatus: undefined
     };
   }
@@ -275,9 +270,6 @@ export class OverviewPageComponent extends React.Component<OverviewProps, State>
           () => {
             this.fetchHealth(isAscending, sortField, type);
             this.fetchTLS(isAscending, sortField);
-            this.fetchOutboundTrafficPolicyMode();
-            this.fetchCanariesStatus();
-            this.fetchIstiodResourceThresholds();
             this.fetchValidations(isAscending, sortField);
 
             if (displayMode !== OverviewDisplayMode.COMPACT) {
@@ -645,43 +637,6 @@ export class OverviewPageComponent extends React.Component<OverviewProps, State>
       });
   };
 
-  fetchOutboundTrafficPolicyMode = (): void => {
-    API.getOutboundTrafficPolicyMode()
-      .then(response => {
-        this.setState({ outboundPolicyMode: { mode: response.data.mode } });
-      })
-      .catch(error => {
-        AlertUtils.addError('Error fetching Mesh OutboundTrafficPolicy.Mode.', error, 'default', MessageType.ERROR);
-      });
-  };
-
-  fetchCanariesStatus = (): void => {
-    API.getCanaryUpgradeStatus()
-      .then(response => {
-        this.setState({
-          canaryUpgradeStatus: {
-            currentVersion: response.data.currentVersion,
-            upgradeVersion: response.data.upgradeVersion,
-            migratedNamespaces: response.data.migratedNamespaces,
-            pendingNamespaces: response.data.pendingNamespaces
-          }
-        });
-      })
-      .catch(error => {
-        AlertUtils.addError('Error fetching canary upgrade status.', error, 'default', MessageType.ERROR);
-      });
-  };
-
-  fetchIstiodResourceThresholds = (): void => {
-    API.getIstiodResourceThresholds()
-      .then(response => {
-        this.setState({ istiodResourceThresholds: response.data });
-      })
-      .catch(error => {
-        AlertUtils.addError('Error fetching Istiod resource thresholds.', error, 'default', MessageType.ERROR);
-      });
-  };
-
   handleApiError = (message: string, error: ApiError): void => {
     FilterHelper.handleError(`${message}: ${API.getErrorString(error)}`);
   };
@@ -1231,15 +1186,12 @@ export class OverviewPageComponent extends React.Component<OverviewProps, State>
     return (
       <div style={{ height: '130px' }}>
         {ns.status ? (
-          <OverviewCardSparklineCharts
+          <OverviewCardDataPlaneNamespace
             key={ns.name}
-            name={ns.name}
-            annotations={ns.annotations}
             duration={FilterHelper.currentDuration()}
             direction={this.state.direction}
             metrics={ns.metrics}
             errorMetrics={ns.errorMetrics}
-            istiodResourceThresholds={this.state.istiodResourceThresholds}
           />
         ) : (
           <div style={{ padding: '1.5rem 0', textAlign: 'center' }}>Namespace metrics are not available</div>
diff --git a/go.mod b/go.mod
index 75ccda385..3bb83c63a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/kiali/kiali
 
-go 1.22.1
+go 1.22.4
 
 require (
 	github.com/NYTimes/gziphandler v1.1.1
@@ -83,7 +83,6 @@ require (
 	golang.org/x/term v0.19.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/time v0.5.0 // indirect
-	google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
 	gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
diff --git a/go.sum b/go.sum
index 70365c73a..b16458cfd 100644
--- a/go.sum
+++ b/go.sum
@@ -202,14 +202,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
-google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
-google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
-google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
 google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4=
 google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
 google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
@@ -228,14 +222,8 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-istio.io/api v1.21.1-0.20240404235206-c5bbf8925ab4 h1:0UHHbkxkYvDR/O0bTzT5xNLLoj+9YZv8qcAFmcPVisg=
-istio.io/api v1.21.1-0.20240404235206-c5bbf8925ab4/go.mod h1:TFCMUCAHRjxBv1CsIsFCsYHPHi4axVI4vdIzVr8eFjY=
-istio.io/api v1.22.0-alpha.1.0.20240422184433-628d0981624d h1:F6NEAUXhUVS89wOBZd7qWAzlk3dD/K5xNUlDO3yVVlc=
-istio.io/api v1.22.0-alpha.1.0.20240422184433-628d0981624d/go.mod h1:S3l8LWqNYS9yT+d4bH+jqzH2lMencPkW7SKM1Cu9EyM=
 istio.io/api v1.22.0 h1:CdMUHgN/OfQK9ojj6lCjxlJSuUe0vD0ZAvoCcoBfn20=
 istio.io/api v1.22.0/go.mod h1:S3l8LWqNYS9yT+d4bH+jqzH2lMencPkW7SKM1Cu9EyM=
-istio.io/client-go v1.21.1 h1:gAZCeG4pV2o2L6WaD/MLruNB+tBxa+Y21BuRJmFYlAI=
-istio.io/client-go v1.21.1/go.mod h1:mqwsapfu4b1FG47puY9H8y4+ga1+d+hxfdosNQ1HclY=
 istio.io/client-go v1.22.0 h1:TQ+Y7hqZVQHvaJXF99Q1jBqnVG7gYAHR9IvCK2nlwfE=
 istio.io/client-go v1.22.0/go.mod h1:1lAPr0DOVBbnRQqLAQKxWbEaxFk6b1CJTm+ypnP7sMo=
 k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA=
diff --git a/hack/istio/skupper/demo-emit-metrics-deployment/Makefile b/hack/istio/skupper/demo-emit-metrics-deployment/Makefile
index 1d101f3b2..e9cf0186b 100644
--- a/hack/istio/skupper/demo-emit-metrics-deployment/Makefile
+++ b/hack/istio/skupper/demo-emit-metrics-deployment/Makefile
@@ -31,4 +31,4 @@ undeploy:
 
 # After changing the k8s.yaml (e.g. the metrics in the ConfigMap), this will redeploy everything and restart the pod.
 redeploy: deploy
-	kubectl rollout restart deployment -n ${DEMO_NAMESPACE}
+	kubectl rollout restart deployment -l app=demo-extension-metrics-server -n ${DEMO_NAMESPACE}
diff --git a/hack/istio/skupper/demo-emit-metrics-deployment/k8s.yaml b/hack/istio/skupper/demo-emit-metrics-deployment/k8s.yaml
index da281b4a3..03d431f48 100644
--- a/hack/istio/skupper/demo-emit-metrics-deployment/k8s.yaml
+++ b/hack/istio/skupper/demo-emit-metrics-deployment/k8s.yaml
@@ -77,12 +77,16 @@ data:
     # HELP kiali_ext_tcp_connections_closed_total total tcp connections closed
     # TYPE kiali_ext_tcp_connections_closed_total counter
     kiali_ext_tcp_connections_closed_total{dest_cluster="1389515b-65e6-4218-8d3b-3da179b53f87",dest_name="mongodb-v1",dest_namespace="mongons",extension="skupper",flags="",reporter="combined",reporter_id="skupper-kiali-bridge-89b57f8cd-j66kk",security="plain",source_cluster="e1fc0634-c934-480e-99c9-bb6a0c0eac3b",source_is_root="true",source_name="mongodb-v1",source_namespace="mongoskupperns"} $(date +%s | cut -c6-)
+    kiali_ext_tcp_connections_closed_total{dest_cluster="1389515b-65e6-4218-8d3b-3da179b53f87",dest_name="mysqldb-v1",dest_namespace="mysqlns",extension="skupper",flags="",reporter="combined",reporter_id="skupper-kiali-bridge-89b57f8cd-j66kk",security="plain",source_cluster="e1fc0634-c934-480e-99c9-bb6a0c0eac3b",source_is_root="true",source_name="mysqldb.test",source_namespace="mysqlskupperns"} $(date +%s | cut -c6-)
     # HELP kiali_ext_tcp_connections_opened_total total tcp connections opened
     # TYPE kiali_ext_tcp_connections_opened_total counter
     kiali_ext_tcp_connections_opened_total{dest_cluster="1389515b-65e6-4218-8d3b-3da179b53f87",dest_name="mongodb-v1",dest_namespace="mongons",extension="skupper",flags="",reporter="combined",reporter_id="skupper-kiali-bridge-89b57f8cd-j66kk",security="plain",source_cluster="e1fc0634-c934-480e-99c9-bb6a0c0eac3b",source_is_root="true",source_name="mongodb-v1",source_namespace="mongoskupperns"} $(date +%s | cut -c6-)
+    kiali_ext_tcp_connections_opened_total{dest_cluster="1389515b-65e6-4218-8d3b-3da179b53f87",dest_name="mysqldb-v1",dest_namespace="mysqlns",extension="skupper",flags="",reporter="combined",reporter_id="skupper-kiali-bridge-89b57f8cd-j66kk",security="plain",source_cluster="e1fc0634-c934-480e-99c9-bb6a0c0eac3b",source_is_root="true",source_name="mysqldb.test",source_namespace="mysqlskupperns"} $(date +%s | cut -c6-)
     # HELP kiali_ext_tcp_received_total total bytes received in a TCP connection
     # TYPE kiali_ext_tcp_received_total counter
     kiali_ext_tcp_received_total{dest_cluster="1389515b-65e6-4218-8d3b-3da179b53f87",dest_name="mongodb-v1",dest_namespace="mongons",extension="skupper",flags="",reporter="combined",reporter_id="skupper-kiali-bridge-89b57f8cd-j66kk",security="plain",source_cluster="e1fc0634-c934-480e-99c9-bb6a0c0eac3b",source_is_root="true",source_name="mongodb-v1",source_namespace="mongoskupperns"} $(date +%s | cut -c6-)
+    kiali_ext_tcp_received_total{dest_cluster="1389515b-65e6-4218-8d3b-3da179b53f87",dest_name="mysqldb-v1",dest_namespace="mysqlns",extension="skupper",flags="",reporter="combined",reporter_id="skupper-kiali-bridge-89b57f8cd-j66kk",security="plain",source_cluster="e1fc0634-c934-480e-99c9-bb6a0c0eac3b",source_is_root="true",source_name="mysqldb.test",source_namespace="mysqlskupperns"} $(date +%s | cut -c6-)
     # HELP kiali_ext_tcp_sent_total total bytes sent in a TCP connection
     # TYPE kiali_ext_tcp_sent_total counter
     kiali_ext_tcp_sent_total{dest_cluster="1389515b-65e6-4218-8d3b-3da179b53f87",dest_name="mongodb-v1",dest_namespace="mongons",extension="skupper",flags="",reporter="combined",reporter_id="skupper-kiali-bridge-89b57f8cd-j66kk",security="plain",source_cluster="e1fc0634-c934-480e-99c9-bb6a0c0eac3b",source_is_root="true",source_name="mongodb-v1",source_namespace="mongoskupperns"} $(date +%s | cut -c6-)
+    kiali_ext_tcp_sent_total{dest_cluster="1389515b-65e6-4218-8d3b-3da179b53f87",dest_name="mysqldb-v1",dest_namespace="mysqlns",extension="skupper",flags="",reporter="combined",reporter_id="skupper-kiali-bridge-89b57f8cd-j66kk",security="plain",source_cluster="e1fc0634-c934-480e-99c9-bb6a0c0eac3b",source_is_root="true",source_name="mysqldb.test",source_namespace="mysqlskupperns"} $(date +%s | cut -c6-)
diff --git a/hack/istio/skupper/install-skupper-demo.sh b/hack/istio/skupper/install-skupper-demo.sh
index 8185dfefa..681020e69 100755
--- a/hack/istio/skupper/install-skupper-demo.sh
+++ b/hack/istio/skupper/install-skupper-demo.sh
@@ -65,6 +65,7 @@ KIALI_VERSION="dev"
 MONGONS="mongons"
 MONGOSKUPPERNS="mongoskupperns"
 MYSQLNS="mysqlns"
+MYSQLSKUPPERNS="mysqlskupperns"
 OPENSHIFT1_API=""
 OPENSHIFT1_USERNAME="kiali"
 OPENSHIFT1_PASSWORD="kiali"
@@ -72,7 +73,9 @@ OPENSHIFT2_API=""
 OPENSHIFT2_USERNAME="kiali"
 OPENSHIFT2_PASSWORD="kiali"
 SKUPPER_EXE="${OUTPUT_DIR}/skupper"
-SKUPPER_TOKEN_FILE="${OUTPUT_DIR}/skupper.token"
+SKUPPER_TOKEN_FILE_MONGO="${OUTPUT_DIR}/skupper-mongo.token"
+SKUPPER_TOKEN_FILE_MYSQL="${OUTPUT_DIR}/skupper-mysql.token"
+SKUPPER_TOKEN_FILE_EW="${OUTPUT_DIR}/skupper-eastwest.token"
 VALIDATE_ENVIRONMENT="true"
 
 # Process command line args
@@ -85,10 +88,14 @@ while [ $# -gt 0 ]; do
     delete)                   _CMD="delete"                        ;shift ;;
     iprom)                    _CMD="iprom"                         ;shift ;;
     kui)                      _CMD="kui"                           ;shift ;;
-    smetrics)                 _CMD="smetrics"                      ;shift ;;
-    sprom)                    _CMD="sprom"                         ;shift ;;
+    bui)                      _CMD="bui"                           ;shift ;;
+    smetricsmongo)            _CMD="smetricsmongo"                 ;shift ;;
+    smetricsmysql)            _CMD="smetricsmysql"                 ;shift ;;
+    sprommongo)               _CMD="sprommongo"                    ;shift ;;
+    sprommysql)               _CMD="sprommysql"                    ;shift ;;
     sstatus)                  _CMD="sstatus"                       ;shift ;;
-    sui)                      _CMD="sui"                           ;shift ;;
+    suimongo)                 _CMD="suimongo"                      ;shift ;;
+    suimysql)                 _CMD="suimysql"                      ;shift ;;
     -c|--client)              CLIENT_EXE="$2"                ;shift;shift ;;
     -ct|--cluster-type)       CLUSTER_TYPE="$2"              ;shift;shift ;;
     -ewd|--east-west-demo)    INSTALL_EAST_WEST_DEMO="$2"    ;shift;shift ;;
@@ -144,10 +151,14 @@ Valid commands:
   delete: Uninstalls the demo by shutting down the two minikube clusters or removing resources from OpenShift
   iprom: Open a browser window to the Istio Prometheus UI
   kui: Open a browser window to the Kiali UI
-  smetrics: Dumps the live metrics from the Skupper metrics endpoint
-  sprom: Open a browser window to the Skupper Prometheus UI
+  bui: Open a browser window to the Bookinfo UI
+  smetricsmongo: Dumps the live metrics from the Mongo Skupper metrics endpoint
+  smetricsmysql: Dumps the live metrics from the MySQL Skupper metrics endpoint
+  sprommongo: Open a browser window to the Skupper Prometheus UI with the Mongo metrics
+  sprommysql: Open a browser window to the Skupper Prometheus UI with the MySQL metrics
   sstatus: Prints the Skupper status for both ends of the pipe
-  sui: Open a browser window to the Skupper UI
+  suimongo: Open a browser window to the Mongo Skupper UI
+  suimysql: Open a browser window to the MySQL Skupper UI
 HELPMSG
       exit 1
       ;;
@@ -213,24 +224,24 @@ minikube_install_basic_demo() {
     if ! helm repo update kiali 2> /dev/null; then
       helm repo add kiali https://kiali.org/helm-charts
     fi
-    helm --kube-context ${CLUSTER1_ISTIO} upgrade --install --namespace istio-system --version ${KIALI_VERSION} --set auth.strategy=anonymous kiali-server kiali/kiali-server
+    helm --kube-context ${CLUSTER1_ISTIO} upgrade --install --namespace istio-system --version ${KIALI_VERSION} --set auth.strategy=anonymous --set deployment.service_type=LoadBalancer kiali-server kiali/kiali-server
   fi
 
   infomsg "Exposing Prometheus UI via LoadBalancer ..."
   ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n istio-system patch svc prometheus --type=merge --patch '{"spec":{"type":"LoadBalancer"}}'
 
+  infomsg "Exposing Bookinfo UI via LoadBalancer ..."
+  ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n bookinfo patch svc productpage --type=merge --patch '{"spec":{"type":"LoadBalancer"}}'
+
   infomsg "Installing MySQL in [${CLUSTER2_DB}] cluster"
   ${CLIENT_EXE} --context ${CLUSTER2_DB} create namespace ${MYSQLNS}
   ${CLIENT_EXE} --context ${CLUSTER2_DB} -n ${MYSQLNS} apply -f ${OUTPUT_DIR}/istio-*/samples/bookinfo/platform/kube/bookinfo-mysql.yaml
-  ${CLIENT_EXE} --context ${CLUSTER2_DB} -n ${MYSQLNS} patch svc mysqldb --type=merge --patch '{"spec":{"type":"LoadBalancer"}}'
-  MYSQL_IP="$(${CLIENT_EXE} --context ${CLUSTER2_DB} -n ${MYSQLNS} get svc mysqldb -o jsonpath='{.status.loadBalancer.ingress[0].ip}')"
-  infomsg "MySQL available at IP: ${MYSQL_IP}"
 
   infomsg "Installing Mongo in [${CLUSTER2_DB}] cluster"
   ${CLIENT_EXE} --context ${CLUSTER2_DB} create namespace ${MONGONS}
   ${CLIENT_EXE} --context ${CLUSTER2_DB} -n ${MONGONS} apply -f ${OUTPUT_DIR}/istio-*/samples/bookinfo/platform/kube/bookinfo-db.yaml
 
-  infomsg "Creating Istio ServiceEntry resource for MySQL access"
+  infomsg "Creating Istio ServiceEntry resource for MySQL access - this will not be fully configured yet"
   cat <<EOM | ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n bookinfo apply -f -
 apiVersion: networking.istio.io/v1
 kind: ServiceEntry
@@ -238,9 +249,9 @@ metadata:
   name: mysqldb.test
 spec:
   addresses:
-  - "${MYSQL_IP}"
+  - "127.0.0.1"
   endpoints:
-  - address: "${MYSQL_IP}"
+  - address: "127.0.0.1"
   hosts:
   - mysqldb.test
   location: MESH_EXTERNAL
@@ -251,37 +262,52 @@ spec:
     protocol: TCP
 EOM
 
-  infomsg "Creating ratings-v2-mysql app and pointing it to the MySQL server in the [${CLUSTER2_DB}] cluster"
+  infomsg "Creating ratings-v2-mysql app - this will use MySQL but will not be correctly configured yet"
   ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n bookinfo apply -f ${OUTPUT_DIR}/istio-*/samples/bookinfo/platform/kube/bookinfo-ratings-v2-mysql.yaml
-  ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n bookinfo set env deployment/ratings-v2-mysql MYSQL_DB_HOST="${MYSQL_IP}"
 
   infomsg "Creating ratings-v2 app - this will use Mongo but will not be correctly configured yet"
   ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n bookinfo apply -f ${OUTPUT_DIR}/istio-*/samples/bookinfo/platform/kube/bookinfo-ratings-v2.yaml
 }
 
-# minikube_install_skupper will create the Skupper pipe so Bookinfo access talk to the Mongo database on the db cluster.
+# minikube_install_skupper will create the Skupper pipe so Bookinfo access talk to the databases on the db cluster.
 # This function should only be executed after the minikube_install_basic_demo function successfully completes.
 minikube_install_skupper() {
+  # create a link to Mongo
   infomsg "Creating the Skupper link so Bookinfo can access Mongo"
-  rm -f "${SKUPPER_TOKEN_FILE}"
+  rm -f "${SKUPPER_TOKEN_FILE_MONGO}"
   ${SKUPPER_EXE} --context ${CLUSTER2_DB} -n ${MONGONS} init
   ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} create namespace ${MONGOSKUPPERNS}
   ${SKUPPER_EXE} --context ${CLUSTER1_ISTIO} -n ${MONGOSKUPPERNS} init --enable-console --enable-flow-collector
-  ${SKUPPER_EXE} --context ${CLUSTER1_ISTIO} -n ${MONGOSKUPPERNS} token create "${SKUPPER_TOKEN_FILE}"
-  ${SKUPPER_EXE} --context ${CLUSTER2_DB} -n ${MONGONS} link create "${SKUPPER_TOKEN_FILE}"
+  ${SKUPPER_EXE} --context ${CLUSTER1_ISTIO} -n ${MONGOSKUPPERNS} token create "${SKUPPER_TOKEN_FILE_MONGO}"
+  ${SKUPPER_EXE} --context ${CLUSTER2_DB} -n ${MONGONS} link create "${SKUPPER_TOKEN_FILE_MONGO}"
   ${SKUPPER_EXE} --context ${CLUSTER2_DB} -n ${MONGONS} expose deployment/mongodb-v1 --port 27017
-
   infomsg "Wait for the mongodb-v1 service to be created by Skupper in the [${CLUSTER1_ISTIO}] cluster"
   while ! ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n ${MONGOSKUPPERNS} get svc mongodb-v1 &> /dev/null ; do echo -n '.'; sleep 1; done; echo
-
   SKUPPER_MONGO_IP="$(${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n ${MONGOSKUPPERNS} get svc mongodb-v1 -o jsonpath='{.spec.clusterIPs[0]}')"
   infomsg "Mongo IP over the Skupper link: ${SKUPPER_MONGO_IP}"
-
   infomsg "Configuring Bookinfo ratings-v2 to talk to Mongo over the Skupper link"
   ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n bookinfo set env deployment/ratings-v2 MONGO_DB_URL="mongodb://${SKUPPER_MONGO_IP}:27017/test"
-
-  infomsg "Exposing Skupper Prometheus so its UI can be accessed"
+  infomsg "Exposing Mongo Skupper Prometheus so its UI can be accessed"
   ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n ${MONGOSKUPPERNS} patch svc skupper-prometheus --type=merge --patch '{"spec":{"type":"LoadBalancer"}}'
+
+  # create a link to MySQL
+  infomsg "Creating the Skupper link so Bookinfo can access MySQL"
+  rm -f "${SKUPPER_TOKEN_FILE_MYSQL}"
+  ${SKUPPER_EXE} --context ${CLUSTER2_DB} -n ${MYSQLNS} init
+  ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} create namespace ${MYSQLSKUPPERNS}
+  ${SKUPPER_EXE} --context ${CLUSTER1_ISTIO} -n ${MYSQLSKUPPERNS} init --enable-console --enable-flow-collector
+  ${SKUPPER_EXE} --context ${CLUSTER1_ISTIO} -n ${MYSQLSKUPPERNS} token create "${SKUPPER_TOKEN_FILE_MYSQL}"
+  ${SKUPPER_EXE} --context ${CLUSTER2_DB} -n ${MYSQLNS} link create "${SKUPPER_TOKEN_FILE_MYSQL}"
+  ${SKUPPER_EXE} --context ${CLUSTER2_DB} -n ${MYSQLNS} expose deployment/mysqldb-v1 --port 3306
+  infomsg "Wait for the mysqldb-v1 service to be created by Skupper in the [${CLUSTER1_ISTIO}] cluster"
+  while ! ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n ${MYSQLSKUPPERNS} get svc mysqldb-v1 &> /dev/null ; do echo -n '.'; sleep 1; done; echo
+  SKUPPER_MYSQL_IP="$(${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n ${MYSQLSKUPPERNS} get svc mysqldb-v1 -o jsonpath='{.spec.clusterIPs[0]}')"
+  infomsg "MySQL IP over the Skupper link: ${SKUPPER_MYSQL_IP}"
+  infomsg "Configuring Bookinfo ratings-v2-mysql to talk to MySQL over the Skupper link via the ServiceEntry"
+  ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n bookinfo set env deployment/ratings-v2-mysql MYSQL_DB_HOST="${SKUPPER_MYSQL_IP}"
+  ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n bookinfo patch serviceentry mysqldb.test --type='json' -p='[{"op": "replace", "path": "/spec/addresses/0", "value": "'${SKUPPER_MYSQL_IP}'"}, {"op": "replace", "path": "/spec/endpoints/0/address", "value": "'${SKUPPER_MYSQL_IP}'"}]'
+  infomsg "Exposing MySQL Skupper Prometheus so its UI can be accessed"
+  ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n ${MYSQLSKUPPERNS} patch svc skupper-prometheus --type=merge --patch '{"spec":{"type":"LoadBalancer"}}'
 }
 
 # openshift_install_basic_demo will install the two databases, Istio, Kiali, and Bookinfo demo in the two existing OpenShift clusters.
@@ -317,7 +343,6 @@ openshift_install_basic_demo() {
     helm upgrade --install --namespace istio-system --version ${KIALI_VERSION} --set auth.strategy=anonymous kiali-server kiali/kiali-server
   fi
 
-
   infomsg "Exposing Prometheus UI via Route ..."
   ${CLIENT_EXE} -n istio-system expose svc prometheus
 
@@ -327,12 +352,6 @@ openshift_install_basic_demo() {
   infomsg "Installing MySQL in [${CLUSTER2_DB}] cluster"
   ${CLIENT_EXE} create namespace ${MYSQLNS}
   ${CLIENT_EXE} -n ${MYSQLNS} apply -f ${OUTPUT_DIR}/istio-*/samples/bookinfo/platform/kube/bookinfo-mysql.yaml
-  ${CLIENT_EXE} -n ${MYSQLNS} patch svc mysqldb --type=merge --patch '{"spec":{"type":"NodePort"}}'
-  MYSQL_NODENAME="$(${CLIENT_EXE} -n ${MYSQLNS} get pod -l app=mysqldb -o jsonpath='{..spec.nodeName}')"
-  NODE_HOSTNAME="$(${CLIENT_EXE} get node ${MYSQL_NODENAME} -o jsonpath='{..metadata.labels.kubernetes\.io/hostname}')"
-  MYSQL_IP="$(getent hosts ${NODE_HOSTNAME} | awk '{print $1}')"
-  MYSQL_PORT="$(${CLIENT_EXE} -n ${MYSQLNS} get svc mysqldb -o jsonpath='{.spec.ports[0].nodePort}')"
-  infomsg "MySQL available at nodeIP:nodePort: ${MYSQL_IP}:${MYSQL_PORT}"
 
   infomsg "Installing Mongo in [${CLUSTER2_DB}] cluster"
   ${CLIENT_EXE} create namespace ${MONGONS}
@@ -341,7 +360,7 @@ openshift_install_basic_demo() {
   # LOGIN TO CLUSTER 1
   openshift_login ${CLUSTER1_ISTIO}
 
-  infomsg "Creating Istio ServiceEntry resource for MySQL access"
+  infomsg "Creating Istio ServiceEntry resource for MySQL access - this will not be fully configured yet"
   cat <<EOM | ${CLIENT_EXE} -n bookinfo apply -f -
 apiVersion: networking.istio.io/v1
 kind: ServiceEntry
@@ -349,65 +368,81 @@ metadata:
   name: mysqldb.test
 spec:
   addresses:
-  - "${MYSQL_IP}"
+  - "127.0.0.1"
   endpoints:
-  - address: "${MYSQL_IP}"
+  - address: "127.0.0.1"
   hosts:
   - mysqldb.test
   location: MESH_EXTERNAL
   resolution: STATIC
   ports:
-  - number: ${MYSQL_PORT}
+  - number: 3306
     name: tcp
     protocol: TCP
 EOM
 
-  infomsg "Creating ratings-v2-mysql app and pointing it to the MySQL server in the [${CLUSTER2_DB}] cluster"
+  infomsg "Creating ratings-v2-mysql app - this will use MySQL but will not be correctly configured yet"
   ${CLIENT_EXE} -n bookinfo apply -f ${OUTPUT_DIR}/istio-*/samples/bookinfo/platform/kube/bookinfo-ratings-v2-mysql.yaml
-  ${CLIENT_EXE} -n bookinfo set env deployment/ratings-v2-mysql MYSQL_DB_HOST="${MYSQL_IP}" MYSQL_DB_PORT="${MYSQL_PORT}"
 
   infomsg "Creating ratings-v2 app - this will use Mongo but will not be correctly configured yet"
   ${CLIENT_EXE} -n bookinfo apply -f ${OUTPUT_DIR}/istio-*/samples/bookinfo/platform/kube/bookinfo-ratings-v2.yaml
 }
 
-# openshift_install_skupper will create the Skupper pipe so Bookinfo access talk to the Mongo database on the db cluster.
+# openshift_install_skupper will create the Skupper pipe so Bookinfo access talk to the databases on the db cluster.
 # This function should only be executed after the openshift_install_basic_demo function successfully completes.
 openshift_install_skupper() {
+  # create a link to Mongo
   infomsg "Creating the Skupper link so Bookinfo can access Mongo"
-  rm -f "${SKUPPER_TOKEN_FILE}"
-
+  rm -f "${SKUPPER_TOKEN_FILE_MONGO}"
   # LOGIN TO CLUSTER 2
   openshift_login ${CLUSTER2_DB}
-
   ${SKUPPER_EXE} -n ${MONGONS} init
-
   # LOGIN TO CLUSTER 1
   openshift_login ${CLUSTER1_ISTIO}
-
   ${CLIENT_EXE} create namespace ${MONGOSKUPPERNS}
   ${SKUPPER_EXE} -n ${MONGOSKUPPERNS} init --enable-console --enable-flow-collector
-  ${SKUPPER_EXE} -n ${MONGOSKUPPERNS} token create "${SKUPPER_TOKEN_FILE}"
-
+  ${SKUPPER_EXE} -n ${MONGOSKUPPERNS} token create "${SKUPPER_TOKEN_FILE_MONGO}"
   # LOGIN TO CLUSTER 2
   openshift_login ${CLUSTER2_DB}
-
-  ${SKUPPER_EXE} -n ${MONGONS} link create "${SKUPPER_TOKEN_FILE}"
+  ${SKUPPER_EXE} -n ${MONGONS} link create "${SKUPPER_TOKEN_FILE_MONGO}"
   ${SKUPPER_EXE} -n ${MONGONS} expose deployment/mongodb-v1 --port 27017
-
   # LOGIN TO CLUSTER 1
   openshift_login ${CLUSTER1_ISTIO}
-
   infomsg "Wait for the mongodb-v1 service to be created by Skupper in the [${CLUSTER1_ISTIO}] cluster"
   while ! ${CLIENT_EXE} -n ${MONGOSKUPPERNS} get svc mongodb-v1 &> /dev/null ; do echo -n '.'; sleep 1; done; echo
-
   SKUPPER_MONGO_IP="$(${CLIENT_EXE} -n ${MONGOSKUPPERNS} get svc mongodb-v1 -o jsonpath='{.spec.clusterIPs[0]}')"
   infomsg "Mongo IP over the Skupper link: ${SKUPPER_MONGO_IP}"
-
   infomsg "Configuring Bookinfo ratings-v2 to talk to Mongo over the Skupper link"
   ${CLIENT_EXE} -n bookinfo set env deployment/ratings-v2 MONGO_DB_URL="mongodb://${SKUPPER_MONGO_IP}:27017/test"
-
-  infomsg "Exposing Skupper Prometheus so its UI can be accessed"
+  infomsg "Exposing Mongo Skupper Prometheus so its UI can be accessed"
   ${CLIENT_EXE} -n ${MONGOSKUPPERNS} expose svc skupper-prometheus
+
+  # create a link to MySQL
+  infomsg "Creating the Skupper link so Bookinfo can access MySQL"
+  rm -f "${SKUPPER_TOKEN_FILE_MYSQL}"
+  # LOGIN TO CLUSTER 2
+  openshift_login ${CLUSTER2_DB}
+  ${SKUPPER_EXE} -n ${MYSQLNS} init
+  # LOGIN TO CLUSTER 1
+  openshift_login ${CLUSTER1_ISTIO}
+  ${CLIENT_EXE} create namespace ${MYSQLSKUPPERNS}
+  ${SKUPPER_EXE} -n ${MYSQLSKUPPERNS} init --enable-console --enable-flow-collector
+  ${SKUPPER_EXE} -n ${MYSQLSKUPPERNS} token create "${SKUPPER_TOKEN_FILE_MYSQL}"
+  # LOGIN TO CLUSTER 2
+  openshift_login ${CLUSTER2_DB}
+  ${SKUPPER_EXE} -n ${MYSQLNS} link create "${SKUPPER_TOKEN_FILE_MYSQL}"
+  ${SKUPPER_EXE} -n ${MYSQLNS} expose deployment/mysqldb-v1 --port 3306
+  # LOGIN TO CLUSTER 1
+  openshift_login ${CLUSTER1_ISTIO}
+  infomsg "Wait for the mysqldb-v1 service to be created by Skupper in the [${CLUSTER1_ISTIO}] cluster"
+  while ! ${CLIENT_EXE} -n ${MYSQLSKUPPERNS} get svc mysqldb-v1 &> /dev/null ; do echo -n '.'; sleep 1; done; echo
+  SKUPPER_MYSQL_IP="$(${CLIENT_EXE} -n ${MYSQLSKUPPERNS} get svc mysqldb-v1 -o jsonpath='{.spec.clusterIPs[0]}')"
+  infomsg "MySQL IP over the Skupper link: ${SKUPPER_MYSQL_IP}"
+  infomsg "Configuring Bookinfo ratings-v2-mysql to talk to MySQL over the Skupper link via the ServiceEntry"
+  ${CLIENT_EXE} -n bookinfo set env deployment/ratings-v2-mysql MYSQL_DB_HOST="${SKUPPER_MYSQL_IP}"
+  ${CLIENT_EXE} -n bookinfo patch serviceentry mysqldb.test --type='json' -p='[{"op": "replace", "path": "/spec/addresses/0", "value": "'${SKUPPER_MYSQL_IP}'"}, {"op": "replace", "path": "/spec/endpoints/0/address", "value": "'${SKUPPER_MYSQL_IP}'"}]'
+  infomsg "Exposing MySQL Skupper Prometheus so its UI can be accessed"
+  ${CLIENT_EXE} -n ${MYSQLSKUPPERNS} expose svc skupper-prometheus
 }
 
 minikube_install_east_west_demo() {
@@ -419,9 +454,9 @@ minikube_install_east_west_demo() {
   ${SKUPPER_EXE} --context ${CLUSTER1_ISTIO} -n ${NAMESPACE_WEST} init --enable-console --enable-flow-collector
   ${SKUPPER_EXE} --context ${CLUSTER2_DB} -n ${NAMESPACE_EAST} init
 
-  rm -r "${SKUPPER_TOKEN_FILE}"
-  ${SKUPPER_EXE} --context ${CLUSTER1_ISTIO} -n ${NAMESPACE_WEST} token create "${SKUPPER_TOKEN_FILE}"
-  ${SKUPPER_EXE} --context ${CLUSTER2_DB} -n ${NAMESPACE_EAST} link create "${SKUPPER_TOKEN_FILE}"
+  rm -r "${SKUPPER_TOKEN_FILE_EW}"
+  ${SKUPPER_EXE} --context ${CLUSTER1_ISTIO} -n ${NAMESPACE_WEST} token create "${SKUPPER_TOKEN_FILE_EW}"
+  ${SKUPPER_EXE} --context ${CLUSTER2_DB} -n ${NAMESPACE_EAST} link create "${SKUPPER_TOKEN_FILE_EW}"
 
   ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n ${NAMESPACE_WEST} create deployment frontend --image quay.io/skupper/hello-world-frontend
   ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n ${NAMESPACE_WEST} patch deployment frontend --type=json -p='[{"op": "add", "path": "/spec/template/metadata/labels/version", "value":"1"}]'
@@ -468,9 +503,9 @@ openshift_install_east_west_demo() {
   openshift_login ${CLUSTER1_ISTIO} && ${SKUPPER_EXE} -n ${NAMESPACE_WEST} init --enable-console --enable-flow-collector
   openshift_login ${CLUSTER2_DB} && ${SKUPPER_EXE} -n ${NAMESPACE_EAST} init
 
-  rm -r "${SKUPPER_TOKEN_FILE}"
-  openshift_login ${CLUSTER1_ISTIO} && ${SKUPPER_EXE} -n ${NAMESPACE_WEST} token create "${SKUPPER_TOKEN_FILE}"
-  openshift_login ${CLUSTER2_DB} && ${SKUPPER_EXE} -n ${NAMESPACE_EAST} link create "${SKUPPER_TOKEN_FILE}"
+  rm -r "${SKUPPER_TOKEN_FILE_EW}"
+  openshift_login ${CLUSTER1_ISTIO} && ${SKUPPER_EXE} -n ${NAMESPACE_WEST} token create "${SKUPPER_TOKEN_FILE_EW}"
+  openshift_login ${CLUSTER2_DB} && ${SKUPPER_EXE} -n ${NAMESPACE_EAST} link create "${SKUPPER_TOKEN_FILE_EW}"
 
   openshift_login ${CLUSTER1_ISTIO} && ${CLIENT_EXE} -n ${NAMESPACE_WEST} create deployment frontend --image quay.io/skupper/hello-world-frontend
   ${CLIENT_EXE} -n ${NAMESPACE_WEST} patch deployment frontend --type=json -p='[{"op": "add", "path": "/spec/template/metadata/labels/version", "value":"1"}]'
@@ -666,8 +701,10 @@ elif [ "$_CMD" == "delete" ]; then
       ${CLIENT_EXE} get namespace bookinfo 2>/dev/null && ${HACK_SCRIPTS_DIR}/istio/install-bookinfo-demo.sh -c ${CLIENT_EXE} --delete-bookinfo true
       infomsg "Uninstalling Istio ..."
       ${CLIENT_EXE} get namespace istio-system 2>/dev/null && ${HACK_SCRIPTS_DIR}/istio/install-istio-via-istioctl.sh -c ${CLIENT_EXE} --delete-istio true
-      infomsg "Uninstalling Skupper pipe ..."
+      infomsg "Uninstalling Mongo Skupper pipe ..."
       ${CLIENT_EXE} get namespace ${MONGOSKUPPERNS} 2>/dev/null && ${CLIENT_EXE} delete namespace ${MONGOSKUPPERNS}
+      infomsg "Uninstalling MySQL Skupper pipe ..."
+      ${CLIENT_EXE} get namespace ${MYSQLSKUPPERNS} 2>/dev/null && ${CLIENT_EXE} delete namespace ${MYSQLSKUPPERNS}
 
       infomsg "Uninstalling ${NAMESPACE_WEST} namespace ..."
       ${CLIENT_EXE} get namespace ${NAMESPACE_WEST} 2>/dev/null && ${CLIENT_EXE} delete namespace ${NAMESPACE_WEST}
@@ -711,25 +748,46 @@ elif [ "$_CMD" == "kui" ]; then
     *) errormsg "Invalid cluster type" && exit 1 ;;
   esac
 
-elif [ "$_CMD" == "smetrics" ]; then
+elif [ "$_CMD" == "bui" ]; then
+
+  confirm_cluster_is_up "${CLUSTER1_ISTIO}"
+  infomsg "Opening browser tab to the Bookinfo UI"
+
+  case ${CLUSTER_TYPE} in
+    minikube) open_browser http://$(${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n bookinfo get svc productpage -ojsonpath='{.status.loadBalancer.ingress[0].ip}'):9080 ;;
+    openshift) open_browser http://$(${CLIENT_EXE} -n bookinfo get route productpage -ojsonpath='{.spec.host}') ;;
+    *) errormsg "Invalid cluster type" && exit 1 ;;
+  esac
+
+elif [ "$_CMD" == "smetricsmongo" -o "$_CMD" == "smetricsmysql" ]; then
+
+  case "${_CMD}" in
+    smetricsmongo) _skupper_ns="${MONGOSKUPPERNS}" ;;
+    smetricsmysql) _skupper_ns="${MYSQLSKUPPERNS}" ;;
+  esac
 
   confirm_cluster_is_up "${CLUSTER1_ISTIO}"
   infomsg "Dumping live metrics from the Skupper service controller"
 
   case ${CLUSTER_TYPE} in
-    minikube) ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} exec -it -n ${MONGOSKUPPERNS} -c service-controller deploy/skupper-service-controller -- curl -k https://localhost:8010/api/v1alpha1/metrics/ ;;
-    openshift) ${CLIENT_EXE} exec -it -n ${MONGOSKUPPERNS} -c service-controller deploy/skupper-service-controller -- curl -k https://localhost:8010/api/v1alpha1/metrics/ ;;
+    minikube) ${CLIENT_EXE} --context ${CLUSTER1_ISTIO} exec -it -n ${_skupper_ns} -c service-controller deploy/skupper-service-controller -- curl -k https://localhost:8010/api/v1alpha1/metrics/ ;;
+    openshift) ${CLIENT_EXE} exec -it -n ${_skupper_ns} -c service-controller deploy/skupper-service-controller -- curl -k https://localhost:8010/api/v1alpha1/metrics/ ;;
     *) errormsg "Invalid cluster type" && exit 1 ;;
   esac
 
-elif [ "$_CMD" == "sprom" ]; then
+elif [ "$_CMD" == "sprommongo" -o "$_CMD" == "sprommysql" ]; then
+
+  case "${_CMD}" in
+    sprommongo) _skupper_ns="${MONGOSKUPPERNS}" ;;
+    sprommysql) _skupper_ns="${MYSQLSKUPPERNS}" ;;
+  esac
 
   confirm_cluster_is_up "${CLUSTER1_ISTIO}"
-  infomsg "Opening browser tab to the Skupper Prometheus UI"
+  infomsg "Opening browser tab to the Skupper Prometheus UI found in namespace [${_skupper_ns}]"
 
   case ${CLUSTER_TYPE} in
-    minikube) open_browser http://$(${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n ${MONGOSKUPPERNS} get svc skupper-prometheus -ojsonpath='{.status.loadBalancer.ingress[0].ip}'):9090 ;;
-    openshift) open_browser http://$(${CLIENT_EXE} -n ${MONGOSKUPPERNS} get route skupper-prometheus -ojsonpath='{.spec.host}') ;;
+    minikube) open_browser http://$(${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n ${_skupper_ns} get svc skupper-prometheus -ojsonpath='{.status.loadBalancer.ingress[0].ip}'):9090 ;;
+    openshift) open_browser http://$(${CLIENT_EXE} -n ${_skupper_ns} get route skupper-prometheus -ojsonpath='{.spec.host}') ;;
     *) errormsg "Invalid cluster type" && exit 1 ;;
   esac
 
@@ -738,7 +796,7 @@ elif [ "$_CMD" == "sstatus" ]; then
   confirm_cluster_is_up "${CLUSTER2_DB}"
   skupper_ui_username="admin"
 
-  infomsg "Status of Skupper link on [${CLUSTER2_DB}] cluster:"
+  infomsg "Status of Mongo Skupper link on [${CLUSTER2_DB}] cluster:"
 
   case ${CLUSTER_TYPE} in
     minikube) ${SKUPPER_EXE} --context ${CLUSTER2_DB} -n ${MONGONS} link status ;;
@@ -747,7 +805,7 @@ elif [ "$_CMD" == "sstatus" ]; then
   esac
 
   confirm_cluster_is_up "${CLUSTER1_ISTIO}"
-  infomsg "Status of Skupper link on [${CLUSTER1_ISTIO}] cluster:"
+  infomsg "Status of Mongo Skupper link on [${CLUSTER1_ISTIO}] cluster:"
 
   case ${CLUSTER_TYPE} in
     minikube) ${SKUPPER_EXE} --context ${CLUSTER1_ISTIO} -n ${MONGOSKUPPERNS} link status ;;
@@ -783,18 +841,23 @@ elif [ "$_CMD" == "sstatus" ]; then
     infomsg "East-West Demo Skupper UI URL (USERNAME=[${skupper_ui_username}], PASSWORD=[${east_west_demo_spass}]): ${east_west_demo_sui}"
   fi
 
-elif [ "$_CMD" == "sui" ]; then
+elif [ "$_CMD" == "suimongo" -o "$_CMD" == "suimysql" ]; then
+
+  case "${_CMD}" in
+    suimongo) _skupper_ns="${MONGOSKUPPERNS}" ;;
+    suimysql) _skupper_ns="${MYSQLSKUPPERNS}" ;;
+  esac
 
   confirm_cluster_is_up "${CLUSTER1_ISTIO}"
   skupper_ui_username="admin"
 
   case ${CLUSTER_TYPE} in
     minikube)
-      PASSWORD="$(${CLIENT_EXE} --context ${CLUSTER1_ISTIO} get secret -n ${MONGOSKUPPERNS} skupper-console-users -ojsonpath={.data.${skupper_ui_username}} | base64 -d)"
-      open_browser https://$(${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n ${MONGOSKUPPERNS} get svc skupper -ojsonpath='{.status.loadBalancer.ingress[0].ip}'):8010 ;;
+      PASSWORD="$(${CLIENT_EXE} --context ${CLUSTER1_ISTIO} get secret -n ${_skupper_ns} skupper-console-users -ojsonpath={.data.${skupper_ui_username}} | base64 -d)"
+      open_browser https://$(${CLIENT_EXE} --context ${CLUSTER1_ISTIO} -n ${_skupper_ns} get svc skupper -ojsonpath='{.status.loadBalancer.ingress[0].ip}'):8010 ;;
     openshift)
-      PASSWORD="$(${CLIENT_EXE} get secret -n ${MONGOSKUPPERNS} skupper-console-users -ojsonpath={.data.${skupper_ui_username}} | base64 -d)"
-      open_browser https://$(${CLIENT_EXE} -n ${MONGOSKUPPERNS} get route skupper -ojsonpath='{.spec.host}') ;;
+      PASSWORD="$(${CLIENT_EXE} get secret -n ${_skupper_ns} skupper-console-users -ojsonpath={.data.${skupper_ui_username}} | base64 -d)"
+      open_browser https://$(${CLIENT_EXE} -n ${_skupper_ns} get route skupper -ojsonpath='{.spec.host}') ;;
     *) errormsg "Invalid cluster type" && exit 1 ;;
   esac
 
diff --git a/hack/run-kiali.sh b/hack/run-kiali.sh
index f3a9a418e..5344057fe 100755
--- a/hack/run-kiali.sh
+++ b/hack/run-kiali.sh
@@ -366,7 +366,7 @@ if [ -z "${PROMETHEUS_URL:-}" ]; then
       warnmsg "Cannot auto-discover Prometheus on Kubernetes. If you exposed it, you can specify the Prometheus URL via --prometheus-url. For now, this session will attempt to port-forward to it."
       prom_remote_port="$(${CLIENT_EXE} get service -n ${ISTIO_NAMESPACE} prometheus -o jsonpath='{.spec.ports[0].targetPort}')"
       if [ "$?" != "0" -o -z "${prom_remote_port}" ]; then
-        warnmsg "Cannot auto-discover Prometheus port on Kubernetes. If you exposed it, you can specify the Prometheus URL via --prometheus-url. For now, this session will attempt to port-forward to it."
+        warnmsg "Failed to port forward. Cannot auto-discover Prometheus port on Kubernetes. If you exposed it, you can specify the Prometheus URL via --prometheus-url. For now, this session will attempt to port-forward to it."
       else
         prom_local_port="$(echo ${LOCAL_REMOTE_PORTS_PROMETHEUS} | cut -d ':' -f 1)"
         LOCAL_REMOTE_PORTS_PROMETHEUS="${prom_local_port}:${prom_remote_port}"
diff --git a/handlers/istio_status.go b/handlers/istio_status.go
index 770e997c5..9b96f05ef 100644
--- a/handlers/istio_status.go
+++ b/handlers/istio_status.go
@@ -2,6 +2,9 @@ package handlers
 
 import (
 	"net/http"
+
+	"github.com/kiali/kiali/kubernetes"
+	"github.com/kiali/kiali/util/sliceutil"
 )
 
 // IstioStatus returns a list of istio components and its status
@@ -13,11 +16,17 @@ func IstioStatus(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	istioStatus, err := business.IstioStatus.GetStatus(r.Context(), clusterNameFromQuery(r.URL.Query()))
+	istioStatus, err := business.IstioStatus.GetStatus(r.Context())
 	if err != nil {
 		handleErrorResponse(w, err)
 		return
 	}
 
+	if cluster := r.URL.Query().Get("clusterName"); cluster != "" {
+		istioStatus = sliceutil.Filter(istioStatus, func(status kubernetes.ComponentStatus) bool {
+			return status.Cluster == cluster
+		})
+	}
+
 	RespondWithJSON(w, http.StatusOK, istioStatus)
 }
diff --git a/handlers/mesh.go b/handlers/mesh.go
index e37b129e7..4d115ecd3 100644
--- a/handlers/mesh.go
+++ b/handlers/mesh.go
@@ -15,28 +15,6 @@ import (
 	"github.com/kiali/kiali/tracing"
 )
 
-func OutboundTrafficPolicyMode(w http.ResponseWriter, r *http.Request) {
-	business, err := getBusiness(r)
-	if err != nil {
-		RespondWithError(w, http.StatusInternalServerError, err.Error())
-		return
-	}
-
-	otp, _ := business.Mesh.OutboundTrafficPolicy()
-	RespondWithJSON(w, http.StatusOK, otp)
-}
-
-func IstiodResourceThresholds(w http.ResponseWriter, r *http.Request) {
-	business, err := getBusiness(r)
-	if err != nil {
-		RespondWithError(w, http.StatusInternalServerError, err.Error())
-		return
-	}
-
-	irt, _ := business.Mesh.IstiodResourceThresholds()
-	RespondWithJSON(w, http.StatusOK, irt)
-}
-
 func IstiodCanariesStatus(w http.ResponseWriter, r *http.Request) {
 	business, err := getBusiness(r)
 	if err != nil {
diff --git a/istio/discovery.go b/istio/discovery.go
index 9b5bb515a..795193b34 100644
--- a/istio/discovery.go
+++ b/istio/discovery.go
@@ -10,6 +10,7 @@ import (
 	"golang.org/x/exp/maps"
 
 	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	k8syaml "k8s.io/apimachinery/pkg/util/yaml"
@@ -53,6 +54,12 @@ func parseIstioConfigMap(istioConfig *corev1.ConfigMap) (*models.IstioMeshConfig
 		return nil, err
 	}
 
+	// Set some defaults if they are not set.
+	// TODO: Ideally we'd just display the raw yaml.
+	if meshConfig.OutboundTrafficPolicy.Mode == "" {
+		meshConfig.OutboundTrafficPolicy.Mode = "ALLOW_ANY"
+	}
+
 	return meshConfig, nil
 }
 
@@ -280,6 +287,19 @@ func (in *Discovery) Mesh(ctx context.Context) (*models.Mesh, error) {
 						controlPlane.ID = env.Value
 					}
 				}
+				controlPlane.Resources = containers[0].Resources
+				if memoryLimit := controlPlane.Resources.Limits.Memory(); memoryLimit != nil {
+					if thresholds := controlPlane.Thresholds; thresholds == nil {
+						controlPlane.Thresholds = &models.IstiodThresholds{}
+					}
+					controlPlane.Thresholds.Memory = float64(memoryLimit.ScaledValue(resource.Mega))
+				}
+				if cpuLimit := controlPlane.Resources.Limits.Cpu(); cpuLimit != nil {
+					if thresholds := controlPlane.Thresholds; thresholds == nil {
+						controlPlane.Thresholds = &models.IstiodThresholds{}
+					}
+					controlPlane.Thresholds.CPU = cpuLimit.AsApproximateFloat64()
+				}
 			}
 
 			// If the cluster id set on the controlplane matches the cluster's id then it manages the cluster it is deployed on.
@@ -306,6 +326,14 @@ func (in *Discovery) Mesh(ctx context.Context) (*models.Mesh, error) {
 				controlPlane.Version = versionInfo
 			}()
 
+			// Get the status for the control plane.
+			status, err := in.canConnectToIstiodForRevision(controlPlane)
+			if err != nil {
+				log.Warningf("Unable to get status for controlplane [%s/%s] on cluster [%s]. Err: %s", controlPlane.IstiodName, controlPlane.IstiodNamespace, cluster.Name, err)
+			} else {
+				controlPlane.Status = status.Status
+			}
+
 			mesh.ControlPlanes = append(mesh.ControlPlanes, controlPlane)
 		}
 	}
@@ -570,3 +598,53 @@ func (in *Discovery) resolveNetwork(clusterName string) string {
 
 	return network
 }
+
+// canConnectToIstiodForRevision checks if Kiali can reach the istiod pod(s) via port
+// fowarding through the k8s api server or via http if the registry is
+// configured with a remote url. An error does not indicate that istiod
+// cannot be reached. The kubernetes.IstioComponentStatus must be checked.
+func (in *Discovery) canConnectToIstiodForRevision(controlPlane models.ControlPlane) (*kubernetes.ComponentStatus, error) {
+	client := in.kialiSAClients[controlPlane.Cluster.Name]
+	if client == nil {
+		return nil, fmt.Errorf("unable to get service account client for cluster [%s]", controlPlane.Cluster.Name)
+	}
+
+	kubeCache, err := in.kialiCache.GetKubeCache(client.ClusterInfo().Name)
+	if err != nil {
+		return nil, err
+	}
+
+	istiodPods, err := GetHealthyIstiodPods(kubeCache, controlPlane.Revision, controlPlane.IstiodNamespace)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(istiodPods) == 0 {
+		return nil, fmt.Errorf("no healthy istiod pods found for revision [%s]", controlPlane.Revision)
+	}
+
+	// Assuming that all pods are running the same version, we only need to get the version from one healthy istiod pod.
+	// Sort by creation time stamp to return the "latest" pod.
+	slices.SortFunc(istiodPods, func(a, b *corev1.Pod) int {
+		return a.CreationTimestamp.Time.Compare(b.CreationTimestamp.Time)
+	})
+	istiodPod := GetLatestPod(istiodPods)
+	status := kubernetes.ComponentHealthy
+	// The 8080 port is not accessible from outside of the pod. However, it is used for kubernetes to do the live probes.
+	// Using the proxy method to make sure that K8s API has access to the Istio Control Plane namespace.
+	// By proxying one Istiod, we ensure that the following connection is allowed:
+	// Kiali -> K8s API (proxy) -> istiod
+	// This scenario is not obvious for private clusters (like GKE private cluster)
+	if _, err := client.ForwardGetRequest(istiodPod.Namespace, istiodPod.Name, 8080, "/ready"); err != nil {
+		log.Warningf("Unable to get ready status of istiod: %s/%s. Err: %s", istiodPod.Namespace, istiodPod.Name, err)
+		status = kubernetes.ComponentUnreachable
+	}
+
+	return &kubernetes.ComponentStatus{
+		Cluster:   controlPlane.Cluster.Name,
+		Name:      controlPlane.IstiodName,
+		Namespace: controlPlane.IstiodNamespace,
+		Status:    status,
+		IsCore:    true,
+	}, nil
+}
diff --git a/istio/kubernetes.go b/istio/kubernetes.go
new file mode 100644
index 000000000..c6cd24108
--- /dev/null
+++ b/istio/kubernetes.go
@@ -0,0 +1,49 @@
+package istio
+
+// Where you should put kubernetes related utilities.
+// Some of these are here simply because they would
+// cause circular imports if they went in the top
+// level kubernetes package.
+
+import (
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/labels"
+
+	"github.com/kiali/kiali/kubernetes/cache"
+)
+
+func GetHealthyIstiodPods(kubeCache cache.KubeCache, revision string, namespace string) ([]*corev1.Pod, error) {
+	podLabels := map[string]string{
+		"app":          "istiod",
+		"istio.io/rev": revision,
+	}
+
+	istiods, err := kubeCache.GetPods(namespace, labels.Set(podLabels).String())
+	if err != nil {
+		return nil, err
+	}
+
+	healthyIstiods := make([]*corev1.Pod, 0, len(istiods))
+	for i, istiod := range istiods {
+		if istiod.Status.Phase == corev1.PodRunning {
+			healthyIstiods = append(healthyIstiods, &istiods[i])
+		}
+	}
+
+	return healthyIstiods, nil
+}
+
+func GetLatestPod(pods []*corev1.Pod) *corev1.Pod {
+	if len(pods) == 0 {
+		return nil
+	}
+
+	latestPod := pods[0]
+	for _, pod := range pods {
+		if pod.CreationTimestamp.After(latestPod.CreationTimestamp.Time) {
+			latestPod = pod
+		}
+	}
+
+	return latestPod
+}
diff --git a/istio/version.go b/istio/version.go
index 0c9c0bc1c..9d310e0ab 100644
--- a/istio/version.go
+++ b/istio/version.go
@@ -198,7 +198,7 @@ func GetVersion(ctx context.Context, conf *config.Config, client kubernetes.Clie
 		return parseRawIstioVersion(rawVersion), nil
 	}
 
-	istiods, err := getHealthyIstiodPods(kubeCache, revision, namespace)
+	istiods, err := GetHealthyIstiodPods(kubeCache, revision, namespace)
 	if err != nil {
 		return nil, err
 	}
@@ -212,7 +212,7 @@ func GetVersion(ctx context.Context, conf *config.Config, client kubernetes.Clie
 	slices.SortFunc(istiods, func(a, b *corev1.Pod) int {
 		return a.CreationTimestamp.Time.Compare(b.CreationTimestamp.Time)
 	})
-	istiod := getLatestPod(istiods)
+	istiod := GetLatestPod(istiods)
 
 	resp, err := client.ForwardGetRequest(istiod.Namespace, istiod.Name, conf.ExternalServices.Istio.IstiodPodMonitoringPort, "/version")
 	if err != nil {
@@ -221,39 +221,3 @@ func GetVersion(ctx context.Context, conf *config.Config, client kubernetes.Clie
 
 	return parseRawIstioVersion(string(resp)), nil
 }
-
-func getHealthyIstiodPods(kubeCache cache.KubeCache, revision string, namespace string) ([]*corev1.Pod, error) {
-	podLabels := map[string]string{
-		"app":          "istiod",
-		"istio.io/rev": revision,
-	}
-
-	istiods, err := kubeCache.GetPods(namespace, labels.Set(podLabels).String())
-	if err != nil {
-		return nil, err
-	}
-
-	healthyIstiods := make([]*corev1.Pod, 0, len(istiods))
-	for i, istiod := range istiods {
-		if istiod.Status.Phase == corev1.PodRunning {
-			healthyIstiods = append(healthyIstiods, &istiods[i])
-		}
-	}
-
-	return healthyIstiods, nil
-}
-
-func getLatestPod(pods []*corev1.Pod) *corev1.Pod {
-	if len(pods) == 0 {
-		return nil
-	}
-
-	latestPod := pods[0]
-	for _, pod := range pods {
-		if pod.CreationTimestamp.After(latestPod.CreationTimestamp.Time) {
-			latestPod = pod
-		}
-	}
-
-	return latestPod
-}
diff --git a/kiali.go b/kiali.go
index a7bfc0469..d4a021416 100644
--- a/kiali.go
+++ b/kiali.go
@@ -247,7 +247,7 @@ func updateConfigWithIstioInfo() {
 		}
 
 		// Try to auto-detect the cluster name
-		homeCluster, _, err = kubernetes.ClusterInfoFromIstiod(conf, cf.GetSAHomeClusterClient())
+		homeCluster, err = kubernetes.ClusterNameFromIstiod(conf, cf.GetSAHomeClusterClient())
 		if err != nil {
 			return err
 		}
diff --git a/kubernetes/istio.go b/kubernetes/istio.go
index 9714b29b2..a37da7282 100644
--- a/kubernetes/istio.go
+++ b/kubernetes/istio.go
@@ -11,6 +11,8 @@ import (
 	networking_v1 "istio.io/client-go/pkg/apis/networking/v1"
 	security_v1 "istio.io/client-go/pkg/apis/security/v1"
 	istio "istio.io/client-go/pkg/clientset/versioned"
+	appsv1 "k8s.io/api/apps/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	k8s_networking_v1 "sigs.k8s.io/gateway-api/apis/v1"
 	gatewayapiclient "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
 
@@ -28,6 +30,10 @@ const (
 )
 
 type ComponentStatus struct {
+	// Cluster where this component is deployed.
+	// Can be unknown or external for external services.
+	Cluster string `json:"-"`
+
 	// Namespace where the component is deployed.
 	// This field is ignored when marshalling to JSON.
 	Namespace string `json:"-"`
@@ -307,20 +313,36 @@ func DestinationRuleHasMTLSEnabled(destinationRule *networking_v1.DestinationRul
 	return false, ""
 }
 
-// ClusterInfoFromIstiod attempts to resolve the cluster info of the "home" cluster where kiali is running
+// ClusterNameFromIstiod attempts to resolve the cluster info of the "home" cluster where kiali is running
 // by inspecting the istiod deployment. Assumes that the istiod deployment is in the same cluster as the kiali pod.
-func ClusterInfoFromIstiod(conf config.Config, k8s ClientInterface) (string, bool, error) {
+func ClusterNameFromIstiod(conf config.Config, k8s ClientInterface) (string, error) {
 	// The "cluster_id" is set in an environment variable of
 	// the "istiod" deployment. Let's try to fetch it.
-	istioDeploymentConfig := conf.ExternalServices.Istio.IstiodDeploymentName
-	istiodDeployment, err := k8s.GetDeployment(conf.IstioNamespace, istioDeploymentConfig)
-	if err != nil {
-		return "", false, err
+	var istiodDeployment *appsv1.Deployment
+	if istiodDeploymentName := conf.ExternalServices.Istio.IstiodDeploymentName; istiodDeploymentName != "" {
+		deployment, err := k8s.GetDeployment(conf.IstioNamespace, istiodDeploymentName)
+		if err != nil {
+			return "", err
+		}
+
+		istiodDeployment = deployment
+	} else {
+		istiodDeployments, err := k8s.GetDeployments(conf.IstioNamespace, metav1.ListOptions{LabelSelector: "app=istiod"})
+		if err != nil {
+			return "", err
+		}
+
+		if len(istiodDeployments) == 0 {
+			return "", fmt.Errorf("istiod deployment not found in namespace [%s]", conf.IstioNamespace)
+		}
+
+		// Just take the first one since they should all have the same cluster id.
+		istiodDeployment = &istiodDeployments[0]
 	}
 
 	istiodContainers := istiodDeployment.Spec.Template.Spec.Containers
 	if len(istiodContainers) == 0 {
-		return "", false, fmt.Errorf("istiod deployment [%s] has no containers", istioDeploymentConfig)
+		return "", fmt.Errorf("istiod deployment [%s] has no containers", istiodDeployment.Name)
 	}
 
 	clusterName := ""
@@ -331,20 +353,12 @@ func ClusterInfoFromIstiod(conf config.Config, k8s ClientInterface) (string, boo
 		}
 	}
 
-	gatewayToNamespace := false
-	for _, v := range istiodContainers[0].Env {
-		if v.Name == "PILOT_SCOPE_GATEWAY_TO_NAMESPACE" {
-			gatewayToNamespace = v.Value == "true"
-			break
-		}
-	}
-
 	if clusterName == "" {
 		// We didn't find it. This may mean that Istio is not setup with multi-cluster enabled.
-		return "", false, fmt.Errorf("istiod deployment [%s] does not have the CLUSTER_ID environment variable set", istioDeploymentConfig)
+		return "", fmt.Errorf("istiod deployment [%s] does not have the CLUSTER_ID environment variable set", istiodDeployment.Name)
 	}
 
-	return clusterName, gatewayToNamespace, nil
+	return clusterName, nil
 }
 
 func GatewayAPIClasses(ambientEnabled bool) []config.GatewayAPIClass {
diff --git a/kubernetes/kubernetes.go b/kubernetes/kubernetes.go
index 13c8c2e02..f768165e6 100644
--- a/kubernetes/kubernetes.go
+++ b/kubernetes/kubernetes.go
@@ -44,6 +44,7 @@ type K8SClientInterface interface {
 	GetConfigMap(namespace, name string) (*core_v1.ConfigMap, error)
 	GetCronJobs(namespace string) ([]batch_v1.CronJob, error)
 	GetDeployment(namespace string, name string) (*apps_v1.Deployment, error)
+	GetDeployments(namespace string, opts meta_v1.ListOptions) ([]apps_v1.Deployment, error)
 	GetJobs(namespace string) ([]batch_v1.Job, error)
 	GetNamespace(namespace string) (*core_v1.Namespace, error)
 	GetNamespaces(labelSelector string) ([]core_v1.Namespace, error)
@@ -316,8 +317,8 @@ func (in *K8SClient) GetDeployment(namespace, name string) (*apps_v1.Deployment,
 
 // GetDeployments returns an array of deployments for a given namespace.
 // It returns an error on any problem.
-func (in *K8SClient) GetDeployments(namespace string) ([]apps_v1.Deployment, error) {
-	if depList, err := in.k8s.AppsV1().Deployments(namespace).List(in.ctx, emptyListOptions); err == nil {
+func (in *K8SClient) GetDeployments(namespace string, opts meta_v1.ListOptions) ([]apps_v1.Deployment, error) {
+	if depList, err := in.k8s.AppsV1().Deployments(namespace).List(in.ctx, opts); err == nil {
 		return depList.Items, nil
 	} else {
 		return []apps_v1.Deployment{}, err
diff --git a/mesh/appender.go b/mesh/appender.go
index 6bc6d9f36..720215471 100644
--- a/mesh/appender.go
+++ b/mesh/appender.go
@@ -70,5 +70,5 @@ type Appender interface {
 
 // IstioStatusGetter gets the status of the mesh graph components
 type IstioStatusGetter interface {
-	GetStatus(ctx context.Context, cluster string) (kubernetes.IstioComponentStatus, error)
+	GetStatus(ctx context.Context) (kubernetes.IstioComponentStatus, error)
 }
diff --git a/mesh/generator/generator.go b/mesh/generator/generator.go
index 046420aa3..5e3118603 100644
--- a/mesh/generator/generator.go
+++ b/mesh/generator/generator.go
@@ -7,7 +7,6 @@ import (
 	"fmt"
 	"regexp"
 	"slices"
-	"strings"
 
 	"github.com/kiali/kiali/config"
 	"github.com/kiali/kiali/graph"
@@ -28,6 +27,16 @@ func getNamespacesByName(name string, namespaces []models.Namespace) []models.Na
 	return result
 }
 
+type componentHealthKey struct {
+	Cluster   string
+	Name      string
+	Namespace string
+}
+
+func (c componentHealthKey) String() string {
+	return c.Name + c.Namespace + c.Cluster
+}
+
 // BuildMeshMap is required by the graph/TelemetryVendor interface
 func BuildMeshMap(ctx context.Context, o mesh.Options, gi *mesh.AppenderGlobalInfo) mesh.MeshMap {
 	var end observability.EndFunc
@@ -68,6 +77,17 @@ func BuildMeshMap(ctx context.Context, o mesh.Options, gi *mesh.AppenderGlobalIn
 		}
 	}
 
+	// get istio status components (istiod, grafana, prometheus, tracing)
+	istioStatus, err := gi.IstioStatusGetter.GetStatus(ctx)
+	mesh.CheckError(err)
+
+	// convert istio status slice into map
+	healthData := map[string]string{}
+	for _, data := range istioStatus {
+		key := componentHealthKey{Name: data.Name, Namespace: data.Namespace, Cluster: data.Cluster}.String()
+		healthData[key] = data.Status
+	}
+
 	clusterMap := make(map[string]bool)
 	for _, cp := range meshDef.ControlPlanes {
 		// add control plane cluster if not already added
@@ -94,34 +114,18 @@ func BuildMeshMap(ctx context.Context, o mesh.Options, gi *mesh.AppenderGlobalIn
 
 		name := cp.IstiodName
 
-		// get istio status components (istiod, grafana, prometheus, tracing)
-		var istioStatus kubernetes.IstioComponentStatus
-		istioStatus, err = gi.IstioStatusGetter.GetStatus(ctx, cp.Cluster.Name)
-		mesh.CheckError(err)
-
-		// convert istio status slice into map
-		healthData := make(map[string]string)
-		for _, data := range istioStatus {
-			// istiod health depends on istiod and istio-pod status (both starts with the istiod name of the control plane)
-			if strings.HasPrefix(data.Name, cp.IstiodName) {
-				// don't update if previous status is not healthy to display a problem in the mesh
-				if healthData[cp.IstiodName] == "" || healthData[cp.IstiodName] == kubernetes.ComponentHealthy {
-					healthData[cp.IstiodName] = data.Status
-				}
-			} else {
-				healthData[data.Name] = data.Status
-			}
-		}
-
 		version := "Unknown"
 		if cp.Version != nil {
 			version = cp.Version.Version
 		}
+
 		infraData := map[string]any{
-			"config":   cp.Config,
-			"revision": cp.Revision,
+			"config":     cp.Config,
+			"revision":   cp.Revision,
+			"thresholds": cp.Thresholds,
 		}
-		istiod, _, err := addInfra(meshMap, mesh.InfraTypeIstiod, cp.Cluster.Name, cp.IstiodNamespace, name, infraData, version, false, healthData[cp.IstiodName], false)
+		healthDataKey := componentHealthKey{Name: cp.IstiodName, Namespace: cp.IstiodNamespace, Cluster: cp.Cluster.Name}.String()
+		istiod, _, err := addInfra(meshMap, mesh.InfraTypeIstiod, cp.Cluster.Name, cp.IstiodNamespace, name, infraData, version, false, healthData[healthDataKey], false)
 		mesh.CheckError(err)
 
 		// add the managed namespaces by cluster and narrowed, if necessary, by revision
diff --git a/models/mesh.go b/models/mesh.go
index e1feca324..3feb6e791 100644
--- a/models/mesh.go
+++ b/models/mesh.go
@@ -1,6 +1,7 @@
 package models
 
 import (
+	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -46,10 +47,21 @@ type ControlPlane struct {
 	// It could also manage the cluster that it is running on.
 	ManagesExternal bool
 
+	// Resources are the resources that the controlplane is using.
+	Resources corev1.ResourceRequirements
+
 	// Revision is the revision of the controlplane.
 	// Can be empty when it's the default revision.
 	Revision string
 
+	// Status is the status of the controlplane as reported by kiali.
+	// It includes the deployment status and whether kiali can connect
+	// to the controlplane or not.
+	Status string
+
+	// Thresholds is the thresholds for the controlplane.
+	Thresholds *IstiodThresholds
+
 	// Version is the version of the controlplane.
 	Version *ExternalServiceInfo
 }
@@ -60,9 +72,6 @@ type ControlPlaneConfiguration struct {
 	// This is not currently used by the frontend so excluding it from the API response.
 	IsGatewayToNamespace bool `json:"-"`
 
-	// OutboundTrafficPolicy is the outbound traffic policy for the controlplane.
-	OutboundTrafficPolicy OutboundPolicy
-
 	// Network is the name of the network that the controlplane is using.
 	Network string
 
@@ -86,7 +95,8 @@ type IstioMeshConfig struct {
 	DefaultConfig struct {
 		MeshId string `yaml:"meshId"`
 	} `yaml:"defaultConfig" json:"defaultConfig"`
-	TrustDomain string `yaml:"trustDomain,omitempty"`
+	OutboundTrafficPolicy OutboundPolicy `yaml:"outboundTrafficPolicy,omitempty"`
+	TrustDomain           string         `yaml:"trustDomain,omitempty"`
 }
 
 func (imc IstioMeshConfig) GetEnableAutoMtls() bool {
diff --git a/models/outbound_policy.go b/models/outbound_policy.go
index 18ec04bd4..448633466 100644
--- a/models/outbound_policy.go
+++ b/models/outbound_policy.go
@@ -2,5 +2,5 @@ package models
 
 // OutboundPolicy contains information egress traffic permissions
 type OutboundPolicy struct {
-	Mode string `json:"mode"`
+	Mode string `yaml:"mode" json:"mode"`
 }
diff --git a/routing/routes.go b/routing/routes.go
index e109d8328..c00f6f93d 100644
--- a/routing/routes.go
+++ b/routing/routes.go
@@ -1484,42 +1484,6 @@ func NewRoutes(
 			HandlerFunc:   handlers.MetricsStats,
 			Authenticated: true,
 		},
-		// swagger:route GET /api/mesh/outbound_traffic_policy/mode
-		// ---
-		// Endpoint to get the OutboundTrafficPolicy Mode configured in the service mesh.
-		//              Produces:
-		//              - application/json
-		//
-		//              Schemes: http, https
-		//
-		// responses:
-		//              500: internalError
-		//              200: clustersResponse
-		{
-			"OutboundTrafficPolicyMode",
-			"GET",
-			"/api/mesh/outbound_traffic_policy/mode",
-			handlers.OutboundTrafficPolicyMode,
-			true,
-		},
-		// swagger:route GET /api/mesh/resources/thresholds
-		// ---
-		// Endpoint to get the IstiodResourceThresholds.
-		//              Produces:
-		//              - application/json
-		//
-		//              Schemes: http, https
-		//
-		// responses:
-		//              500: internalError
-		//              200: istiodResourceThresholds
-		{
-			"IstiodResourceThresholds",
-			"GET",
-			"/api/mesh/resources/thresholds",
-			handlers.IstiodResourceThresholds,
-			true,
-		},
 		// swagger:route GET /api/mesh/canaries/status
 		// ---
 		// Endpoint to get the IstiodCanariesStatus.


Test patch:
diff --git a/business/controlplane_monitor_test.go b/business/controlplane_monitor_test.go
index 60acbd7cd..d5659eb2e 100644
--- a/business/controlplane_monitor_test.go
+++ b/business/controlplane_monitor_test.go
@@ -2,7 +2,6 @@ package business
 
 import (
 	"context"
-	"fmt"
 	"io"
 	"net/http"
 	"net/http/httptest"
@@ -19,6 +18,7 @@ import (
 	"github.com/kiali/kiali/config"
 	"github.com/kiali/kiali/istio"
 	"github.com/kiali/kiali/kubernetes"
+	"github.com/kiali/kiali/kubernetes/cache"
 	"github.com/kiali/kiali/kubernetes/kubetest"
 	"github.com/kiali/kiali/models"
 )
@@ -107,152 +107,6 @@ func runningIstiodPod() *core_v1.Pod {
 	}
 }
 
-func TestCanConnectToIstiod(t *testing.T) {
-	assert := assert.New(t)
-	require := require.New(t)
-
-	conf := config.NewConfig()
-
-	testServer := istiodTestServer(t)
-	fakeForwarder := &fakeForwarder{
-		ClientInterface: kubetest.NewFakeK8sClient(
-			runningIstiodPod(),
-			fakeIstiodDeployment(conf.KubernetesConfig.ClusterName, false),
-			fakeIstioConfigMap("default"),
-			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}},
-		),
-		testURL: testServer.URL,
-	}
-
-	cache := SetupBusinessLayer(t, fakeForwarder, *conf)
-
-	cf := kubetest.NewK8SClientFactoryMock(fakeForwarder)
-	k8sclients := make(map[string]kubernetes.ClientInterface)
-	k8sclients[conf.KubernetesConfig.ClusterName] = fakeForwarder
-	discovery := istio.NewDiscovery(k8sclients, cache, conf)
-	cpm := NewControlPlaneMonitor(cache, cf, *conf, discovery)
-
-	status, err := cpm.CanConnectToIstiod(fakeForwarder)
-	require.NoError(err)
-	require.Len(status, 1)
-	assert.Equal(kubernetes.ComponentHealthy, status[0].Status)
-}
-
-func TestConnectToIstiodExternalURL(t *testing.T) {
-	assert := assert.New(t)
-	require := require.New(t)
-
-	testServer := istiodTestServer(t)
-
-	conf := config.NewConfig()
-	conf.ExternalServices.Istio.Registry = &config.RegistryConfig{
-		IstiodURL: testServer.URL,
-	}
-
-	k8s := kubetest.NewFakeK8sClient(runningIstiodPod())
-	cache := SetupBusinessLayer(t, k8s, *conf)
-
-	cf := kubetest.NewK8SClientFactoryMock(k8s)
-	k8sclients := make(map[string]kubernetes.ClientInterface)
-	k8sclients[conf.KubernetesConfig.ClusterName] = k8s
-	discovery := istio.NewDiscovery(k8sclients, cache, conf)
-	cpm := NewControlPlaneMonitor(cache, cf, *conf, discovery)
-
-	status, err := cpm.CanConnectToIstiod(k8s)
-	require.NoError(err)
-	require.Len(status, 1)
-	assert.Equal(kubernetes.ComponentHealthy, status[0].Status)
-}
-
-func TestConnectToIstiodWithRevisionExternalURL(t *testing.T) {
-	assert := assert.New(t)
-	require := require.New(t)
-
-	testServer := istiodTestServer(t)
-
-	conf := config.NewConfig()
-	conf.ExternalServices.Istio.Registry = &config.RegistryConfig{
-		IstiodURL: testServer.URL,
-	}
-
-	k8s := kubetest.NewFakeK8sClient(runningIstiodPod())
-	cache := SetupBusinessLayer(t, k8s, *conf)
-
-	cf := kubetest.NewK8SClientFactoryMock(k8s)
-	k8sclients := make(map[string]kubernetes.ClientInterface)
-	k8sclients[conf.KubernetesConfig.ClusterName] = k8s
-	discovery := istio.NewDiscovery(k8sclients, cache, conf)
-	cpm := NewControlPlaneMonitor(cache, cf, *conf, discovery)
-
-	status, err := cpm.CanConnectToIstiodForRevision(k8s, "default")
-	require.NoError(err)
-	require.Len(status, 1)
-	assert.Equal(kubernetes.ComponentHealthy, status[0].Status)
-}
-
-type badForwarder struct {
-	kubernetes.ClientInterface
-}
-
-func (f *badForwarder) ForwardGetRequest(namespace, podName string, destinationPort int, path string) ([]byte, error) {
-	return nil, fmt.Errorf("unable to forward request")
-}
-
-func TestCanConnectToUnreachableIstiod(t *testing.T) {
-	assert := assert.New(t)
-	require := require.New(t)
-
-	conf := config.NewConfig()
-
-	fakeForwarder := &badForwarder{
-		ClientInterface: kubetest.NewFakeK8sClient(
-			runningIstiodPod(),
-			fakeIstiodDeployment(conf.KubernetesConfig.ClusterName, false),
-			fakeIstioConfigMap("default"),
-			&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}},
-		),
-	}
-
-	cache := SetupBusinessLayer(t, fakeForwarder, *conf)
-
-	cf := kubetest.NewK8SClientFactoryMock(fakeForwarder)
-	k8sclients := make(map[string]kubernetes.ClientInterface)
-	k8sclients[conf.KubernetesConfig.ClusterName] = fakeForwarder
-	discovery := istio.NewDiscovery(k8sclients, cache, conf)
-	cpm := NewControlPlaneMonitor(cache, cf, *conf, discovery)
-
-	status, err := cpm.CanConnectToIstiod(fakeForwarder)
-	require.NoError(err)
-	require.Len(status, 1)
-	assert.Equal(kubernetes.ComponentUnreachable, status[0].Status)
-}
-
-func fakeIstiodWithRevision(cluster string, revision string, manageExternal bool) *apps_v1.Deployment {
-	deployment := fakeIstiodDeployment(cluster, manageExternal)
-	deployment.Labels[models.IstioRevisionLabel] = revision
-	deployment.Name = "istiod-" + revision
-	return deployment
-}
-
-func fakeIstioConfigMap(revision string) *core_v1.ConfigMap {
-	const configMapData = `accessLogFile: /dev/stdout
-enableAutoMtls: true
-rootNamespace: istio-system
-trustDomain: cluster.local
-`
-	configMapName := "istio"
-	if revision != "default" {
-		configMapName = "istio-" + revision
-	}
-	return &core_v1.ConfigMap{
-		ObjectMeta: meta_v1.ObjectMeta{
-			Name:      configMapName,
-			Namespace: "istio-system",
-		},
-		Data: map[string]string{"mesh": configMapData},
-	}
-}
-
 func fakeIstiodDeployment(cluster string, manageExternal bool) *apps_v1.Deployment {
 	deployment := &apps_v1.Deployment{
 		ObjectMeta: meta_v1.ObjectMeta{
@@ -296,13 +150,7 @@ func TestRefreshIstioCache(t *testing.T) {
 
 	conf := config.NewConfig()
 	conf.KubernetesConfig.ClusterName = "Kubernetes"
-	kubernetes.SetConfig(t, *conf)
 
-	const configMapData = `accessLogFile: /dev/stdout
-enableAutoMtls: true
-rootNamespace: istio-system
-trustDomain: cluster.local
-`
 	istioConfigMap := &core_v1.ConfigMap{
 		ObjectMeta: meta_v1.ObjectMeta{
 			Name:      "istio",
@@ -311,7 +159,7 @@ trustDomain: cluster.local
 				models.IstioRevisionLabel: "default",
 			},
 		},
-		Data: map[string]string{"mesh": configMapData},
+		Data: map[string]string{"mesh": ""},
 	}
 
 	k8s := kubetest.NewFakeK8sClient(
@@ -329,11 +177,10 @@ trustDomain: cluster.local
 		testURL:         testServer.URL,
 	}
 
-	cache := SetupBusinessLayer(t, fakeForwarder, *conf)
-
-	cf := kubetest.NewK8SClientFactoryMock(fakeForwarder)
 	k8sclients := make(map[string]kubernetes.ClientInterface)
 	k8sclients[conf.KubernetesConfig.ClusterName] = fakeForwarder
+	cf := kubetest.NewFakeClientFactory(conf, k8sclients)
+	cache := cache.NewTestingCacheWithFactory(t, cf, *conf)
 	discovery := istio.NewDiscovery(k8sclients, cache, conf)
 	cpm := NewControlPlaneMonitor(cache, cf, *conf, discovery)
 
@@ -357,12 +204,9 @@ func TestCancelingContextEndsPolling(t *testing.T) {
 	conf := config.NewConfig()
 	kubernetes.SetConfig(t, *conf)
 
-	k8s := kubetest.NewFakeK8sClient()
-	cache := SetupBusinessLayer(t, k8s, *conf)
-
-	cf := kubetest.NewK8SClientFactoryMock(k8s)
-	k8sclients := make(map[string]kubernetes.ClientInterface)
-	k8sclients[conf.KubernetesConfig.ClusterName] = k8s
+	k8sclients := map[string]kubernetes.ClientInterface{conf.KubernetesConfig.ClusterName: kubetest.NewFakeK8sClient()}
+	cf := kubetest.NewFakeClientFactory(conf, k8sclients)
+	cache := cache.NewTestingCacheWithFactory(t, cf, *conf)
 	discovery := istio.NewDiscovery(k8sclients, cache, conf)
 	cpm := NewControlPlaneMonitor(cache, cf, *conf, discovery)
 
@@ -377,13 +221,7 @@ func TestPollingPopulatesCache(t *testing.T) {
 	require := require.New(t)
 
 	conf := config.NewConfig()
-	kubernetes.SetConfig(t, *conf)
 
-	const configMapData = `accessLogFile: /dev/stdout
-enableAutoMtls: true
-rootNamespace: istio-system
-trustDomain: cluster.local
-`
 	istioConfigMap := &core_v1.ConfigMap{
 		ObjectMeta: meta_v1.ObjectMeta{
 			Name:      "istio",
@@ -392,7 +230,7 @@ trustDomain: cluster.local
 				models.IstioRevisionLabel: "default",
 			},
 		},
-		Data: map[string]string{"mesh": configMapData},
+		Data: map[string]string{"mesh": ""},
 	}
 
 	testServer := istiodTestServer(t)
@@ -411,11 +249,10 @@ trustDomain: cluster.local
 		testURL:         testServer.URL,
 	}
 
-	cache := SetupBusinessLayer(t, fakeForwarder, *conf)
-
-	cf := kubetest.NewK8SClientFactoryMock(fakeForwarder)
 	k8sclients := make(map[string]kubernetes.ClientInterface)
 	k8sclients[conf.KubernetesConfig.ClusterName] = fakeForwarder
+	cf := kubetest.NewFakeClientFactory(conf, k8sclients)
+	cache := cache.NewTestingCacheWithFactory(t, cf, *conf)
 	discovery := istio.NewDiscovery(k8sclients, cache, conf)
 	cpm := NewControlPlaneMonitor(cache, cf, *conf, discovery)
 	// Make this really low so that we get something sooner.
@@ -443,54 +280,3 @@ trustDomain: cluster.local
 		}
 	}
 }
-
-func TestRefreshIstioCacheWithMultipleRevisions(t *testing.T) {
-	require := require.New(t)
-
-	conf := config.NewConfig()
-	conf.KubernetesConfig.ClusterName = "Kubernetes"
-	kubernetes.SetConfig(t, *conf)
-
-	defaultIstiod := fakeIstiodDeployment(conf.KubernetesConfig.ClusterName, false)
-	istiod_1_19 := fakeIstiodWithRevision(conf.KubernetesConfig.ClusterName, "1-19-0", false)
-	defaultPod := runningIstiodPod()
-	defaultPod.Labels[models.IstioRevisionLabel] = "default"
-	istiod_1_19_pod := runningIstiodPod()
-	istiod_1_19_pod.Labels[models.IstioRevisionLabel] = "1-19-0"
-
-	k8s := kubetest.NewFakeK8sClient(
-		&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}},
-		defaultIstiod,
-		istiod_1_19,
-		fakeIstioConfigMap("default"),
-		fakeIstioConfigMap("1-19-0"),
-	)
-	// RefreshIstioCache relies on this being set.
-	k8s.KubeClusterInfo.Name = conf.KubernetesConfig.ClusterName
-
-	testServer := istiodTestServer(t)
-	fakeForwarder := &fakeForwarder{
-		ClientInterface: k8s,
-		testURL:         testServer.URL,
-	}
-
-	cache := SetupBusinessLayer(t, fakeForwarder, *conf)
-
-	cf := kubetest.NewK8SClientFactoryMock(fakeForwarder)
-	k8sclients := make(map[string]kubernetes.ClientInterface)
-	k8sclients[conf.KubernetesConfig.ClusterName] = fakeForwarder
-	discovery := istio.NewDiscovery(k8sclients, cache, conf)
-	cpm := NewControlPlaneMonitor(cache, cf, *conf, discovery)
-
-	defaultStatuses, err := cpm.CanConnectToIstiodForRevision(fakeForwarder, "default")
-	require.NoError(err)
-	for _, status := range defaultStatuses {
-		require.NotEqual(istiod_1_19_pod.Name, status.Name)
-	}
-
-	istiod_1_19_statuses, err := cpm.CanConnectToIstiodForRevision(fakeForwarder, "1-19-0")
-	require.NoError(err)
-	for _, status := range istiod_1_19_statuses {
-		require.NotEqual(istiod_1_19_pod.Name, status.Name)
-	}
-}
diff --git a/business/dashboards_test.go b/business/dashboards_test.go
index 21b452101..37080c3dd 100644
--- a/business/dashboards_test.go
+++ b/business/dashboards_test.go
@@ -55,7 +55,7 @@ func TestGetDashboard(t *testing.T) {
 	prom.MockMetric("my_metric_1_1", expectedLabels, &query.RangeQuery, 10)
 	prom.MockHistogram("my_metric_1_2", expectedLabels, &query.RangeQuery, 11, 12)
 
-	k8s := kubetest.NewK8SClientMock()
+	k8s := kubetest.NewFakeK8sClient()
 	mockClientFactory := kubetest.NewK8SClientFactoryMock(k8s)
 	SetWithBackends(mockClientFactory, nil)
 
diff --git a/business/istio_status_test.go b/business/istio_status_test.go
index fa28f1ae1..24a935d57 100644
--- a/business/istio_status_test.go
+++ b/business/istio_status_test.go
@@ -113,9 +113,8 @@ func TestComponentNamespaces(t *testing.T) {
 	a := assert.New(t)
 
 	conf := confWithComponentNamespaces()
-	config.Set(conf)
 
-	nss := getComponentNamespaces()
+	nss := getComponentNamespaces(conf)
 
 	a.Contains(nss, "istio-system")
 	a.Contains(nss, "istio-admin")
@@ -123,14 +122,12 @@ func TestComponentNamespaces(t *testing.T) {
 	a.Len(nss, 4)
 }
 
-func mockAddOnsCalls(t *testing.T, objects []runtime.Object, isIstioReachable bool, overrideAddonURLs bool) (kubernetes.ClientInterface, *int, *int) {
+func mockAddOnsCalls(t *testing.T, objects []runtime.Object, _ bool, overrideAddonURLs bool) (kubernetes.ClientInterface, *int, *int) {
 	// Prepare the Call counts for each Addon
 	grafanaCalls, prometheusCalls := 0, 0
 
 	objects = append(objects, &osproject_v1.Project{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}})
 
-	// Mock k8s api calls
-	mockDeploymentCall(objects, isIstioReachable)
 	k8s := kubetest.NewFakeK8sClient(objects...)
 	k8s.OpenShift = true
 	routes := mockAddOnCalls(defaultAddOnCalls(&grafanaCalls, &prometheusCalls))
@@ -198,7 +195,7 @@ func TestGrafanaWorking(t *testing.T) {
 	clients := make(map[string]kubernetes.ClientInterface)
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
-	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	icsl, error := iss.GetStatus(context.TODO())
 	assert.NoError(error)
 
 	// Requests to AddOns have to be 1
@@ -237,7 +234,7 @@ func TestGrafanaDisabled(t *testing.T) {
 	clients := make(map[string]kubernetes.ClientInterface)
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
-	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	icsl, error := iss.GetStatus(context.TODO())
 	assert.NoError(error)
 
 	// No request performed to Grafana endpoint
@@ -274,7 +271,6 @@ func TestGrafanaNotWorking(t *testing.T) {
 			}),
 	}
 	objects = append(objects, &osproject_v1.Project{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}})
-	mockDeploymentCall(objects, true)
 	addOnsStetup := defaultAddOnCalls(&grafanaCalls, &prometheusCalls)
 	addOnsStetup["grafana"] = addOnsSetup{
 		Url:        "/grafana/mock",
@@ -297,7 +293,7 @@ func TestGrafanaNotWorking(t *testing.T) {
 	clients := make(map[string]kubernetes.ClientInterface)
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
-	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	icsl, error := iss.GetStatus(context.TODO())
 	assert.NoError(error)
 
 	// Requests to AddOns have to be 1
@@ -331,7 +327,7 @@ func TestFailingTracingService(t *testing.T) {
 	clients := make(map[string]kubernetes.ClientInterface)
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 	iss := NewWithBackends(clients, clients, nil, mockFailingJaeger()).IstioStatus
-	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	icsl, error := iss.GetStatus(context.TODO())
 	assert.NoError(error)
 
 	// Requests to AddOns have to be 1
@@ -345,7 +341,6 @@ func TestFailingTracingService(t *testing.T) {
 	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
 	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
 	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
-
 }
 
 func TestOverriddenUrls(t *testing.T) {
@@ -361,7 +356,7 @@ func TestOverriddenUrls(t *testing.T) {
 	clients := make(map[string]kubernetes.ClientInterface)
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
-	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	icsl, error := iss.GetStatus(context.TODO())
 	assert.NoError(error)
 
 	// Requests to AddOns have to be 1
@@ -392,7 +387,7 @@ func TestCustomDashboardsMainPrometheus(t *testing.T) {
 	clients := make(map[string]kubernetes.ClientInterface)
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
-	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	icsl, error := iss.GetStatus(context.TODO())
 	assert.NoError(error)
 
 	// Requests to AddOns have to be 1
@@ -419,7 +414,7 @@ func TestNoIstioComponentFoundError(t *testing.T) {
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 
 	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
-	_, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	_, error := iss.GetStatus(context.TODO())
 	assert.Error(error)
 }
 
@@ -441,12 +436,18 @@ func TestDefaults(t *testing.T) {
 	conf := config.NewConfig()
 	// Set global cache var
 	SetupBusinessLayer(t, k8s, *conf)
+	discovery := &fakeMeshDiscovery{
+		mesh: models.Mesh{
+			ControlPlanes: []models.ControlPlane{{Cluster: &models.KubeCluster{Name: conf.KubernetesConfig.ClusterName}, Status: kubernetes.ComponentHealthy}},
+		},
+	}
+	WithDiscovery(discovery)
 
 	clients := make(map[string]kubernetes.ClientInterface)
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
 
-	icsl, err := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	icsl, err := iss.GetStatus(context.TODO())
 	assert.NoError(err)
 
 	// Two istio components are not found or unhealthy
@@ -493,12 +494,18 @@ func TestNonDefaults(t *testing.T) {
 
 	// Set global cache var
 	SetupBusinessLayer(t, k8s, *conf)
+	discovery := &fakeMeshDiscovery{
+		mesh: models.Mesh{
+			ControlPlanes: []models.ControlPlane{{Cluster: &models.KubeCluster{Name: conf.KubernetesConfig.ClusterName}, Status: kubernetes.ComponentHealthy}},
+		},
+	}
+	WithDiscovery(discovery)
 
 	clients := make(map[string]kubernetes.ClientInterface)
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
 
-	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	icsl, error := iss.GetStatus(context.TODO())
 	assert.NoError(error)
 
 	// Two istio components are not found or unhealthy
@@ -543,90 +550,29 @@ func TestIstiodNotReady(t *testing.T) {
 
 	// Set global cache var
 	SetupBusinessLayer(t, k8s, *conf)
-
-	clients := make(map[string]kubernetes.ClientInterface)
-	clients[conf.KubernetesConfig.ClusterName] = k8s
-	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
-
-	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
-	assert.NoError(error)
-
-	// Three istio components are unhealthy, not found or not ready
-	assertComponent(assert, icsl, "istio-ingressgateway", kubernetes.ComponentNotFound, false)
-	assertComponent(assert, icsl, "istio-egressgateway", kubernetes.ComponentUnhealthy, false)
-	assertComponent(assert, icsl, "istiod", kubernetes.ComponentNotReady, true)
-
-	// The rest of the components are healthy
-	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
-	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
-	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
-
-	// Terminating pods are not present
-	assertNotPresent(assert, icsl, "istiod-x3v1kn0l-terminating")
-	assertNotPresent(assert, icsl, "istiod-x3v1kn1l-terminating")
-
-	// Requests to AddOns have to be 1
-	assert.Equal(1, *grafanaCalls)
-	assert.Equal(1, *promCalls)
-}
-
-// Istiod pods are not reachable from kiali
-// Kiali should notify that in the Istio Component Status
-func TestIstiodUnreachable(t *testing.T) {
-	assert := assert.New(t)
-
-	objects := []runtime.Object{
-		fakeDeploymentWithStatus("istio-egressgateway", map[string]string{"app": "istio-egressgateway", "istio": "egressgateway"}, unhealthyStatus),
-		fakeDeploymentWithStatus("istiod", map[string]string{"app": "istiod", "istio": "pilot"}, healthyStatus),
-	}
-
-	var istioStatus kubernetes.IstioComponentStatus
-	for _, pod := range healthyIstiods() {
-		// Only running pods are considered healthy.
-		if pod.Status.Phase == v1.PodRunning && pod.Labels["app"] == "istiod" {
-			istioStatus = append(istioStatus, kubernetes.ComponentStatus{
-				Name:   pod.Name,
-				Status: kubernetes.ComponentUnreachable,
-				IsCore: true,
-			})
-		}
-	}
-	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objects, false, false)
-
-	conf := config.Get()
-	conf.IstioLabels.AppLabelName = "app.kubernetes.io/name"
-	conf.ExternalServices.Istio.ComponentStatuses = config.ComponentStatuses{
-		Enabled: true,
-		Components: []config.ComponentStatus{
-			{AppLabel: "istiod", IsCore: true},
-			{AppLabel: "istio-egressgateway", IsCore: false},
-			{AppLabel: "istio-ingressgateway", IsCore: false},
+	discovery := &fakeMeshDiscovery{
+		mesh: models.Mesh{
+			ControlPlanes: []models.ControlPlane{{Cluster: &models.KubeCluster{Name: conf.KubernetesConfig.ClusterName}, Status: ""}},
 		},
 	}
-	config.Set(conf)
-
-	// Set global cache var
-	SetupBusinessLayer(t, k8s, *conf)
-	WithControlPlaneMonitor(&FakeControlPlaneMonitor{status: istioStatus})
+	WithDiscovery(discovery)
 
 	clients := make(map[string]kubernetes.ClientInterface)
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
 
-	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	icsl, error := iss.GetStatus(context.TODO())
 	assert.NoError(error)
 
-	// Four istio components are unhealthy, not found or not ready
+	// Three istio components are unhealthy, not found or not ready
 	assertComponent(assert, icsl, "istio-ingressgateway", kubernetes.ComponentNotFound, false)
 	assertComponent(assert, icsl, "istio-egressgateway", kubernetes.ComponentUnhealthy, false)
-	assertComponent(assert, icsl, "istiod-x3v1kn0l-running", kubernetes.ComponentUnreachable, true)
-	assertComponent(assert, icsl, "istiod-x3v1kn1l-running", kubernetes.ComponentUnreachable, true)
+	assertComponent(assert, icsl, "istiod", kubernetes.ComponentNotReady, true)
 
 	// The rest of the components are healthy
 	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
 	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
 	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
-	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
 
 	// Terminating pods are not present
 	assertNotPresent(assert, icsl, "istiod-x3v1kn0l-terminating")
@@ -668,12 +614,18 @@ func TestCustomizedAppLabel(t *testing.T) {
 
 	// Set global cache var
 	SetupBusinessLayer(t, k8s, *conf)
+	discovery := &fakeMeshDiscovery{
+		mesh: models.Mesh{
+			ControlPlanes: []models.ControlPlane{{Cluster: &models.KubeCluster{Name: conf.KubernetesConfig.ClusterName}, Status: kubernetes.ComponentHealthy}},
+		},
+	}
+	WithDiscovery(discovery)
 
 	clients := make(map[string]kubernetes.ClientInterface)
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
 
-	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	icsl, error := iss.GetStatus(context.TODO())
 	assert.NoError(error)
 
 	// Two istio components are not found or unhealthy
@@ -722,12 +674,18 @@ func TestDaemonSetComponentHealthy(t *testing.T) {
 
 	// Set global cache var
 	SetupBusinessLayer(t, k8s, *conf)
+	discovery := &fakeMeshDiscovery{
+		mesh: models.Mesh{
+			ControlPlanes: []models.ControlPlane{{Cluster: &models.KubeCluster{Name: conf.KubernetesConfig.ClusterName}, Status: kubernetes.ComponentHealthy}},
+		},
+	}
+	WithDiscovery(discovery)
 
 	clients := make(map[string]kubernetes.ClientInterface)
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
 
-	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	icsl, error := iss.GetStatus(context.TODO())
 	assert.NoError(error)
 
 	// One istio components is unhealthy
@@ -772,12 +730,18 @@ func TestDaemonSetComponentUnhealthy(t *testing.T) {
 
 	// Set global cache var
 	SetupBusinessLayer(t, k8s, *conf)
+	discovery := &fakeMeshDiscovery{
+		mesh: models.Mesh{
+			ControlPlanes: []models.ControlPlane{{Cluster: &models.KubeCluster{Name: conf.KubernetesConfig.ClusterName}, Status: kubernetes.ComponentHealthy}},
+		},
+	}
+	WithDiscovery(discovery)
 
 	clients := make(map[string]kubernetes.ClientInterface)
 	clients[conf.KubernetesConfig.ClusterName] = k8s
 	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
 
-	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
+	icsl, error := iss.GetStatus(context.TODO())
 	assert.NoError(error)
 
 	// Two istio components are unhealthy
@@ -831,27 +795,6 @@ func mockFailingJaeger() tracing.ClientInterface {
 	return j
 }
 
-// Setup K8S api call to fetch Pods
-func mockDeploymentCall(objects []runtime.Object, isIstioReachable bool) {
-	var istioStatus kubernetes.IstioComponentStatus
-	if !isIstioReachable {
-		for _, obj := range objects {
-			if pod, isPod := obj.(*v1.Pod); isPod {
-				// Only running pods are considered healthy.
-				if pod.Status.Phase == v1.PodRunning && pod.Labels["app"] == "istiod" {
-					istioStatus = append(istioStatus, kubernetes.ComponentStatus{
-						Name:   pod.Name,
-						Status: kubernetes.ComponentUnreachable,
-						IsCore: true,
-					})
-				}
-			}
-		}
-	}
-
-	WithControlPlaneMonitor(&FakeControlPlaneMonitor{status: istioStatus})
-}
-
 func fakeDeploymentWithStatus(name string, labels map[string]string, status apps_v1.DeploymentStatus) *apps_v1.Deployment {
 	return &apps_v1.Deployment{
 		ObjectMeta: meta_v1.ObjectMeta{
diff --git a/handlers/mesh_test.go b/handlers/mesh_test.go
index 2d130553a..ee07e3873 100644
--- a/handlers/mesh_test.go
+++ b/handlers/mesh_test.go
@@ -7,6 +7,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/require"
+	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/tools/clientcmd/api"
@@ -40,11 +41,39 @@ func TestGetMeshGraph(t *testing.T) {
 	require := require.New(t)
 
 	conf := config.NewConfig()
+	conf.KubernetesConfig.ClusterName = "Kubernetes"
 	kubernetes.SetConfig(t, *conf)
 
 	clients := map[string]kubernetes.ClientInterface{
 		conf.KubernetesConfig.ClusterName: kubetest.NewFakeK8sClient(
 			&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: conf.IstioNamespace}},
+			// Ideally we wouldn't need to set all this stuff up here but there's not a good way
+			// mock out the business.IstioStatus service since it's a struct.
+			&appsv1.Deployment{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "istiod",
+					Namespace: conf.IstioNamespace,
+					Labels: map[string]string{
+						"app":          "istiod",
+						"istio.io/rev": "default",
+					},
+				},
+				Spec: appsv1.DeploymentSpec{
+					Template: corev1.PodTemplateSpec{
+						ObjectMeta: metav1.ObjectMeta{
+							Labels: map[string]string{"app": "istiod", "istio.io/rev": "default"},
+						},
+					},
+				},
+			},
+			&corev1.ConfigMap{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "istio",
+					Namespace: conf.IstioNamespace,
+					Labels:    map[string]string{"istio.io/rev": "default"},
+				},
+				Data: map[string]string{"mesh": ""},
+			},
 		),
 	}
 	cf := kubetest.NewFakeClientFactory(conf, clients)
diff --git a/istio/discovery_test.go b/istio/discovery_test.go
index 234865b11..d04f9fc39 100644
--- a/istio/discovery_test.go
+++ b/istio/discovery_test.go
@@ -2,6 +2,11 @@ package istio_test
 
 import (
 	"context"
+	"fmt"
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
 	"slices"
 	"strings"
 	"testing"
@@ -10,6 +15,7 @@ import (
 	"github.com/stretchr/testify/require"
 	apps_v1 "k8s.io/api/apps/v1"
 	core_v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/rest"
@@ -973,3 +979,447 @@ trustDomain: cluster.local
 	require.False(discovery.IsRemoteCluster(context.Background(), "east"))
 	require.True(discovery.IsRemoteCluster(context.Background(), "non-existant"))
 }
+
+func TestIstiodResourceThresholds(t *testing.T) {
+	conf := config.NewConfig()
+	istiodAppLabels := map[string]string{
+		"app":                     "istiod",
+		models.IstioRevisionLabel: "default",
+	}
+
+	testCases := map[string]struct {
+		istiodConatiner core_v1.Container
+		istiodMeta      v1.ObjectMeta
+		expected        *models.IstiodThresholds
+		expectedErr     error
+	}{
+		"istiod with no limits": {
+			istiodMeta: v1.ObjectMeta{
+				Name:      "istiod",
+				Namespace: "istio-system",
+				Labels:    istiodAppLabels,
+			},
+			istiodConatiner: core_v1.Container{
+				Name: "istiod",
+			},
+			expected: &models.IstiodThresholds{
+				CPU:    0,
+				Memory: 0,
+			},
+		},
+		"istiod with limits": {
+			istiodMeta: v1.ObjectMeta{
+				Name:      "istiod",
+				Namespace: "istio-system",
+				Labels:    istiodAppLabels,
+			},
+			istiodConatiner: core_v1.Container{
+				Name: "istiod",
+				Resources: core_v1.ResourceRequirements{
+					Limits: core_v1.ResourceList{
+						core_v1.ResourceCPU:    resource.MustParse("1000m"),
+						core_v1.ResourceMemory: resource.MustParse("1G"),
+					},
+				},
+			},
+			expected: &models.IstiodThresholds{
+				CPU:    1,
+				Memory: 1000,
+			},
+		},
+		"istiod with binary limits": {
+			istiodMeta: v1.ObjectMeta{
+				Name:      "istiod",
+				Namespace: "istio-system",
+				Labels:    istiodAppLabels,
+			},
+			istiodConatiner: core_v1.Container{
+				Name: "istiod",
+				Resources: core_v1.ResourceRequirements{
+					Limits: core_v1.ResourceList{
+						core_v1.ResourceCPU:    resource.MustParse("14m"),
+						core_v1.ResourceMemory: resource.MustParse("1Gi"),
+					},
+				},
+			},
+			expected: &models.IstiodThresholds{
+				CPU: 0.014,
+				// Rounded
+				Memory: 1074,
+			},
+		},
+		"istiod with cpu numeral": {
+			istiodMeta: v1.ObjectMeta{
+				Name:      "istiod",
+				Namespace: "istio-system",
+				Labels:    istiodAppLabels,
+			},
+			istiodConatiner: core_v1.Container{
+				Name: "istiod",
+				Resources: core_v1.ResourceRequirements{
+					Limits: core_v1.ResourceList{
+						core_v1.ResourceCPU: resource.MustParse("1.5"),
+					},
+				},
+			},
+			expected: &models.IstiodThresholds{
+				CPU: 1.5,
+			},
+		},
+		"istiod with only cpu limits": {
+			istiodMeta: v1.ObjectMeta{
+				Name:      "istiod",
+				Namespace: "istio-system",
+				Labels:    istiodAppLabels,
+			},
+			istiodConatiner: core_v1.Container{
+				Name: "istiod",
+				Resources: core_v1.ResourceRequirements{
+					Limits: core_v1.ResourceList{
+						core_v1.ResourceCPU: resource.MustParse("1000m"),
+					},
+				},
+			},
+			expected: &models.IstiodThresholds{
+				CPU: 1,
+			},
+		},
+		"istiod with only memory limits": {
+			istiodMeta: v1.ObjectMeta{
+				Name:      "istiod",
+				Namespace: "istio-system",
+				Labels:    istiodAppLabels,
+			},
+			istiodConatiner: core_v1.Container{
+				Name: "istiod",
+				Resources: core_v1.ResourceRequirements{
+					Limits: core_v1.ResourceList{
+						core_v1.ResourceMemory: resource.MustParse("1G"),
+					},
+				},
+			},
+			expected: &models.IstiodThresholds{
+				Memory: 1000,
+			},
+		},
+		"istiod in a different namespace": {
+			istiodMeta: v1.ObjectMeta{
+				Name:      "istiod",
+				Namespace: "istio-system-2",
+				Labels:    istiodAppLabels,
+			},
+			istiodConatiner: core_v1.Container{
+				Name: "istiod",
+				Resources: core_v1.ResourceRequirements{
+					Limits: core_v1.ResourceList{
+						core_v1.ResourceMemory: resource.MustParse("1G"),
+					},
+				},
+			},
+			expectedErr: fmt.Errorf("istiod deployment not found"),
+		},
+		"Missing limits": {
+			istiodMeta: v1.ObjectMeta{
+				Name:      "istiod",
+				Namespace: "istio-system",
+				Labels:    istiodAppLabels,
+			},
+			istiodConatiner: core_v1.Container{
+				Name:      "istiod",
+				Resources: core_v1.ResourceRequirements{},
+			},
+			expected: &models.IstiodThresholds{},
+		},
+		"Missing resources": {
+			istiodMeta: v1.ObjectMeta{
+				Name:      "istiod",
+				Namespace: "istio-system",
+				Labels:    istiodAppLabels,
+			},
+			istiodConatiner: core_v1.Container{
+				Name: "istiod",
+			},
+			expected: &models.IstiodThresholds{},
+		},
+	}
+
+	for name, testCase := range testCases {
+		t.Run(name, func(t *testing.T) {
+			require := require.New(t)
+			istiodDeployment := &apps_v1.Deployment{
+				ObjectMeta: testCase.istiodMeta,
+				Spec: apps_v1.DeploymentSpec{
+					Template: core_v1.PodTemplateSpec{
+						Spec: core_v1.PodSpec{
+							Containers: []core_v1.Container{
+								testCase.istiodConatiner,
+							},
+						},
+					},
+				},
+			}
+			k8s := kubetest.NewFakeK8sClient(
+				&core_v1.Namespace{ObjectMeta: v1.ObjectMeta{Name: "istio-system"}},
+				istiodDeployment,
+				&core_v1.ConfigMap{
+					ObjectMeta: v1.ObjectMeta{
+						Name:      "istio",
+						Namespace: "istio-system",
+						Labels:    map[string]string{models.IstioRevisionLabel: "default"},
+					},
+					Data: map[string]string{"mesh": ""},
+				},
+			)
+
+			clients := map[string]kubernetes.ClientInterface{conf.KubernetesConfig.ClusterName: k8s}
+			cache := cache.NewTestingCacheWithClients(t, clients, *conf)
+			discovery := istio.NewDiscovery(clients, cache, conf)
+
+			mesh, err := discovery.Mesh(context.Background())
+			if testCase.expectedErr != nil {
+				require.Error(err)
+				// End the test early if we expect an error.
+				return
+			}
+			require.NoError(err)
+			require.Len(mesh.ControlPlanes, 1)
+			require.Equal(testCase.expected, mesh.ControlPlanes[0].Thresholds)
+		})
+	}
+}
+
+func istiodTestServer(t *testing.T) *httptest.Server {
+	t.Helper()
+	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		var file string
+		switch r.URL.Path {
+		case "/debug/registryz":
+			file = "../tests/data/registry/registry-registryz.json"
+		case "/debug/syncz":
+			file = "../tests/data/registry/registry-syncz.json"
+		case "/debug":
+			w.WriteHeader(http.StatusOK)
+			return
+		case "/ready":
+			w.WriteHeader(http.StatusOK)
+			return
+		default:
+			w.WriteHeader(http.StatusInternalServerError)
+			t.Fatalf("Unexpected request path: %s", r.URL.Path)
+			return
+		}
+		if _, err := w.Write(kubernetes.ReadFile(t, file)); err != nil {
+			t.Fatalf("Error writing response: %s", err)
+		}
+	}))
+	t.Cleanup(testServer.Close)
+	return testServer
+}
+
+func fakeIstioConfigMap(revision string) *core_v1.ConfigMap {
+	const configMapData = `accessLogFile: /dev/stdout
+enableAutoMtls: true
+rootNamespace: istio-system
+trustDomain: cluster.local
+`
+	configMapName := "istio"
+	if revision != "default" {
+		configMapName = "istio-" + revision
+	}
+	return &core_v1.ConfigMap{
+		ObjectMeta: v1.ObjectMeta{
+			Name:      configMapName,
+			Namespace: "istio-system",
+			Labels:    map[string]string{models.IstioRevisionLabel: revision},
+		},
+		Data: map[string]string{"mesh": configMapData},
+	}
+}
+
+type fakeForwarder struct {
+	kubernetes.ClientInterface
+	testURL string
+}
+
+func (f *fakeForwarder) ForwardGetRequest(namespace, podName string, destinationPort int, path string) ([]byte, error) {
+	url, _ := url.JoinPath(f.testURL, path)
+	resp, err := http.Get(url)
+	if err != nil {
+		return nil, err
+	}
+
+	defer resp.Body.Close()
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+
+	return body, nil
+}
+
+func runningIstiodPod(revision string) *core_v1.Pod {
+	return &core_v1.Pod{
+		ObjectMeta: v1.ObjectMeta{
+			Name:      "istiod-123" + "-" + revision,
+			Namespace: "istio-system",
+			Labels: map[string]string{
+				"app":                     "istiod",
+				models.IstioRevisionLabel: revision,
+			},
+		},
+		Status: core_v1.PodStatus{
+			Phase: core_v1.PodRunning,
+		},
+	}
+}
+
+func TestCanConnectToIstiod(t *testing.T) {
+	assert := assert.New(t)
+	require := require.New(t)
+
+	conf := config.NewConfig()
+
+	testServer := istiodTestServer(t)
+	fakeForwarder := &fakeForwarder{
+		ClientInterface: kubetest.NewFakeK8sClient(
+			runningIstiodPod("default"),
+			fakeIstiodDeployment(conf.KubernetesConfig.ClusterName, false),
+			fakeIstioConfigMap("default"),
+			&core_v1.Namespace{ObjectMeta: v1.ObjectMeta{Name: "istio-system"}},
+		),
+		testURL: testServer.URL,
+	}
+
+	k8sclients := make(map[string]kubernetes.ClientInterface)
+	k8sclients[conf.KubernetesConfig.ClusterName] = fakeForwarder
+	cache := cache.NewTestingCacheWithClients(t, k8sclients, *conf)
+	discovery := istio.NewDiscovery(k8sclients, cache, conf)
+
+	mesh, err := discovery.Mesh(context.Background())
+	require.NoError(err)
+	require.Len(mesh.ControlPlanes, 1)
+	assert.Equal(kubernetes.ComponentHealthy, mesh.ControlPlanes[0].Status)
+}
+
+type badForwarder struct {
+	kubernetes.ClientInterface
+}
+
+func (f *badForwarder) ForwardGetRequest(namespace, podName string, destinationPort int, path string) ([]byte, error) {
+	return nil, fmt.Errorf("unable to forward request")
+}
+
+func TestCanConnectToUnreachableIstiod(t *testing.T) {
+	assert := assert.New(t)
+	require := require.New(t)
+
+	conf := config.NewConfig()
+
+	fakeForwarder := &badForwarder{
+		ClientInterface: kubetest.NewFakeK8sClient(
+			runningIstiodPod("default"),
+			fakeIstiodDeployment(conf.KubernetesConfig.ClusterName, false),
+			fakeIstioConfigMap("default"),
+			&core_v1.Namespace{ObjectMeta: v1.ObjectMeta{Name: "istio-system"}},
+		),
+	}
+
+	k8sclients := make(map[string]kubernetes.ClientInterface)
+	k8sclients[conf.KubernetesConfig.ClusterName] = fakeForwarder
+	cache := cache.NewTestingCacheWithClients(t, k8sclients, *conf)
+	discovery := istio.NewDiscovery(k8sclients, cache, conf)
+
+	mesh, err := discovery.Mesh(context.Background())
+	require.NoError(err)
+	require.Len(mesh.ControlPlanes, 1)
+	assert.Equal(kubernetes.ComponentUnreachable, mesh.ControlPlanes[0].Status)
+}
+
+func fakeIstiodWithRevision(cluster string, revision string, manageExternal bool) *apps_v1.Deployment {
+	deployment := fakeIstiodDeployment(cluster, manageExternal)
+	deployment.Labels[models.IstioRevisionLabel] = revision
+	deployment.Name = "istiod-" + revision
+	return deployment
+}
+
+func TestUpdateStatusMultipleRevsWithoutHealthyPods(t *testing.T) {
+	require := require.New(t)
+
+	conf := config.NewConfig()
+	conf.KubernetesConfig.ClusterName = "Kubernetes"
+
+	defaultIstiod := fakeIstiodDeployment(conf.KubernetesConfig.ClusterName, false)
+	istiod_1_19 := fakeIstiodWithRevision(conf.KubernetesConfig.ClusterName, "1-19-0", false)
+
+	k8s := kubetest.NewFakeK8sClient(
+		&core_v1.Namespace{ObjectMeta: v1.ObjectMeta{Name: "istio-system"}},
+		defaultIstiod,
+		istiod_1_19,
+		fakeIstioConfigMap("default"),
+		fakeIstioConfigMap("1-19-0"),
+	)
+	// RefreshIstioCache relies on this being set.
+	k8s.KubeClusterInfo.Name = conf.KubernetesConfig.ClusterName
+
+	testServer := istiodTestServer(t)
+	fakeForwarder := &fakeForwarder{
+		ClientInterface: k8s,
+		testURL:         testServer.URL,
+	}
+
+	k8sclients := make(map[string]kubernetes.ClientInterface)
+	k8sclients[conf.KubernetesConfig.ClusterName] = fakeForwarder
+	cache := cache.NewTestingCacheWithClients(t, k8sclients, *conf)
+	discovery := istio.NewDiscovery(k8sclients, cache, conf)
+
+	mesh, err := discovery.Mesh(context.Background())
+	require.NoError(err)
+	require.Len(mesh.ControlPlanes, 2)
+
+	require.Equal("", mesh.ControlPlanes[0].Status)
+	require.Equal("", mesh.ControlPlanes[1].Status)
+}
+
+func TestUpdateStatusMultipleHealthyRevs(t *testing.T) {
+	require := require.New(t)
+
+	conf := config.NewConfig()
+	conf.KubernetesConfig.ClusterName = "Kubernetes"
+
+	defaultIstiod := fakeIstiodDeployment(conf.KubernetesConfig.ClusterName, false)
+	istiod_1_19 := fakeIstiodWithRevision(conf.KubernetesConfig.ClusterName, "1-19-0", false)
+	defaultPod := runningIstiodPod("default")
+	defaultPod.Labels[models.IstioRevisionLabel] = "default"
+	istiod_1_19_pod := runningIstiodPod("1-19-0")
+	istiod_1_19_pod.Labels[models.IstioRevisionLabel] = "1-19-0"
+
+	k8s := kubetest.NewFakeK8sClient(
+		&core_v1.Namespace{ObjectMeta: v1.ObjectMeta{Name: "istio-system"}},
+		defaultIstiod,
+		istiod_1_19,
+		fakeIstioConfigMap("default"),
+		fakeIstioConfigMap("1-19-0"),
+		defaultPod,
+		istiod_1_19_pod,
+	)
+	// RefreshIstioCache relies on this being set.
+	k8s.KubeClusterInfo.Name = conf.KubernetesConfig.ClusterName
+
+	testServer := istiodTestServer(t)
+	fakeForwarder := &fakeForwarder{
+		ClientInterface: k8s,
+		testURL:         testServer.URL,
+	}
+
+	k8sclients := make(map[string]kubernetes.ClientInterface)
+	k8sclients[conf.KubernetesConfig.ClusterName] = fakeForwarder
+	cache := cache.NewTestingCacheWithClients(t, k8sclients, *conf)
+	discovery := istio.NewDiscovery(k8sclients, cache, conf)
+
+	mesh, err := discovery.Mesh(context.Background())
+	require.NoError(err)
+	require.Len(mesh.ControlPlanes, 2)
+
+	require.Equal(kubernetes.ComponentHealthy, mesh.ControlPlanes[0].Status)
+	require.Equal(kubernetes.ComponentHealthy, mesh.ControlPlanes[1].Status)
+}
diff --git a/istio/version_test.go b/istio/version_test.go
index 39dec4de6..cd023fb56 100644
--- a/istio/version_test.go
+++ b/istio/version_test.go
@@ -5,7 +5,7 @@ import (
 	"io"
 	"net/http"
 	"net/http/httptest"
-	"strings"
+	"net/url"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -145,14 +145,9 @@ type fakeForwarder struct {
 	testURL string
 }
 
-func joinURL(base, path string) string {
-	base = strings.TrimSuffix(base, "/")
-	path = strings.TrimPrefix(path, "/")
-	return base + "/" + path
-}
-
 func (f *fakeForwarder) ForwardGetRequest(namespace, podName string, destinationPort int, path string) ([]byte, error) {
-	resp, err := http.Get(joinURL(f.testURL, path))
+	url, _ := url.JoinPath(f.testURL, path)
+	resp, err := http.Get(url)
 	if err != nil {
 		return nil, err
 	}
diff --git a/kubernetes/istio_test.go b/kubernetes/istio_test.go
index 7d9f1a0a1..d775e56b3 100644
--- a/kubernetes/istio_test.go
+++ b/kubernetes/istio_test.go
@@ -25,6 +25,7 @@ func TestGetClusterInfoFromIstiod(t *testing.T) {
 			ObjectMeta: meta_v1.ObjectMeta{
 				Name:      "istiod",
 				Namespace: "istio-system",
+				Labels:    map[string]string{"app": "istiod"},
 			},
 			Spec: apps_v1.DeploymentSpec{
 				Template: core_v1.PodTemplateSpec{
@@ -37,10 +38,6 @@ func TestGetClusterInfoFromIstiod(t *testing.T) {
 										Name:  "CLUSTER_ID",
 										Value: "east",
 									},
-									{
-										Name:  "PILOT_SCOPE_GATEWAY_TO_NAMESPACE",
-										Value: "true",
-									},
 								},
 							},
 						},
@@ -49,11 +46,10 @@ func TestGetClusterInfoFromIstiod(t *testing.T) {
 			},
 		},
 	)
-	clusterID, pilotScope, err := kubernetes.ClusterInfoFromIstiod(*conf, k8s)
+	clusterID, err := kubernetes.ClusterNameFromIstiod(*conf, k8s)
 	require.NoError(err)
 
 	assert.Equal("east", clusterID)
-	assert.True(pilotScope)
 }
 
 func TestGetClusterInfoFromIstiodFails(t *testing.T) {
@@ -81,6 +77,112 @@ func TestGetClusterInfoFromIstiodFails(t *testing.T) {
 			},
 		},
 	)
-	_, _, err := kubernetes.ClusterInfoFromIstiod(*conf, k8s)
+	_, err := kubernetes.ClusterNameFromIstiod(*conf, k8s)
 	require.Error(err)
 }
+
+func TestClusterNameFromIstiodUsesConfigWhenSet(t *testing.T) {
+	require := require.New(t)
+
+	conf := config.NewConfig()
+	conf.ExternalServices.Istio.IstiodDeploymentName = "different-istiod"
+	k8s := kubetest.NewFakeK8sClient(
+		&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}},
+		&apps_v1.Deployment{
+			ObjectMeta: meta_v1.ObjectMeta{
+				Name:      "istiod",
+				Namespace: "istio-system",
+			},
+			Spec: apps_v1.DeploymentSpec{
+				Template: core_v1.PodTemplateSpec{
+					Spec: core_v1.PodSpec{
+						Containers: []core_v1.Container{
+							{
+								Name: "istiod",
+								// In reality both should have the same cluster id but want to test that the config is used.
+								Env: []core_v1.EnvVar{{Name: "CLUSTER_ID", Value: "east"}},
+							},
+						},
+					},
+				},
+			},
+		},
+		&apps_v1.Deployment{
+			ObjectMeta: meta_v1.ObjectMeta{
+				Name:      "different-istiod",
+				Namespace: "istio-system",
+			},
+			Spec: apps_v1.DeploymentSpec{
+				Template: core_v1.PodTemplateSpec{
+					Spec: core_v1.PodSpec{
+						Containers: []core_v1.Container{
+							{
+								Name: "istiod",
+								Env:  []core_v1.EnvVar{{Name: "CLUSTER_ID", Value: "west"}},
+							},
+						},
+					},
+				},
+			},
+		},
+	)
+
+	clusterName, err := kubernetes.ClusterNameFromIstiod(*conf, k8s)
+	require.NoError(err)
+
+	require.Equal("west", clusterName)
+}
+
+func TestClusterNameFromIstiodResolvesClusterWithoutConfig(t *testing.T) {
+	require := require.New(t)
+
+	conf := config.NewConfig()
+	conf.ExternalServices.Istio.IstiodDeploymentName = ""
+	k8s := kubetest.NewFakeK8sClient(
+		&core_v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}},
+		&apps_v1.Deployment{
+			ObjectMeta: meta_v1.ObjectMeta{
+				Name:      "istiod",
+				Namespace: "istio-system",
+				Labels:    map[string]string{"app": "istiod"},
+			},
+			Spec: apps_v1.DeploymentSpec{
+				Template: core_v1.PodTemplateSpec{
+					Spec: core_v1.PodSpec{
+						Containers: []core_v1.Container{
+							{
+								Name: "istiod",
+								// In reality both should have the same cluster id but want to test that the config is used.
+								Env: []core_v1.EnvVar{{Name: "CLUSTER_ID", Value: "east"}},
+							},
+						},
+					},
+				},
+			},
+		},
+		&apps_v1.Deployment{
+			ObjectMeta: meta_v1.ObjectMeta{
+				Name:      "different-istiod",
+				Namespace: "istio-system",
+				Labels:    map[string]string{"app": "istiod"},
+			},
+			Spec: apps_v1.DeploymentSpec{
+				Template: core_v1.PodTemplateSpec{
+					Spec: core_v1.PodSpec{
+						Containers: []core_v1.Container{
+							{
+								Name: "istiod",
+								Env:  []core_v1.EnvVar{{Name: "CLUSTER_ID", Value: "west"}},
+							},
+						},
+					},
+				},
+			},
+		},
+	)
+
+	clusterName, err := kubernetes.ClusterNameFromIstiod(*conf, k8s)
+	require.NoError(err)
+
+	require.Equal("west", clusterName)
+}
diff --git a/kubernetes/kubetest/mock_kubernetes.go b/kubernetes/kubetest/mock_kubernetes.go
index a5858109d..3848ea9e5 100644
--- a/kubernetes/kubetest/mock_kubernetes.go
+++ b/kubernetes/kubetest/mock_kubernetes.go
@@ -8,6 +8,7 @@ import (
 	auth_v1 "k8s.io/api/authorization/v1"
 	batch_v1 "k8s.io/api/batch/v1"
 	core_v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 )
 
@@ -45,7 +46,7 @@ func (o *K8SClientMock) GetDeployment(namespace string, name string) (*apps_v1.D
 	return args.Get(0).(*apps_v1.Deployment), args.Error(1)
 }
 
-func (o *K8SClientMock) GetDeployments(namespace string) ([]apps_v1.Deployment, error) {
+func (o *K8SClientMock) GetDeployments(namespace string, opts metav1.ListOptions) ([]apps_v1.Deployment, error) {
 	args := o.Called(namespace)
 	return args.Get(0).([]apps_v1.Deployment), args.Error(1)
 }
diff --git a/mesh/api/api_test.go b/mesh/api/api_test.go
index 6d2316771..e5d7c0c46 100644
--- a/mesh/api/api_test.go
+++ b/mesh/api/api_test.go
@@ -188,7 +188,7 @@ func mockMeshGraph(t *testing.T) (*mesh.AppenderGlobalInfo, error) {
 type fakeMeshStatusGetter struct{}
 
 // mock GetStatus function to obtain fake graph component status
-func (f *fakeMeshStatusGetter) GetStatus(ctx context.Context, cluster string) (kubernetes.IstioComponentStatus, error) {
+func (f *fakeMeshStatusGetter) GetStatus(ctx context.Context) (kubernetes.IstioComponentStatus, error) {
 	return kubernetes.IstioComponentStatus{
 		kubernetes.ComponentStatus{
 			Name:   "istiod",
diff --git a/mesh/api/testdata/test_mesh_graph.expected b/mesh/api/testdata/test_mesh_graph.expected
index 860400d50..7ec396239 100644
--- a/mesh/api/testdata/test_mesh_graph.expected
+++ b/mesh/api/testdata/test_mesh_graph.expected
@@ -267,7 +267,7 @@
             "IstioInjectionAnnotation": "sidecar.istio.io/inject",
             "IstioSidecarInjectorConfigMapName": "istio-sidecar-injector",
             "IstioSidecarAnnotation": "sidecar.istio.io/status",
-            "IstiodDeploymentName": "istiod",
+            "IstiodDeploymentName": "",
             "IstiodPodMonitoringPort": 15014,
             "IstiodPollingIntervalSeconds": 20,
             "Registry": null,
@@ -288,9 +288,6 @@
           "healthData": "Healthy",
           "infraData": {
             "config": {
-              "OutboundTrafficPolicy": {
-                "mode": ""
-              },
               "Network": "",
               "DisableMixerHttpReports": false,
               "DiscoverySelectors": null,
@@ -301,9 +298,16 @@
               "defaultConfig": {
                 "MeshId": ""
               },
+              "OutboundTrafficPolicy": {
+                "mode": "ALLOW_ANY"
+              },
               "TrustDomain": "cluster.local"
             },
-            "revision": "default"
+            "revision": "default",
+            "thresholds": {
+              "memory": 0,
+              "cpu": 0
+            }
           },
           "version": "Unknown"
         }