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 }