github.com/kiali/kiali@v1.84.0/business/istio_status_test.go (about)

     1  package business
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"sync"
     9  	"testing"
    10  
    11  	"github.com/gorilla/mux"
    12  	osproject_v1 "github.com/openshift/api/project/v1"
    13  	"github.com/stretchr/testify/assert"
    14  	apps_v1 "k8s.io/api/apps/v1"
    15  	v1 "k8s.io/api/core/v1"
    16  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  
    19  	"github.com/kiali/kiali/config"
    20  	"github.com/kiali/kiali/kubernetes"
    21  	"github.com/kiali/kiali/kubernetes/kubetest"
    22  	"github.com/kiali/kiali/log"
    23  	"github.com/kiali/kiali/models"
    24  	"github.com/kiali/kiali/tracing"
    25  	"github.com/kiali/kiali/tracing/tracingtest"
    26  )
    27  
    28  type addOnsSetup struct {
    29  	Url        string
    30  	StatusCode int
    31  	CallCount  *int
    32  }
    33  
    34  var notReadyStatus = apps_v1.DeploymentStatus{
    35  	Replicas:            0,
    36  	AvailableReplicas:   0,
    37  	UnavailableReplicas: 0,
    38  }
    39  
    40  var healthyStatus = apps_v1.DeploymentStatus{
    41  	Replicas:            2,
    42  	AvailableReplicas:   2,
    43  	UnavailableReplicas: 0,
    44  }
    45  
    46  var unhealthyStatus = apps_v1.DeploymentStatus{
    47  	Replicas:            2,
    48  	AvailableReplicas:   1,
    49  	UnavailableReplicas: 1,
    50  }
    51  
    52  var healthyDaemonSetStatus = apps_v1.DaemonSetStatus{
    53  	DesiredNumberScheduled: 2,
    54  	CurrentNumberScheduled: 2,
    55  	NumberAvailable:        2,
    56  	NumberUnavailable:      0,
    57  }
    58  
    59  var unhealthyDaemonSetStatus = apps_v1.DaemonSetStatus{
    60  	DesiredNumberScheduled: 2,
    61  	CurrentNumberScheduled: 2,
    62  	NumberAvailable:        1,
    63  	NumberUnavailable:      1,
    64  }
    65  
    66  func TestComponentNotRunning(t *testing.T) {
    67  	assert := assert.New(t)
    68  
    69  	dss := []apps_v1.DeploymentStatus{
    70  		{
    71  			Replicas:            3,
    72  			AvailableReplicas:   2,
    73  			UnavailableReplicas: 1,
    74  		},
    75  		{
    76  			Replicas:            1,
    77  			AvailableReplicas:   0,
    78  			UnavailableReplicas: 0,
    79  		},
    80  	}
    81  
    82  	for _, ds := range dss {
    83  		d := fakeDeploymentWithStatus(
    84  			"istio-egressgateway",
    85  			map[string]string{"app": "istio-egressgateway", "istio": "egressgateway"},
    86  			ds,
    87  		)
    88  		wl := &models.Workload{}
    89  		wl.ParseDeployment(d)
    90  		assert.Equal(kubernetes.ComponentUnhealthy, GetWorkloadStatus(*wl))
    91  	}
    92  }
    93  
    94  func TestComponentRunning(t *testing.T) {
    95  	assert := assert.New(t)
    96  
    97  	d := fakeDeploymentWithStatus(
    98  		"istio-egressgateway",
    99  		map[string]string{"app": "istio-egressgateway"},
   100  		apps_v1.DeploymentStatus{
   101  			Replicas:            2,
   102  			AvailableReplicas:   2,
   103  			UnavailableReplicas: 0,
   104  		})
   105  
   106  	wl := &models.Workload{}
   107  	wl.ParseDeployment(d)
   108  
   109  	assert.Equal(kubernetes.ComponentHealthy, GetWorkloadStatus(*wl))
   110  }
   111  
   112  func TestComponentNamespaces(t *testing.T) {
   113  	a := assert.New(t)
   114  
   115  	conf := confWithComponentNamespaces()
   116  	config.Set(conf)
   117  
   118  	nss := getComponentNamespaces()
   119  
   120  	a.Contains(nss, "istio-system")
   121  	a.Contains(nss, "istio-admin")
   122  	a.Contains(nss, "ingress-egress")
   123  	a.Len(nss, 4)
   124  }
   125  
   126  func mockAddOnsCalls(t *testing.T, objects []runtime.Object, isIstioReachable bool, overrideAddonURLs bool) (kubernetes.ClientInterface, *int, *int) {
   127  	// Prepare the Call counts for each Addon
   128  	grafanaCalls, prometheusCalls := 0, 0
   129  
   130  	objects = append(objects, &osproject_v1.Project{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}})
   131  
   132  	// Mock k8s api calls
   133  	mockDeploymentCall(objects, isIstioReachable)
   134  	k8s := kubetest.NewFakeK8sClient(objects...)
   135  	k8s.OpenShift = true
   136  	routes := mockAddOnCalls(defaultAddOnCalls(&grafanaCalls, &prometheusCalls))
   137  	httpServer := mockServer(t, routes)
   138  
   139  	// Adapt the AddOns URLs to the mock Server
   140  	conf := addonAddMockUrls(httpServer.URL, config.NewConfig(), overrideAddonURLs)
   141  	config.Set(conf)
   142  
   143  	return k8s, &grafanaCalls, &prometheusCalls
   144  }
   145  
   146  func sampleIstioComponent() ([]runtime.Object, bool, bool) {
   147  	deployment := fakeDeploymentWithStatus(
   148  		"istio-egressgateway",
   149  		map[string]string{"app": "istio-egressgateway"},
   150  		apps_v1.DeploymentStatus{
   151  			Replicas:            2,
   152  			AvailableReplicas:   2,
   153  			UnavailableReplicas: 0,
   154  		})
   155  	objects := []runtime.Object{deployment}
   156  	for _, obj := range healthyIstiods() {
   157  		o := obj
   158  		objects = append(objects, &o)
   159  	}
   160  	return objects, true, false
   161  }
   162  
   163  func healthyIstiods() []v1.Pod {
   164  	return []v1.Pod{
   165  		fakePod("istiod-x3v1kn0l-running", "istio-system", "istiod", "Running"),
   166  		fakePod("istiod-x3v1kn1l-running", "istio-system", "istiod", "Running"),
   167  		fakePod("istiod-x3v1kn0l-terminating", "istio-system", "istiod", "Terminating"),
   168  		fakePod("istiod-x3v1kn1l-terminating", "istio-system", "istiod", "Terminating"),
   169  	}
   170  }
   171  
   172  func fakePod(name, namespace, appLabel, phase string) v1.Pod {
   173  	return v1.Pod{
   174  		ObjectMeta: meta_v1.ObjectMeta{
   175  			Name:      name,
   176  			Namespace: namespace,
   177  			Labels: map[string]string{
   178  				"app": appLabel,
   179  			},
   180  		},
   181  		Status: v1.PodStatus{
   182  			Phase: v1.PodPhase(phase),
   183  		},
   184  	}
   185  }
   186  
   187  func TestGrafanaWorking(t *testing.T) {
   188  	assert := assert.New(t)
   189  
   190  	objs, b1, b2 := sampleIstioComponent()
   191  	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objs, b1, b2)
   192  
   193  	conf := config.Get()
   194  
   195  	// Set global cache var
   196  	SetupBusinessLayer(t, k8s, *conf)
   197  
   198  	clients := make(map[string]kubernetes.ClientInterface)
   199  	clients[conf.KubernetesConfig.ClusterName] = k8s
   200  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   201  	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   202  	assert.NoError(error)
   203  
   204  	// Requests to AddOns have to be 1
   205  	assert.Equal(1, *grafanaCalls)
   206  	assert.Equal(1, *promCalls)
   207  
   208  	// All services are healthy
   209  	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
   210  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   211  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
   212  	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
   213  }
   214  
   215  func TestGrafanaDisabled(t *testing.T) {
   216  	assert := assert.New(t)
   217  
   218  	objects := []runtime.Object{
   219  		fakeDeploymentWithStatus(
   220  			"istio-egressgateway",
   221  			map[string]string{"app": "istio-egressgateway"},
   222  			apps_v1.DeploymentStatus{
   223  				Replicas:            2,
   224  				AvailableReplicas:   2,
   225  				UnavailableReplicas: 0,
   226  			}),
   227  	}
   228  	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objects, true, false)
   229  	// Disable Grafana
   230  	conf := config.Get()
   231  	conf.ExternalServices.Grafana.Enabled = false
   232  	config.Set(conf)
   233  
   234  	// Set global cache var
   235  	SetupBusinessLayer(t, k8s, *conf)
   236  
   237  	clients := make(map[string]kubernetes.ClientInterface)
   238  	clients[conf.KubernetesConfig.ClusterName] = k8s
   239  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   240  	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   241  	assert.NoError(error)
   242  
   243  	// No request performed to Grafana endpoint
   244  	assert.Zero(*grafanaCalls)
   245  
   246  	// Requests to Tracing and Prometheus performed once
   247  	assert.Equal(1, *promCalls)
   248  
   249  	// Grafana is disabled
   250  	assertNotPresent(assert, icsl, "grafana")
   251  
   252  	// Two Istio components are missing
   253  	assertComponent(assert, icsl, "istio-ingressgateway", kubernetes.ComponentNotFound, true)
   254  	assertComponent(assert, icsl, "istiod", kubernetes.ComponentNotFound, true)
   255  
   256  	// The rest of the components are healthy
   257  	assertComponent(assert, icsl, "istio-egressgateway", kubernetes.ComponentHealthy, false)
   258  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   259  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
   260  	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
   261  }
   262  
   263  func TestGrafanaNotWorking(t *testing.T) {
   264  	assert := assert.New(t)
   265  	grafanaCalls, prometheusCalls := 0, 0
   266  	objects := []runtime.Object{
   267  		fakeDeploymentWithStatus(
   268  			"istio-egressgateway",
   269  			map[string]string{"app": "istio-egressgateway"},
   270  			apps_v1.DeploymentStatus{
   271  				Replicas:            2,
   272  				AvailableReplicas:   2,
   273  				UnavailableReplicas: 0,
   274  			}),
   275  	}
   276  	objects = append(objects, &osproject_v1.Project{ObjectMeta: meta_v1.ObjectMeta{Name: "istio-system"}})
   277  	mockDeploymentCall(objects, true)
   278  	addOnsStetup := defaultAddOnCalls(&grafanaCalls, &prometheusCalls)
   279  	addOnsStetup["grafana"] = addOnsSetup{
   280  		Url:        "/grafana/mock",
   281  		StatusCode: 501,
   282  		CallCount:  &grafanaCalls,
   283  	}
   284  	routes := mockAddOnCalls(addOnsStetup)
   285  	httpServer := mockServer(t, routes)
   286  
   287  	// Adapt the AddOns URLs to the mock Server
   288  	conf := addonAddMockUrls(httpServer.URL, config.NewConfig(), false)
   289  	config.Set(conf)
   290  
   291  	k8s := kubetest.NewFakeK8sClient(objects...)
   292  	k8s.OpenShift = true
   293  
   294  	// Set global cache var
   295  	SetupBusinessLayer(t, k8s, *conf)
   296  
   297  	clients := make(map[string]kubernetes.ClientInterface)
   298  	clients[conf.KubernetesConfig.ClusterName] = k8s
   299  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   300  	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   301  	assert.NoError(error)
   302  
   303  	// Requests to AddOns have to be 1
   304  	assert.Equal(1, grafanaCalls)
   305  	assert.Equal(1, prometheusCalls)
   306  
   307  	// Grafana and two Istio comps missing
   308  	assertComponent(assert, icsl, "grafana", kubernetes.ComponentUnreachable, false)
   309  	assertComponent(assert, icsl, "istiod", kubernetes.ComponentNotFound, true)
   310  	assertComponent(assert, icsl, "istio-ingressgateway", kubernetes.ComponentNotFound, true)
   311  
   312  	// The rest of the components are healthy
   313  	assertComponent(assert, icsl, "istio-egressgateway", kubernetes.ComponentHealthy, false)
   314  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   315  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
   316  	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
   317  }
   318  
   319  func TestFailingTracingService(t *testing.T) {
   320  	assert := assert.New(t)
   321  
   322  	objs, b1, b2 := sampleIstioComponent()
   323  	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objs, b1, b2)
   324  
   325  	conf := config.Get()
   326  	config.Set(conf)
   327  
   328  	// Set global cache var
   329  	SetupBusinessLayer(t, k8s, *conf)
   330  
   331  	clients := make(map[string]kubernetes.ClientInterface)
   332  	clients[conf.KubernetesConfig.ClusterName] = k8s
   333  	iss := NewWithBackends(clients, clients, nil, mockFailingJaeger()).IstioStatus
   334  	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   335  	assert.NoError(error)
   336  
   337  	// Requests to AddOns have to be 1
   338  	assert.Equal(1, *grafanaCalls)
   339  	assert.Equal(1, *promCalls)
   340  
   341  	// Tracing service is unreachable
   342  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentUnreachable, false)
   343  
   344  	// The rest of the services are healthy
   345  	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
   346  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   347  	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
   348  
   349  }
   350  
   351  func TestOverriddenUrls(t *testing.T) {
   352  	assert := assert.New(t)
   353  
   354  	objects, idReachable, _ := sampleIstioComponent()
   355  	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objects, idReachable, true)
   356  
   357  	conf := config.NewConfig()
   358  	// Set global cache var
   359  	SetupBusinessLayer(t, k8s, *conf)
   360  
   361  	clients := make(map[string]kubernetes.ClientInterface)
   362  	clients[conf.KubernetesConfig.ClusterName] = k8s
   363  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   364  	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   365  	assert.NoError(error)
   366  
   367  	// Requests to AddOns have to be 1
   368  	assert.Equal(1, *grafanaCalls)
   369  	assert.Equal(1, *promCalls)
   370  
   371  	// All the services are healthy
   372  	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
   373  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   374  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
   375  	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
   376  }
   377  
   378  func TestCustomDashboardsMainPrometheus(t *testing.T) {
   379  	assert := assert.New(t)
   380  
   381  	objs, b1, b2 := sampleIstioComponent()
   382  	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objs, b1, b2)
   383  
   384  	// Custom Dashboard prom URL forced to be empty
   385  	conf := config.Get()
   386  	conf.ExternalServices.CustomDashboards.Prometheus.URL = ""
   387  	config.Set(conf)
   388  
   389  	// Set global cache var
   390  	SetupBusinessLayer(t, k8s, *conf)
   391  
   392  	clients := make(map[string]kubernetes.ClientInterface)
   393  	clients[conf.KubernetesConfig.ClusterName] = k8s
   394  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   395  	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   396  	assert.NoError(error)
   397  
   398  	// Requests to AddOns have to be 1
   399  	assert.Equal(1, *grafanaCalls)
   400  	assert.Equal(2, *promCalls)
   401  
   402  	// All the services are healthy
   403  	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
   404  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   405  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
   406  	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
   407  }
   408  
   409  func TestNoIstioComponentFoundError(t *testing.T) {
   410  	assert := assert.New(t)
   411  
   412  	k8s, _, _ := mockAddOnsCalls(t, []runtime.Object{}, true, false)
   413  
   414  	conf := config.NewConfig()
   415  	// Set global cache var
   416  	SetupBusinessLayer(t, k8s, *conf)
   417  
   418  	clients := make(map[string]kubernetes.ClientInterface)
   419  	clients[conf.KubernetesConfig.ClusterName] = k8s
   420  
   421  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   422  	_, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   423  	assert.Error(error)
   424  }
   425  
   426  func TestDefaults(t *testing.T) {
   427  	assert := assert.New(t)
   428  
   429  	objects := []runtime.Object{
   430  		fakeDeploymentWithStatus("istio-egressgateway", map[string]string{"app": "istio-egressgateway", "istio": "egressgateway"}, unhealthyStatus),
   431  		fakeDeploymentWithStatus("istiod", map[string]string{"app": "istiod", "istio": "pilot"}, healthyStatus),
   432  	}
   433  
   434  	for _, obj := range healthyIstiods() {
   435  		o := obj
   436  		objects = append(objects, &o)
   437  	}
   438  
   439  	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objects, true, false)
   440  
   441  	conf := config.NewConfig()
   442  	// Set global cache var
   443  	SetupBusinessLayer(t, k8s, *conf)
   444  
   445  	clients := make(map[string]kubernetes.ClientInterface)
   446  	clients[conf.KubernetesConfig.ClusterName] = k8s
   447  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   448  
   449  	icsl, err := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   450  	assert.NoError(err)
   451  
   452  	// Two istio components are not found or unhealthy
   453  	assertComponent(assert, icsl, "istio-ingressgateway", kubernetes.ComponentNotFound, true)
   454  	assertComponent(assert, icsl, "istio-egressgateway", kubernetes.ComponentUnhealthy, false)
   455  
   456  	// The rest of the components are healthy
   457  	assertComponent(assert, icsl, "istiod", kubernetes.ComponentHealthy, true)
   458  	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
   459  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   460  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
   461  	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
   462  
   463  	// Requests to AddOns have to be 1
   464  	assert.Equal(1, *grafanaCalls)
   465  	assert.Equal(1, *promCalls)
   466  }
   467  
   468  func TestNonDefaults(t *testing.T) {
   469  	assert := assert.New(t)
   470  
   471  	objects := []runtime.Object{
   472  		fakeDeploymentWithStatus("istio-egressgateway", map[string]string{"app": "istio-egressgateway", "istio": "egressgateway"}, unhealthyStatus),
   473  		fakeDeploymentWithStatus("istiod", map[string]string{"app": "istiod", "istio": "pilot"}, healthyStatus),
   474  	}
   475  
   476  	for _, obj := range healthyIstiods() {
   477  		o := obj
   478  		objects = append(objects, &o)
   479  	}
   480  
   481  	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objects, true, false)
   482  
   483  	conf := config.Get()
   484  	conf.ExternalServices.Istio.ComponentStatuses = config.ComponentStatuses{
   485  		Enabled: true,
   486  		Components: []config.ComponentStatus{
   487  			{AppLabel: "istiod", IsCore: false},
   488  			{AppLabel: "istio-egressgateway", IsCore: false},
   489  			{AppLabel: "istio-ingressgateway", IsCore: false},
   490  		},
   491  	}
   492  	config.Set(conf)
   493  
   494  	// Set global cache var
   495  	SetupBusinessLayer(t, k8s, *conf)
   496  
   497  	clients := make(map[string]kubernetes.ClientInterface)
   498  	clients[conf.KubernetesConfig.ClusterName] = k8s
   499  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   500  
   501  	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   502  	assert.NoError(error)
   503  
   504  	// Two istio components are not found or unhealthy
   505  	assertComponent(assert, icsl, "istio-ingressgateway", kubernetes.ComponentNotFound, false)
   506  	assertComponent(assert, icsl, "istio-egressgateway", kubernetes.ComponentUnhealthy, false)
   507  
   508  	// The rest of the components are healthy
   509  	assertComponent(assert, icsl, "istiod", kubernetes.ComponentHealthy, false)
   510  	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
   511  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   512  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
   513  	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
   514  
   515  	// Requests to AddOns have to be 1
   516  	assert.Equal(1, *grafanaCalls)
   517  	assert.Equal(1, *promCalls)
   518  }
   519  
   520  // Istiod replicas is downscaled to 0
   521  // Kiali should notify that in the Istio Component Status
   522  func TestIstiodNotReady(t *testing.T) {
   523  	assert := assert.New(t)
   524  
   525  	objects := []runtime.Object{
   526  		fakeDeploymentWithStatus("istio-egressgateway", map[string]string{"app": "istio-egressgateway", "istio": "egressgateway"}, unhealthyStatus),
   527  		fakeDeploymentWithStatus("istiod", map[string]string{"app": "istiod", "istio": "pilot"}, notReadyStatus),
   528  	}
   529  
   530  	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objects, false, false)
   531  
   532  	conf := config.Get()
   533  	conf.IstioLabels.AppLabelName = "app.kubernetes.io/name"
   534  	conf.ExternalServices.Istio.ComponentStatuses = config.ComponentStatuses{
   535  		Enabled: true,
   536  		Components: []config.ComponentStatus{
   537  			{AppLabel: "istiod", IsCore: true},
   538  			{AppLabel: "istio-egressgateway", IsCore: false},
   539  			{AppLabel: "istio-ingressgateway", IsCore: false},
   540  		},
   541  	}
   542  	config.Set(conf)
   543  
   544  	// Set global cache var
   545  	SetupBusinessLayer(t, k8s, *conf)
   546  
   547  	clients := make(map[string]kubernetes.ClientInterface)
   548  	clients[conf.KubernetesConfig.ClusterName] = k8s
   549  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   550  
   551  	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   552  	assert.NoError(error)
   553  
   554  	// Three istio components are unhealthy, not found or not ready
   555  	assertComponent(assert, icsl, "istio-ingressgateway", kubernetes.ComponentNotFound, false)
   556  	assertComponent(assert, icsl, "istio-egressgateway", kubernetes.ComponentUnhealthy, false)
   557  	assertComponent(assert, icsl, "istiod", kubernetes.ComponentNotReady, true)
   558  
   559  	// The rest of the components are healthy
   560  	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
   561  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   562  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
   563  
   564  	// Terminating pods are not present
   565  	assertNotPresent(assert, icsl, "istiod-x3v1kn0l-terminating")
   566  	assertNotPresent(assert, icsl, "istiod-x3v1kn1l-terminating")
   567  
   568  	// Requests to AddOns have to be 1
   569  	assert.Equal(1, *grafanaCalls)
   570  	assert.Equal(1, *promCalls)
   571  }
   572  
   573  // Istiod pods are not reachable from kiali
   574  // Kiali should notify that in the Istio Component Status
   575  func TestIstiodUnreachable(t *testing.T) {
   576  	assert := assert.New(t)
   577  
   578  	objects := []runtime.Object{
   579  		fakeDeploymentWithStatus("istio-egressgateway", map[string]string{"app": "istio-egressgateway", "istio": "egressgateway"}, unhealthyStatus),
   580  		fakeDeploymentWithStatus("istiod", map[string]string{"app": "istiod", "istio": "pilot"}, healthyStatus),
   581  	}
   582  
   583  	var istioStatus kubernetes.IstioComponentStatus
   584  	for _, pod := range healthyIstiods() {
   585  		// Only running pods are considered healthy.
   586  		if pod.Status.Phase == v1.PodRunning && pod.Labels["app"] == "istiod" {
   587  			istioStatus = append(istioStatus, kubernetes.ComponentStatus{
   588  				Name:   pod.Name,
   589  				Status: kubernetes.ComponentUnreachable,
   590  				IsCore: true,
   591  			})
   592  		}
   593  	}
   594  	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objects, false, false)
   595  
   596  	conf := config.Get()
   597  	conf.IstioLabels.AppLabelName = "app.kubernetes.io/name"
   598  	conf.ExternalServices.Istio.ComponentStatuses = config.ComponentStatuses{
   599  		Enabled: true,
   600  		Components: []config.ComponentStatus{
   601  			{AppLabel: "istiod", IsCore: true},
   602  			{AppLabel: "istio-egressgateway", IsCore: false},
   603  			{AppLabel: "istio-ingressgateway", IsCore: false},
   604  		},
   605  	}
   606  	config.Set(conf)
   607  
   608  	// Set global cache var
   609  	SetupBusinessLayer(t, k8s, *conf)
   610  	WithControlPlaneMonitor(&FakeControlPlaneMonitor{status: istioStatus})
   611  
   612  	clients := make(map[string]kubernetes.ClientInterface)
   613  	clients[conf.KubernetesConfig.ClusterName] = k8s
   614  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   615  
   616  	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   617  	assert.NoError(error)
   618  
   619  	// Four istio components are unhealthy, not found or not ready
   620  	assertComponent(assert, icsl, "istio-ingressgateway", kubernetes.ComponentNotFound, false)
   621  	assertComponent(assert, icsl, "istio-egressgateway", kubernetes.ComponentUnhealthy, false)
   622  	assertComponent(assert, icsl, "istiod-x3v1kn0l-running", kubernetes.ComponentUnreachable, true)
   623  	assertComponent(assert, icsl, "istiod-x3v1kn1l-running", kubernetes.ComponentUnreachable, true)
   624  
   625  	// The rest of the components are healthy
   626  	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
   627  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   628  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
   629  	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
   630  
   631  	// Terminating pods are not present
   632  	assertNotPresent(assert, icsl, "istiod-x3v1kn0l-terminating")
   633  	assertNotPresent(assert, icsl, "istiod-x3v1kn1l-terminating")
   634  
   635  	// Requests to AddOns have to be 1
   636  	assert.Equal(1, *grafanaCalls)
   637  	assert.Equal(1, *promCalls)
   638  }
   639  
   640  // Istio deployments only have the "app" app_label.
   641  // Users can't customize this one. They can only customize it for their own deployments.
   642  func TestCustomizedAppLabel(t *testing.T) {
   643  	assert := assert.New(t)
   644  
   645  	objects := []runtime.Object{
   646  		fakeDeploymentWithStatus("istio-egressgateway", map[string]string{"app": "istio-egressgateway", "istio": "egressgateway"}, unhealthyStatus),
   647  		fakeDeploymentWithStatus("istiod", map[string]string{"app": "istiod", "istio": "pilot"}, healthyStatus),
   648  	}
   649  
   650  	for _, obj := range healthyIstiods() {
   651  		o := obj
   652  		objects = append(objects, &o)
   653  	}
   654  
   655  	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objects, true, false)
   656  
   657  	conf := config.Get()
   658  	conf.IstioLabels.AppLabelName = "app.kubernetes.io/name"
   659  	conf.ExternalServices.Istio.ComponentStatuses = config.ComponentStatuses{
   660  		Enabled: true,
   661  		Components: []config.ComponentStatus{
   662  			{AppLabel: "istiod", IsCore: false},
   663  			{AppLabel: "istio-egressgateway", IsCore: false},
   664  			{AppLabel: "istio-ingressgateway", IsCore: false},
   665  		},
   666  	}
   667  	config.Set(conf)
   668  
   669  	// Set global cache var
   670  	SetupBusinessLayer(t, k8s, *conf)
   671  
   672  	clients := make(map[string]kubernetes.ClientInterface)
   673  	clients[conf.KubernetesConfig.ClusterName] = k8s
   674  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   675  
   676  	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   677  	assert.NoError(error)
   678  
   679  	// Two istio components are not found or unhealthy
   680  	assertComponent(assert, icsl, "istio-ingressgateway", kubernetes.ComponentNotFound, false)
   681  	assertComponent(assert, icsl, "istio-egressgateway", kubernetes.ComponentUnhealthy, false)
   682  
   683  	// The rest of the components are healthy
   684  	assertComponent(assert, icsl, "istiod", kubernetes.ComponentHealthy, false)
   685  	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
   686  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   687  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
   688  	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
   689  
   690  	// Requests to AddOns have to be 1
   691  	assert.Equal(1, *grafanaCalls)
   692  	assert.Equal(1, *promCalls)
   693  }
   694  
   695  func TestDaemonSetComponentHealthy(t *testing.T) {
   696  	assert := assert.New(t)
   697  
   698  	objects := []runtime.Object{
   699  		fakeDaemonSetWithStatus("istio-ingressgateway", map[string]string{"app": "istio-ingressgateway", "istio": "ingressgateway"}, healthyDaemonSetStatus),
   700  		fakeDeploymentWithStatus("istio-egressgateway", map[string]string{"app": "istio-egressgateway", "istio": "egressgateway"}, unhealthyStatus),
   701  		fakeDeploymentWithStatus("istiod", map[string]string{"app": "istiod", "istio": "pilot"}, healthyStatus),
   702  	}
   703  
   704  	for _, obj := range healthyIstiods() {
   705  		o := obj
   706  		objects = append(objects, &o)
   707  	}
   708  
   709  	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objects, true, false)
   710  
   711  	conf := config.Get()
   712  	conf.IstioLabels.AppLabelName = "app.kubernetes.io/name"
   713  	conf.ExternalServices.Istio.ComponentStatuses = config.ComponentStatuses{
   714  		Enabled: true,
   715  		Components: []config.ComponentStatus{
   716  			{AppLabel: "istiod", IsCore: false},
   717  			{AppLabel: "istio-egressgateway", IsCore: false},
   718  			{AppLabel: "istio-ingressgateway", IsCore: false},
   719  		},
   720  	}
   721  	config.Set(conf)
   722  
   723  	// Set global cache var
   724  	SetupBusinessLayer(t, k8s, *conf)
   725  
   726  	clients := make(map[string]kubernetes.ClientInterface)
   727  	clients[conf.KubernetesConfig.ClusterName] = k8s
   728  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   729  
   730  	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   731  	assert.NoError(error)
   732  
   733  	// One istio components is unhealthy
   734  	assertComponent(assert, icsl, "istio-egressgateway", kubernetes.ComponentUnhealthy, false)
   735  
   736  	// The rest of the components are healthy
   737  	assertComponent(assert, icsl, "istio-ingressgateway", kubernetes.ComponentHealthy, false)
   738  	assertComponent(assert, icsl, "istiod", kubernetes.ComponentHealthy, false)
   739  	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
   740  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   741  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
   742  	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
   743  
   744  	// Requests to AddOns have to be 1
   745  	assert.Equal(1, *grafanaCalls)
   746  	assert.Equal(1, *promCalls)
   747  }
   748  
   749  // Users may use DaemonSets to deploy istio components
   750  func TestDaemonSetComponentUnhealthy(t *testing.T) {
   751  	assert := assert.New(t)
   752  
   753  	objects := []runtime.Object{
   754  		fakeDaemonSetWithStatus("istio-ingressgateway", map[string]string{"app": "istio-ingressgateway", "istio": "ingressgateway"}, unhealthyDaemonSetStatus),
   755  		fakeDeploymentWithStatus("istio-egressgateway", map[string]string{"app": "istio-egressgateway", "istio": "egressgateway"}, unhealthyStatus),
   756  		fakeDeploymentWithStatus("istiod", map[string]string{"app": "istiod", "istio": "pilot"}, healthyStatus),
   757  	}
   758  
   759  	k8s, grafanaCalls, promCalls := mockAddOnsCalls(t, objects, true, false)
   760  
   761  	conf := config.Get()
   762  	conf.IstioLabels.AppLabelName = "app.kubernetes.io/name"
   763  	conf.ExternalServices.Istio.ComponentStatuses = config.ComponentStatuses{
   764  		Enabled: true,
   765  		Components: []config.ComponentStatus{
   766  			{AppLabel: "istiod", IsCore: false},
   767  			{AppLabel: "istio-egressgateway", IsCore: false},
   768  			{AppLabel: "istio-ingressgateway", IsCore: false},
   769  		},
   770  	}
   771  	config.Set(conf)
   772  
   773  	// Set global cache var
   774  	SetupBusinessLayer(t, k8s, *conf)
   775  
   776  	clients := make(map[string]kubernetes.ClientInterface)
   777  	clients[conf.KubernetesConfig.ClusterName] = k8s
   778  	iss := NewWithBackends(clients, clients, nil, mockJaeger()).IstioStatus
   779  
   780  	icsl, error := iss.GetStatus(context.TODO(), conf.KubernetesConfig.ClusterName)
   781  	assert.NoError(error)
   782  
   783  	// Two istio components are unhealthy
   784  	assertComponent(assert, icsl, "istio-ingressgateway", kubernetes.ComponentUnhealthy, false)
   785  	assertComponent(assert, icsl, "istio-egressgateway", kubernetes.ComponentUnhealthy, false)
   786  
   787  	// The rest of the components are healthy
   788  	assertComponent(assert, icsl, "istiod", kubernetes.ComponentHealthy, false)
   789  	assertComponent(assert, icsl, "grafana", kubernetes.ComponentHealthy, false)
   790  	assertComponent(assert, icsl, "prometheus", kubernetes.ComponentHealthy, false)
   791  	assertComponent(assert, icsl, "tracing", kubernetes.ComponentHealthy, false)
   792  	assertComponent(assert, icsl, "custom dashboards", kubernetes.ComponentHealthy, false)
   793  
   794  	// Requests to AddOns have to be 1
   795  	assert.Equal(1, *grafanaCalls)
   796  	assert.Equal(1, *promCalls)
   797  }
   798  
   799  func assertComponent(assert *assert.Assertions, icsl kubernetes.IstioComponentStatus, name string, status string, isCore bool) {
   800  	componentFound := false
   801  	for _, ics := range icsl {
   802  		if ics.Name == name {
   803  			assert.Equal(status, ics.Status)
   804  			assert.Equal(isCore, ics.IsCore)
   805  			componentFound = true
   806  		}
   807  	}
   808  
   809  	assert.True(componentFound)
   810  }
   811  
   812  func assertNotPresent(assert *assert.Assertions, icsl kubernetes.IstioComponentStatus, name string) {
   813  	componentFound := false
   814  	for _, ics := range icsl {
   815  		if ics.Name == name {
   816  			componentFound = true
   817  		}
   818  	}
   819  	assert.False(componentFound)
   820  }
   821  
   822  func mockJaeger() tracing.ClientInterface {
   823  	j := new(tracingtest.TracingClientMock)
   824  	j.On("GetServiceStatus").Return(true, nil)
   825  	return j
   826  }
   827  
   828  func mockFailingJaeger() tracing.ClientInterface {
   829  	j := new(tracingtest.TracingClientMock)
   830  	j.On("GetServiceStatus").Return(false, errors.New("error connecting with tracing service"))
   831  	return j
   832  }
   833  
   834  // Setup K8S api call to fetch Pods
   835  func mockDeploymentCall(objects []runtime.Object, isIstioReachable bool) {
   836  	var istioStatus kubernetes.IstioComponentStatus
   837  	if !isIstioReachable {
   838  		for _, obj := range objects {
   839  			if pod, isPod := obj.(*v1.Pod); isPod {
   840  				// Only running pods are considered healthy.
   841  				if pod.Status.Phase == v1.PodRunning && pod.Labels["app"] == "istiod" {
   842  					istioStatus = append(istioStatus, kubernetes.ComponentStatus{
   843  						Name:   pod.Name,
   844  						Status: kubernetes.ComponentUnreachable,
   845  						IsCore: true,
   846  					})
   847  				}
   848  			}
   849  		}
   850  	}
   851  
   852  	WithControlPlaneMonitor(&FakeControlPlaneMonitor{status: istioStatus})
   853  }
   854  
   855  func fakeDeploymentWithStatus(name string, labels map[string]string, status apps_v1.DeploymentStatus) *apps_v1.Deployment {
   856  	return &apps_v1.Deployment{
   857  		ObjectMeta: meta_v1.ObjectMeta{
   858  			Name:      name,
   859  			Namespace: "istio-system",
   860  			Labels:    labels,
   861  		},
   862  		Status: status,
   863  		Spec: apps_v1.DeploymentSpec{
   864  			Template: v1.PodTemplateSpec{
   865  				ObjectMeta: meta_v1.ObjectMeta{
   866  					Name:   "",
   867  					Labels: labels,
   868  				},
   869  			},
   870  			Replicas: &status.Replicas,
   871  		},
   872  	}
   873  }
   874  
   875  func fakeDaemonSetWithStatus(name string, labels map[string]string, status apps_v1.DaemonSetStatus) *apps_v1.DaemonSet {
   876  	return &apps_v1.DaemonSet{
   877  		ObjectMeta: meta_v1.ObjectMeta{
   878  			Name:      name,
   879  			Namespace: "istio-system",
   880  			Labels:    labels,
   881  		},
   882  		Status: status,
   883  		Spec: apps_v1.DaemonSetSpec{
   884  			Selector: &meta_v1.LabelSelector{
   885  				MatchLabels: labels,
   886  			},
   887  			Template: v1.PodTemplateSpec{
   888  				ObjectMeta: meta_v1.ObjectMeta{
   889  					Name:   "",
   890  					Labels: labels,
   891  				},
   892  			},
   893  		},
   894  	}
   895  }
   896  
   897  func confWithComponentNamespaces() *config.Config {
   898  	conf := config.NewConfig()
   899  	conf.ExternalServices.Istio.ComponentStatuses = config.ComponentStatuses{
   900  		Enabled: true,
   901  		Components: []config.ComponentStatus{
   902  			{AppLabel: "pilot", IsCore: true},
   903  			{AppLabel: "ingress", IsCore: true, Namespace: "ingress-egress"},
   904  			{AppLabel: "egress", IsCore: false, Namespace: "ingress-egress"},
   905  			{AppLabel: "sds", IsCore: false, Namespace: "istio-admin"},
   906  		},
   907  	}
   908  
   909  	return conf
   910  }
   911  
   912  func mockServer(t *testing.T, mr *mux.Router) *httptest.Server {
   913  	s := httptest.NewServer(mr)
   914  	t.Cleanup(s.Close)
   915  	return s
   916  }
   917  
   918  func addAddOnRoute(mr *mux.Router, mu *sync.Mutex, url string, statusCode int, callNum *int) {
   919  	mr.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
   920  		mu.Lock()
   921  		if callNum != nil {
   922  			*callNum = *callNum + 1
   923  		}
   924  		mu.Unlock()
   925  		if statusCode > 299 {
   926  			http.Error(w, "Not a success", statusCode)
   927  		} else {
   928  			if c, err := w.Write([]byte("OK")); err != nil {
   929  				log.Errorf("Error when mocking the addon call: %s (%d)", url, c)
   930  			}
   931  		}
   932  	})
   933  }
   934  
   935  func mockAddOnCalls(addons map[string]addOnsSetup) *mux.Router {
   936  	var mu sync.Mutex
   937  	mr := mux.NewRouter()
   938  	for _, addon := range addons {
   939  		addAddOnRoute(mr, &mu, addon.Url, addon.StatusCode, addon.CallCount)
   940  	}
   941  	return mr
   942  }
   943  
   944  func defaultAddOnCalls(grafana, prom *int) map[string]addOnsSetup {
   945  	return map[string]addOnsSetup{
   946  		"prometheus": {
   947  			Url:        "/prometheus/mock",
   948  			StatusCode: 200,
   949  			CallCount:  prom,
   950  		},
   951  		"prometheus-healthy": {
   952  			Url:        "/prometheus/mock/-/healthy",
   953  			StatusCode: 200,
   954  			CallCount:  prom,
   955  		},
   956  		"grafana": {
   957  			Url:        "/grafana/mock",
   958  			StatusCode: 200,
   959  			CallCount:  grafana,
   960  		},
   961  		"custom dashboards": {
   962  			Url:        "/prometheus-dashboards/mock",
   963  			StatusCode: 200,
   964  			CallCount:  nil,
   965  		},
   966  	}
   967  }
   968  
   969  func addonAddMockUrls(baseUrl string, conf *config.Config, overrideUrl bool) *config.Config {
   970  	conf.ExternalServices.Grafana.Enabled = true
   971  	conf.ExternalServices.Grafana.InClusterURL = baseUrl + "/grafana/mock"
   972  	conf.ExternalServices.Grafana.IsCore = false
   973  
   974  	conf.ExternalServices.Tracing.Enabled = true
   975  	conf.ExternalServices.Tracing.InClusterURL = baseUrl + "/tracing/mock"
   976  	conf.ExternalServices.Tracing.IsCore = false
   977  
   978  	conf.ExternalServices.Prometheus.URL = baseUrl + "/prometheus/mock"
   979  
   980  	conf.ExternalServices.CustomDashboards.Enabled = true
   981  	conf.ExternalServices.CustomDashboards.Prometheus.URL = baseUrl + "/prometheus-dashboards/mock"
   982  	conf.ExternalServices.CustomDashboards.IsCore = false
   983  
   984  	if overrideUrl {
   985  		conf.ExternalServices.Grafana.HealthCheckUrl = conf.ExternalServices.Grafana.InClusterURL
   986  		conf.ExternalServices.Grafana.InClusterURL = baseUrl + "/grafana/wrong"
   987  
   988  		conf.ExternalServices.Prometheus.HealthCheckUrl = conf.ExternalServices.Prometheus.URL
   989  		conf.ExternalServices.Prometheus.URL = baseUrl + "/prometheus/wrong"
   990  
   991  		conf.ExternalServices.CustomDashboards.Prometheus.HealthCheckUrl = conf.ExternalServices.CustomDashboards.Prometheus.URL
   992  		conf.ExternalServices.CustomDashboards.Prometheus.URL = baseUrl + "/prometheus/wrong"
   993  
   994  	}
   995  	return conf
   996  }