github.com/netdata/go.d.plugin@v0.58.1/modules/k8s_state/kube_state_test.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package k8s_state 4 5 import ( 6 "context" 7 "errors" 8 "fmt" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/netdata/go.d.plugin/agent/module" 14 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 corev1 "k8s.io/api/core/v1" 18 apiresource "k8s.io/apimachinery/pkg/api/resource" 19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 "k8s.io/apimachinery/pkg/version" 21 "k8s.io/client-go/discovery" 22 "k8s.io/client-go/kubernetes" 23 "k8s.io/client-go/kubernetes/fake" 24 ) 25 26 func TestNew(t *testing.T) { 27 assert.Implements(t, (*module.Module)(nil), New()) 28 } 29 30 func TestKubeState_Init(t *testing.T) { 31 tests := map[string]struct { 32 wantFail bool 33 prepare func() *KubeState 34 }{ 35 "success when no error on initializing K8s client": { 36 wantFail: false, 37 prepare: func() *KubeState { 38 ks := New() 39 ks.newKubeClient = func() (kubernetes.Interface, error) { return fake.NewSimpleClientset(), nil } 40 return ks 41 }, 42 }, 43 "fail when get an error on initializing K8s client": { 44 wantFail: true, 45 prepare: func() *KubeState { 46 ks := New() 47 ks.newKubeClient = func() (kubernetes.Interface, error) { return nil, errors.New("newKubeClient() error") } 48 return ks 49 }, 50 }, 51 } 52 53 for name, test := range tests { 54 t.Run(name, func(t *testing.T) { 55 ks := test.prepare() 56 57 if test.wantFail { 58 assert.False(t, ks.Init()) 59 } else { 60 assert.True(t, ks.Init()) 61 } 62 }) 63 } 64 } 65 66 func TestKubeState_Check(t *testing.T) { 67 tests := map[string]struct { 68 wantFail bool 69 prepare func() *KubeState 70 }{ 71 "success when connected to the K8s API": { 72 wantFail: false, 73 prepare: func() *KubeState { 74 ks := New() 75 ks.newKubeClient = func() (kubernetes.Interface, error) { return fake.NewSimpleClientset(), nil } 76 return ks 77 }, 78 }, 79 "fail when not connected to the K8s API": { 80 wantFail: true, 81 prepare: func() *KubeState { 82 ks := New() 83 client := &brokenInfoKubeClient{fake.NewSimpleClientset()} 84 ks.newKubeClient = func() (kubernetes.Interface, error) { return client, nil } 85 return ks 86 }, 87 }, 88 } 89 90 for name, test := range tests { 91 t.Run(name, func(t *testing.T) { 92 ks := test.prepare() 93 require.True(t, ks.Init()) 94 95 if test.wantFail { 96 assert.False(t, ks.Check()) 97 } else { 98 assert.True(t, ks.Check()) 99 } 100 }) 101 } 102 } 103 104 func TestKubeState_Charts(t *testing.T) { 105 ks := New() 106 107 assert.NotEmpty(t, *ks.Charts()) 108 } 109 110 func TestKubeState_Cleanup(t *testing.T) { 111 tests := map[string]struct { 112 prepare func() *KubeState 113 doInit bool 114 doCollect bool 115 }{ 116 "before init": { 117 doInit: false, 118 doCollect: false, 119 prepare: func() *KubeState { 120 ks := New() 121 ks.newKubeClient = func() (kubernetes.Interface, error) { return fake.NewSimpleClientset(), nil } 122 return ks 123 }, 124 }, 125 "after init": { 126 doInit: true, 127 doCollect: false, 128 prepare: func() *KubeState { 129 ks := New() 130 ks.newKubeClient = func() (kubernetes.Interface, error) { return fake.NewSimpleClientset(), nil } 131 return ks 132 }, 133 }, 134 "after collect": { 135 doInit: true, 136 doCollect: true, 137 prepare: func() *KubeState { 138 ks := New() 139 ks.newKubeClient = func() (kubernetes.Interface, error) { return fake.NewSimpleClientset(), nil } 140 return ks 141 }, 142 }, 143 } 144 145 for name, test := range tests { 146 t.Run(name, func(t *testing.T) { 147 ks := test.prepare() 148 149 if test.doInit { 150 _ = ks.Init() 151 } 152 if test.doCollect { 153 _ = ks.Collect() 154 time.Sleep(ks.initDelay) 155 } 156 157 assert.NotPanics(t, ks.Cleanup) 158 time.Sleep(time.Second) 159 if test.doCollect { 160 assert.True(t, ks.discoverer.stopped()) 161 } 162 }) 163 } 164 } 165 166 func TestKubeState_Collect(t *testing.T) { 167 type ( 168 testCaseStep func(t *testing.T, ks *KubeState) 169 testCase struct { 170 client kubernetes.Interface 171 steps []testCaseStep 172 } 173 ) 174 175 tests := map[string]struct { 176 create func(t *testing.T) testCase 177 }{ 178 "Node only": { 179 create: func(t *testing.T) testCase { 180 client := fake.NewSimpleClientset( 181 newNode("node01"), 182 ) 183 184 step1 := func(t *testing.T, ks *KubeState) { 185 mx := ks.Collect() 186 expected := map[string]int64{ 187 "discovery_node_discoverer_state": 1, 188 "discovery_pod_discoverer_state": 1, 189 "node_node01_age": 3, 190 "node_node01_alloc_cpu_limits_used": 0, 191 "node_node01_alloc_cpu_limits_util": 0, 192 "node_node01_alloc_cpu_requests_used": 0, 193 "node_node01_alloc_cpu_requests_util": 0, 194 "node_node01_alloc_mem_limits_used": 0, 195 "node_node01_alloc_mem_limits_util": 0, 196 "node_node01_alloc_mem_requests_used": 0, 197 "node_node01_alloc_mem_requests_util": 0, 198 "node_node01_alloc_pods_allocated": 0, 199 "node_node01_alloc_pods_available": 110, 200 "node_node01_alloc_pods_util": 0, 201 "node_node01_cond_diskpressure": 0, 202 "node_node01_cond_memorypressure": 0, 203 "node_node01_cond_networkunavailable": 0, 204 "node_node01_cond_pidpressure": 0, 205 "node_node01_cond_ready": 1, 206 "node_node01_schedulability_schedulable": 1, 207 "node_node01_schedulability_unschedulable": 0, 208 "node_node01_containers": 0, 209 "node_node01_containers_state_running": 0, 210 "node_node01_containers_state_terminated": 0, 211 "node_node01_containers_state_waiting": 0, 212 "node_node01_init_containers": 0, 213 "node_node01_init_containers_state_running": 0, 214 "node_node01_init_containers_state_terminated": 0, 215 "node_node01_init_containers_state_waiting": 0, 216 "node_node01_pods_cond_containersready": 0, 217 "node_node01_pods_cond_podinitialized": 0, 218 "node_node01_pods_cond_podready": 0, 219 "node_node01_pods_cond_podscheduled": 0, 220 "node_node01_pods_phase_failed": 0, 221 "node_node01_pods_phase_pending": 0, 222 "node_node01_pods_phase_running": 0, 223 "node_node01_pods_phase_succeeded": 0, 224 "node_node01_pods_readiness": 0, 225 "node_node01_pods_readiness_ready": 0, 226 "node_node01_pods_readiness_unready": 0, 227 } 228 copyAge(expected, mx) 229 assert.Equal(t, expected, mx) 230 assert.Equal(t, 231 len(nodeChartsTmpl)+len(baseCharts), 232 len(*ks.Charts()), 233 ) 234 } 235 236 return testCase{ 237 client: client, 238 steps: []testCaseStep{step1}, 239 } 240 }, 241 }, 242 "Pod only": { 243 create: func(t *testing.T) testCase { 244 pod := newPod("node01", "pod01") 245 client := fake.NewSimpleClientset( 246 pod, 247 ) 248 249 step1 := func(t *testing.T, ks *KubeState) { 250 mx := ks.Collect() 251 expected := map[string]int64{ 252 "discovery_node_discoverer_state": 1, 253 "discovery_pod_discoverer_state": 1, 254 "pod_default_pod01_age": 3, 255 "pod_default_pod01_cpu_limits_used": 400, 256 "pod_default_pod01_cpu_requests_used": 200, 257 "pod_default_pod01_mem_limits_used": 419430400, 258 "pod_default_pod01_mem_requests_used": 209715200, 259 "pod_default_pod01_cond_containersready": 1, 260 "pod_default_pod01_cond_podinitialized": 1, 261 "pod_default_pod01_cond_podready": 1, 262 "pod_default_pod01_cond_podscheduled": 1, 263 "pod_default_pod01_container_container1_readiness": 1, 264 "pod_default_pod01_container_container1_restarts": 0, 265 "pod_default_pod01_container_container1_state_running": 1, 266 "pod_default_pod01_container_container1_state_terminated": 0, 267 "pod_default_pod01_container_container1_state_waiting": 0, 268 "pod_default_pod01_container_container2_readiness": 1, 269 "pod_default_pod01_container_container2_restarts": 0, 270 "pod_default_pod01_container_container2_state_running": 1, 271 "pod_default_pod01_container_container2_state_terminated": 0, 272 "pod_default_pod01_container_container2_state_waiting": 0, 273 "pod_default_pod01_containers": 2, 274 "pod_default_pod01_containers_state_running": 2, 275 "pod_default_pod01_containers_state_terminated": 0, 276 "pod_default_pod01_containers_state_waiting": 0, 277 "pod_default_pod01_init_containers": 1, 278 "pod_default_pod01_init_containers_state_running": 0, 279 "pod_default_pod01_init_containers_state_terminated": 1, 280 "pod_default_pod01_init_containers_state_waiting": 0, 281 "pod_default_pod01_phase_failed": 0, 282 "pod_default_pod01_phase_pending": 0, 283 "pod_default_pod01_phase_running": 1, 284 "pod_default_pod01_phase_succeeded": 0, 285 } 286 copyAge(expected, mx) 287 288 assert.Equal(t, expected, mx) 289 assert.Equal(t, 290 len(podChartsTmpl)+len(containerChartsTmpl)*len(pod.Spec.Containers)+len(baseCharts), 291 len(*ks.Charts()), 292 ) 293 } 294 295 return testCase{ 296 client: client, 297 steps: []testCaseStep{step1}, 298 } 299 }, 300 }, 301 "Nodes and Pods": { 302 create: func(t *testing.T) testCase { 303 node := newNode("node01") 304 pod := newPod(node.Name, "pod01") 305 client := fake.NewSimpleClientset( 306 node, 307 pod, 308 ) 309 310 step1 := func(t *testing.T, ks *KubeState) { 311 mx := ks.Collect() 312 expected := map[string]int64{ 313 "discovery_node_discoverer_state": 1, 314 "discovery_pod_discoverer_state": 1, 315 "node_node01_age": 3, 316 "node_node01_alloc_cpu_limits_used": 400, 317 "node_node01_alloc_cpu_limits_util": 11428, 318 "node_node01_alloc_cpu_requests_used": 200, 319 "node_node01_alloc_cpu_requests_util": 5714, 320 "node_node01_alloc_mem_limits_used": 419430400, 321 "node_node01_alloc_mem_limits_util": 11428, 322 "node_node01_alloc_mem_requests_used": 209715200, 323 "node_node01_alloc_mem_requests_util": 5714, 324 "node_node01_alloc_pods_allocated": 1, 325 "node_node01_alloc_pods_available": 109, 326 "node_node01_alloc_pods_util": 909, 327 "node_node01_cond_diskpressure": 0, 328 "node_node01_cond_memorypressure": 0, 329 "node_node01_cond_networkunavailable": 0, 330 "node_node01_cond_pidpressure": 0, 331 "node_node01_cond_ready": 1, 332 "node_node01_schedulability_schedulable": 1, 333 "node_node01_schedulability_unschedulable": 0, 334 "node_node01_containers": 2, 335 "node_node01_containers_state_running": 2, 336 "node_node01_containers_state_terminated": 0, 337 "node_node01_containers_state_waiting": 0, 338 "node_node01_init_containers": 1, 339 "node_node01_init_containers_state_running": 0, 340 "node_node01_init_containers_state_terminated": 1, 341 "node_node01_init_containers_state_waiting": 0, 342 "node_node01_pods_cond_containersready": 1, 343 "node_node01_pods_cond_podinitialized": 1, 344 "node_node01_pods_cond_podready": 1, 345 "node_node01_pods_cond_podscheduled": 1, 346 "node_node01_pods_phase_failed": 0, 347 "node_node01_pods_phase_pending": 0, 348 "node_node01_pods_phase_running": 1, 349 "node_node01_pods_phase_succeeded": 0, 350 "node_node01_pods_readiness": 100000, 351 "node_node01_pods_readiness_ready": 1, 352 "node_node01_pods_readiness_unready": 0, 353 "pod_default_pod01_age": 3, 354 "pod_default_pod01_cpu_limits_used": 400, 355 "pod_default_pod01_cpu_requests_used": 200, 356 "pod_default_pod01_mem_limits_used": 419430400, 357 "pod_default_pod01_mem_requests_used": 209715200, 358 "pod_default_pod01_cond_containersready": 1, 359 "pod_default_pod01_cond_podinitialized": 1, 360 "pod_default_pod01_cond_podready": 1, 361 "pod_default_pod01_cond_podscheduled": 1, 362 "pod_default_pod01_container_container1_readiness": 1, 363 "pod_default_pod01_container_container1_restarts": 0, 364 "pod_default_pod01_container_container1_state_running": 1, 365 "pod_default_pod01_container_container1_state_terminated": 0, 366 "pod_default_pod01_container_container1_state_waiting": 0, 367 "pod_default_pod01_container_container2_readiness": 1, 368 "pod_default_pod01_container_container2_restarts": 0, 369 "pod_default_pod01_container_container2_state_running": 1, 370 "pod_default_pod01_container_container2_state_terminated": 0, 371 "pod_default_pod01_container_container2_state_waiting": 0, 372 "pod_default_pod01_containers": 2, 373 "pod_default_pod01_containers_state_running": 2, 374 "pod_default_pod01_containers_state_terminated": 0, 375 "pod_default_pod01_containers_state_waiting": 0, 376 "pod_default_pod01_init_containers": 1, 377 "pod_default_pod01_init_containers_state_running": 0, 378 "pod_default_pod01_init_containers_state_terminated": 1, 379 "pod_default_pod01_init_containers_state_waiting": 0, 380 "pod_default_pod01_phase_failed": 0, 381 "pod_default_pod01_phase_pending": 0, 382 "pod_default_pod01_phase_running": 1, 383 "pod_default_pod01_phase_succeeded": 0, 384 } 385 copyAge(expected, mx) 386 387 assert.Equal(t, expected, mx) 388 assert.Equal(t, 389 len(nodeChartsTmpl)+len(podChartsTmpl)+len(containerChartsTmpl)*len(pod.Spec.Containers)+len(baseCharts), 390 len(*ks.Charts()), 391 ) 392 } 393 394 return testCase{ 395 client: client, 396 steps: []testCaseStep{step1}, 397 } 398 }, 399 }, 400 "delete a Pod in runtime": { 401 create: func(t *testing.T) testCase { 402 ctx := context.Background() 403 node := newNode("node01") 404 pod := newPod(node.Name, "pod01") 405 client := fake.NewSimpleClientset( 406 node, 407 pod, 408 ) 409 step1 := func(t *testing.T, ks *KubeState) { 410 _ = ks.Collect() 411 _ = client.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{}) 412 } 413 414 step2 := func(t *testing.T, ks *KubeState) { 415 mx := ks.Collect() 416 expected := map[string]int64{ 417 "discovery_node_discoverer_state": 1, 418 "discovery_pod_discoverer_state": 1, 419 "node_node01_age": 4, 420 "node_node01_alloc_cpu_limits_used": 0, 421 "node_node01_alloc_cpu_limits_util": 0, 422 "node_node01_alloc_cpu_requests_used": 0, 423 "node_node01_alloc_cpu_requests_util": 0, 424 "node_node01_alloc_mem_limits_used": 0, 425 "node_node01_alloc_mem_limits_util": 0, 426 "node_node01_alloc_mem_requests_used": 0, 427 "node_node01_alloc_mem_requests_util": 0, 428 "node_node01_alloc_pods_allocated": 0, 429 "node_node01_alloc_pods_available": 110, 430 "node_node01_alloc_pods_util": 0, 431 "node_node01_cond_diskpressure": 0, 432 "node_node01_cond_memorypressure": 0, 433 "node_node01_cond_networkunavailable": 0, 434 "node_node01_cond_pidpressure": 0, 435 "node_node01_cond_ready": 1, 436 "node_node01_schedulability_schedulable": 1, 437 "node_node01_schedulability_unschedulable": 0, 438 "node_node01_containers": 0, 439 "node_node01_containers_state_running": 0, 440 "node_node01_containers_state_terminated": 0, 441 "node_node01_containers_state_waiting": 0, 442 "node_node01_init_containers": 0, 443 "node_node01_init_containers_state_running": 0, 444 "node_node01_init_containers_state_terminated": 0, 445 "node_node01_init_containers_state_waiting": 0, 446 "node_node01_pods_cond_containersready": 0, 447 "node_node01_pods_cond_podinitialized": 0, 448 "node_node01_pods_cond_podready": 0, 449 "node_node01_pods_cond_podscheduled": 0, 450 "node_node01_pods_phase_failed": 0, 451 "node_node01_pods_phase_pending": 0, 452 "node_node01_pods_phase_running": 0, 453 "node_node01_pods_phase_succeeded": 0, 454 "node_node01_pods_readiness": 0, 455 "node_node01_pods_readiness_ready": 0, 456 "node_node01_pods_readiness_unready": 0, 457 } 458 copyAge(expected, mx) 459 460 assert.Equal(t, expected, mx) 461 assert.Equal(t, 462 len(nodeChartsTmpl)+len(podChartsTmpl)+len(containerChartsTmpl)*len(pod.Spec.Containers)+len(baseCharts), 463 len(*ks.Charts()), 464 ) 465 assert.Equal(t, 466 len(podChartsTmpl)+len(containerChartsTmpl)*len(pod.Spec.Containers), 467 calcObsoleteCharts(*ks.Charts()), 468 ) 469 } 470 471 return testCase{ 472 client: client, 473 steps: []testCaseStep{step1, step2}, 474 } 475 }, 476 }, 477 "slow spec.NodeName set": { 478 create: func(t *testing.T) testCase { 479 ctx := context.Background() 480 node := newNode("node01") 481 podOrig := newPod(node.Name, "pod01") 482 podOrig.Spec.NodeName = "" 483 client := fake.NewSimpleClientset( 484 node, 485 podOrig, 486 ) 487 podUpdated := newPod(node.Name, "pod01") // with set Spec.NodeName 488 489 step1 := func(t *testing.T, ks *KubeState) { 490 _ = ks.Collect() 491 for _, c := range *ks.Charts() { 492 if strings.HasPrefix(c.ID, "pod_") { 493 ok := isLabelValueSet(c, labelKeyNodeName) 494 assert.Falsef(t, ok, "chart '%s' has not empty %s label", c.ID, labelKeyNodeName) 495 } 496 } 497 } 498 step2 := func(t *testing.T, ks *KubeState) { 499 _, _ = client.CoreV1().Pods(podOrig.Namespace).Update(ctx, podUpdated, metav1.UpdateOptions{}) 500 time.Sleep(time.Millisecond * 50) 501 _ = ks.Collect() 502 503 for _, c := range *ks.Charts() { 504 if strings.HasPrefix(c.ID, "pod_") { 505 ok := isLabelValueSet(c, labelKeyNodeName) 506 assert.Truef(t, ok, "chart '%s' has empty %s label", c.ID, labelKeyNodeName) 507 } 508 } 509 } 510 511 return testCase{ 512 client: client, 513 steps: []testCaseStep{step1, step2}, 514 } 515 }, 516 }, 517 "add a Pod in runtime": { 518 create: func(t *testing.T) testCase { 519 ctx := context.Background() 520 node := newNode("node01") 521 pod1 := newPod(node.Name, "pod01") 522 pod2 := newPod(node.Name, "pod02") 523 client := fake.NewSimpleClientset( 524 node, 525 pod1, 526 ) 527 step1 := func(t *testing.T, ks *KubeState) { 528 _ = ks.Collect() 529 _, _ = client.CoreV1().Pods(pod1.Namespace).Create(ctx, pod2, metav1.CreateOptions{}) 530 } 531 532 step2 := func(t *testing.T, ks *KubeState) { 533 mx := ks.Collect() 534 expected := map[string]int64{ 535 "discovery_node_discoverer_state": 1, 536 "discovery_pod_discoverer_state": 1, 537 "node_node01_age": 4, 538 "node_node01_alloc_cpu_limits_used": 800, 539 "node_node01_alloc_cpu_limits_util": 22857, 540 "node_node01_alloc_cpu_requests_used": 400, 541 "node_node01_alloc_cpu_requests_util": 11428, 542 "node_node01_alloc_mem_limits_used": 838860800, 543 "node_node01_alloc_mem_limits_util": 22857, 544 "node_node01_alloc_mem_requests_used": 419430400, 545 "node_node01_alloc_mem_requests_util": 11428, 546 "node_node01_alloc_pods_allocated": 2, 547 "node_node01_alloc_pods_available": 108, 548 "node_node01_alloc_pods_util": 1818, 549 "node_node01_cond_diskpressure": 0, 550 "node_node01_cond_memorypressure": 0, 551 "node_node01_cond_networkunavailable": 0, 552 "node_node01_cond_pidpressure": 0, 553 "node_node01_cond_ready": 1, 554 "node_node01_schedulability_schedulable": 1, 555 "node_node01_schedulability_unschedulable": 0, 556 "node_node01_containers": 4, 557 "node_node01_containers_state_running": 4, 558 "node_node01_containers_state_terminated": 0, 559 "node_node01_containers_state_waiting": 0, 560 "node_node01_init_containers": 2, 561 "node_node01_init_containers_state_running": 0, 562 "node_node01_init_containers_state_terminated": 2, 563 "node_node01_init_containers_state_waiting": 0, 564 "node_node01_pods_cond_containersready": 2, 565 "node_node01_pods_cond_podinitialized": 2, 566 "node_node01_pods_cond_podready": 2, 567 "node_node01_pods_cond_podscheduled": 2, 568 "node_node01_pods_phase_failed": 0, 569 "node_node01_pods_phase_pending": 0, 570 "node_node01_pods_phase_running": 2, 571 "node_node01_pods_phase_succeeded": 0, 572 "node_node01_pods_readiness": 100000, 573 "node_node01_pods_readiness_ready": 2, 574 "node_node01_pods_readiness_unready": 0, 575 "pod_default_pod01_age": 4, 576 "pod_default_pod01_cpu_limits_used": 400, 577 "pod_default_pod01_cpu_requests_used": 200, 578 "pod_default_pod01_mem_limits_used": 419430400, 579 "pod_default_pod01_mem_requests_used": 209715200, 580 "pod_default_pod01_cond_containersready": 1, 581 "pod_default_pod01_cond_podinitialized": 1, 582 "pod_default_pod01_cond_podready": 1, 583 "pod_default_pod01_cond_podscheduled": 1, 584 "pod_default_pod01_container_container1_readiness": 1, 585 "pod_default_pod01_container_container1_restarts": 0, 586 "pod_default_pod01_container_container1_state_running": 1, 587 "pod_default_pod01_container_container1_state_terminated": 0, 588 "pod_default_pod01_container_container1_state_waiting": 0, 589 "pod_default_pod01_container_container2_readiness": 1, 590 "pod_default_pod01_container_container2_restarts": 0, 591 "pod_default_pod01_container_container2_state_running": 1, 592 "pod_default_pod01_container_container2_state_terminated": 0, 593 "pod_default_pod01_container_container2_state_waiting": 0, 594 "pod_default_pod01_containers": 2, 595 "pod_default_pod01_containers_state_running": 2, 596 "pod_default_pod01_containers_state_terminated": 0, 597 "pod_default_pod01_containers_state_waiting": 0, 598 "pod_default_pod01_init_containers": 1, 599 "pod_default_pod01_init_containers_state_running": 0, 600 "pod_default_pod01_init_containers_state_terminated": 1, 601 "pod_default_pod01_init_containers_state_waiting": 0, 602 "pod_default_pod01_phase_failed": 0, 603 "pod_default_pod01_phase_pending": 0, 604 "pod_default_pod01_phase_running": 1, 605 "pod_default_pod01_phase_succeeded": 0, 606 "pod_default_pod02_age": 4, 607 "pod_default_pod02_cpu_limits_used": 400, 608 "pod_default_pod02_cpu_requests_used": 200, 609 "pod_default_pod02_mem_limits_used": 419430400, 610 "pod_default_pod02_mem_requests_used": 209715200, 611 "pod_default_pod02_cond_containersready": 1, 612 "pod_default_pod02_cond_podinitialized": 1, 613 "pod_default_pod02_cond_podready": 1, 614 "pod_default_pod02_cond_podscheduled": 1, 615 "pod_default_pod02_container_container1_readiness": 1, 616 "pod_default_pod02_container_container1_restarts": 0, 617 "pod_default_pod02_container_container1_state_running": 1, 618 "pod_default_pod02_container_container1_state_terminated": 0, 619 "pod_default_pod02_container_container1_state_waiting": 0, 620 "pod_default_pod02_container_container2_readiness": 1, 621 "pod_default_pod02_container_container2_restarts": 0, 622 "pod_default_pod02_container_container2_state_running": 1, 623 "pod_default_pod02_container_container2_state_terminated": 0, 624 "pod_default_pod02_container_container2_state_waiting": 0, 625 "pod_default_pod02_containers": 2, 626 "pod_default_pod02_containers_state_running": 2, 627 "pod_default_pod02_containers_state_terminated": 0, 628 "pod_default_pod02_containers_state_waiting": 0, 629 "pod_default_pod02_init_containers": 1, 630 "pod_default_pod02_init_containers_state_running": 0, 631 "pod_default_pod02_init_containers_state_terminated": 1, 632 "pod_default_pod02_init_containers_state_waiting": 0, 633 "pod_default_pod02_phase_failed": 0, 634 "pod_default_pod02_phase_pending": 0, 635 "pod_default_pod02_phase_running": 1, 636 "pod_default_pod02_phase_succeeded": 0, 637 } 638 copyAge(expected, mx) 639 640 assert.Equal(t, expected, mx) 641 assert.Equal(t, 642 len(nodeChartsTmpl)+ 643 len(podChartsTmpl)*2+ 644 len(containerChartsTmpl)*len(pod1.Spec.Containers)+ 645 len(containerChartsTmpl)*len(pod2.Spec.Containers)+ 646 len(baseCharts), 647 len(*ks.Charts()), 648 ) 649 } 650 651 return testCase{ 652 client: client, 653 steps: []testCaseStep{step1, step2}, 654 } 655 }, 656 }, 657 } 658 659 for name, creator := range tests { 660 t.Run(name, func(t *testing.T) { 661 test := creator.create(t) 662 663 ks := New() 664 ks.newKubeClient = func() (kubernetes.Interface, error) { return test.client, nil } 665 666 require.True(t, ks.Init()) 667 require.True(t, ks.Check()) 668 defer ks.Cleanup() 669 670 for i, executeStep := range test.steps { 671 if i == 0 { 672 _ = ks.Collect() 673 time.Sleep(ks.initDelay) 674 } else { 675 time.Sleep(time.Second) 676 } 677 executeStep(t, ks) 678 } 679 }) 680 } 681 } 682 683 func newNode(name string) *corev1.Node { 684 return &corev1.Node{ 685 ObjectMeta: metav1.ObjectMeta{ 686 Name: name, 687 CreationTimestamp: metav1.Time{Time: time.Now()}, 688 }, 689 Status: corev1.NodeStatus{ 690 Capacity: corev1.ResourceList{ 691 corev1.ResourceCPU: mustQuantity("4000m"), 692 corev1.ResourceMemory: mustQuantity("4000Mi"), 693 "pods": mustQuantity("110"), 694 }, 695 Allocatable: corev1.ResourceList{ 696 corev1.ResourceCPU: mustQuantity("3500m"), 697 corev1.ResourceMemory: mustQuantity("3500Mi"), 698 "pods": mustQuantity("110"), 699 }, 700 Conditions: []corev1.NodeCondition{ 701 {Type: corev1.NodeReady, Status: corev1.ConditionTrue}, 702 {Type: corev1.NodeMemoryPressure, Status: corev1.ConditionFalse}, 703 {Type: corev1.NodeDiskPressure, Status: corev1.ConditionFalse}, 704 {Type: corev1.NodePIDPressure, Status: corev1.ConditionFalse}, 705 {Type: corev1.NodeNetworkUnavailable, Status: corev1.ConditionFalse}, 706 }, 707 }, 708 } 709 } 710 711 func newPod(nodeName, name string) *corev1.Pod { 712 return &corev1.Pod{ 713 ObjectMeta: metav1.ObjectMeta{ 714 Name: name, 715 Namespace: corev1.NamespaceDefault, 716 CreationTimestamp: metav1.Time{Time: time.Now()}, 717 }, 718 Spec: corev1.PodSpec{ 719 NodeName: nodeName, 720 InitContainers: []corev1.Container{ 721 { 722 Name: "init-container1", 723 Resources: corev1.ResourceRequirements{ 724 Limits: corev1.ResourceList{ 725 corev1.ResourceCPU: mustQuantity("50m"), 726 corev1.ResourceMemory: mustQuantity("50Mi"), 727 }, 728 Requests: corev1.ResourceList{ 729 corev1.ResourceCPU: mustQuantity("10m"), 730 corev1.ResourceMemory: mustQuantity("10Mi"), 731 }, 732 }, 733 }, 734 }, 735 Containers: []corev1.Container{ 736 { 737 Name: "container1", 738 Resources: corev1.ResourceRequirements{ 739 Limits: corev1.ResourceList{ 740 corev1.ResourceCPU: mustQuantity("200m"), 741 corev1.ResourceMemory: mustQuantity("200Mi"), 742 }, 743 Requests: corev1.ResourceList{ 744 corev1.ResourceCPU: mustQuantity("100m"), 745 corev1.ResourceMemory: mustQuantity("100Mi"), 746 }, 747 }, 748 }, 749 { 750 Name: "container2", 751 Resources: corev1.ResourceRequirements{ 752 Limits: corev1.ResourceList{ 753 corev1.ResourceCPU: mustQuantity("200m"), 754 corev1.ResourceMemory: mustQuantity("200Mi")}, 755 Requests: corev1.ResourceList{ 756 corev1.ResourceCPU: mustQuantity("100m"), 757 corev1.ResourceMemory: mustQuantity("100Mi"), 758 }, 759 }, 760 }, 761 }, 762 }, 763 Status: corev1.PodStatus{ 764 Phase: corev1.PodRunning, 765 Conditions: []corev1.PodCondition{ 766 {Type: corev1.PodReady, Status: corev1.ConditionTrue}, 767 {Type: corev1.PodScheduled, Status: corev1.ConditionTrue}, 768 {Type: corev1.PodInitialized, Status: corev1.ConditionTrue}, 769 {Type: corev1.ContainersReady, Status: corev1.ConditionTrue}, 770 }, 771 InitContainerStatuses: []corev1.ContainerStatus{ 772 { 773 Name: "init-container1", 774 State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}}, 775 }, 776 }, 777 ContainerStatuses: []corev1.ContainerStatus{ 778 { 779 Name: "container1", 780 Ready: true, 781 State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}}, 782 }, 783 { 784 Name: "container2", 785 Ready: true, 786 State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}}, 787 }, 788 }, 789 }, 790 } 791 } 792 793 type brokenInfoKubeClient struct { 794 kubernetes.Interface 795 } 796 797 func (kc *brokenInfoKubeClient) Discovery() discovery.DiscoveryInterface { 798 return &brokenInfoDiscovery{kc.Interface.Discovery()} 799 } 800 801 type brokenInfoDiscovery struct { 802 discovery.DiscoveryInterface 803 } 804 805 func (d *brokenInfoDiscovery) ServerVersion() (*version.Info, error) { 806 return nil, errors.New("brokenInfoDiscovery.ServerVersion() error") 807 } 808 809 func calcObsoleteCharts(charts module.Charts) (num int) { 810 for _, c := range charts { 811 if c.Obsolete { 812 num++ 813 } 814 } 815 return num 816 } 817 818 func mustQuantity(s string) apiresource.Quantity { 819 q, err := apiresource.ParseQuantity(s) 820 if err != nil { 821 panic(fmt.Sprintf("fail to create resource quantity: %v", err)) 822 } 823 return q 824 } 825 826 func copyAge(dst, src map[string]int64) { 827 for k, v := range src { 828 if !strings.HasSuffix(k, "_age") { 829 continue 830 } 831 if _, ok := dst[k]; ok { 832 dst[k] = v 833 } 834 } 835 } 836 837 func isLabelValueSet(c *module.Chart, name string) bool { 838 for _, l := range c.Labels { 839 if l.Key == name { 840 return l.Value != "" 841 } 842 } 843 return false 844 }