github.com/argoproj/argo-cd/v3@v3.2.1/controller/cache/info_test.go (about) 1 package cache 2 3 import ( 4 "sort" 5 "testing" 6 7 "github.com/argoproj/gitops-engine/pkg/utils/kube" 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 corev1 "k8s.io/api/core/v1" 11 "k8s.io/apimachinery/pkg/api/resource" 12 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 13 "sigs.k8s.io/yaml" 14 15 "github.com/argoproj/argo-cd/v3/common" 16 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 17 "github.com/argoproj/argo-cd/v3/util/argo/normalizers" 18 "github.com/argoproj/argo-cd/v3/util/errors" 19 ) 20 21 func strToUnstructured(jsonStr string) *unstructured.Unstructured { 22 obj := make(map[string]any) 23 err := yaml.Unmarshal([]byte(jsonStr), &obj) 24 errors.CheckError(err) 25 return &unstructured.Unstructured{Object: obj} 26 } 27 28 var ( 29 testService = strToUnstructured(` 30 apiVersion: v1 31 kind: Service 32 metadata: 33 name: helm-guestbook 34 namespace: default 35 resourceVersion: "123" 36 uid: "4" 37 spec: 38 selector: 39 app: guestbook 40 type: LoadBalancer 41 status: 42 loadBalancer: 43 ingress: 44 - hostname: localhost`) 45 46 testLinkAnnotatedService = strToUnstructured(` 47 apiVersion: v1 48 kind: Service 49 metadata: 50 name: helm-guestbook 51 namespace: default 52 resourceVersion: "123" 53 uid: "4" 54 annotations: 55 link.argocd.argoproj.io/external-link: http://my-grafana.example.com/pre-generated-link 56 spec: 57 selector: 58 app: guestbook 59 type: LoadBalancer 60 status: 61 loadBalancer: 62 ingress: 63 - hostname: localhost`) 64 65 testIngress = strToUnstructured(` 66 apiVersion: extensions/v1beta1 67 kind: Ingress 68 metadata: 69 name: helm-guestbook 70 namespace: default 71 uid: "4" 72 spec: 73 backend: 74 serviceName: not-found-service 75 servicePort: 443 76 rules: 77 - host: helm-guestbook.example.com 78 http: 79 paths: 80 - backend: 81 serviceName: helm-guestbook 82 servicePort: 443 83 path: / 84 - backend: 85 serviceName: helm-guestbook 86 servicePort: https 87 path: / 88 tls: 89 - host: helm-guestbook.example.com 90 secretName: my-tls-secret 91 status: 92 loadBalancer: 93 ingress: 94 - ip: 107.178.210.11`) 95 96 testLinkAnnotatedIngress = strToUnstructured(` 97 apiVersion: extensions/v1beta1 98 kind: Ingress 99 metadata: 100 name: helm-guestbook 101 namespace: default 102 uid: "4" 103 annotations: 104 link.argocd.argoproj.io/external-link: http://my-grafana.example.com/ingress-link 105 spec: 106 backend: 107 serviceName: not-found-service 108 servicePort: 443 109 rules: 110 - host: helm-guestbook.example.com 111 http: 112 paths: 113 - backend: 114 serviceName: helm-guestbook 115 servicePort: 443 116 path: / 117 - backend: 118 serviceName: helm-guestbook 119 servicePort: https 120 path: / 121 tls: 122 - host: helm-guestbook.example.com 123 secretName: my-tls-secret 124 status: 125 loadBalancer: 126 ingress: 127 - ip: 107.178.210.11`) 128 129 testIngressWildCardPath = strToUnstructured(` 130 apiVersion: extensions/v1beta1 131 kind: Ingress 132 metadata: 133 name: helm-guestbook 134 namespace: default 135 uid: "4" 136 spec: 137 backend: 138 serviceName: not-found-service 139 servicePort: 443 140 rules: 141 - host: helm-guestbook.example.com 142 http: 143 paths: 144 - backend: 145 serviceName: helm-guestbook 146 servicePort: 443 147 path: /* 148 - backend: 149 serviceName: helm-guestbook 150 servicePort: https 151 path: /* 152 tls: 153 - host: helm-guestbook.example.com 154 secretName: my-tls-secret 155 status: 156 loadBalancer: 157 ingress: 158 - ip: 107.178.210.11`) 159 160 testIngressWithoutTLS = strToUnstructured(` 161 apiVersion: extensions/v1beta1 162 kind: Ingress 163 metadata: 164 name: helm-guestbook 165 namespace: default 166 uid: "4" 167 spec: 168 backend: 169 serviceName: not-found-service 170 servicePort: 443 171 rules: 172 - host: helm-guestbook.example.com 173 http: 174 paths: 175 - backend: 176 serviceName: helm-guestbook 177 servicePort: 443 178 path: / 179 - backend: 180 serviceName: helm-guestbook 181 servicePort: https 182 path: / 183 status: 184 loadBalancer: 185 ingress: 186 - ip: 107.178.210.11`) 187 188 testIngressNetworkingV1 = strToUnstructured(` 189 apiVersion: networking.k8s.io/v1 190 kind: Ingress 191 metadata: 192 name: helm-guestbook 193 namespace: default 194 uid: "4" 195 spec: 196 backend: 197 service: 198 name: not-found-service 199 port: 200 number: 443 201 rules: 202 - host: helm-guestbook.example.com 203 http: 204 paths: 205 - backend: 206 service: 207 name: helm-guestbook 208 port: 209 number: 443 210 path: / 211 - backend: 212 service: 213 name: helm-guestbook 214 port: 215 name: https 216 path: / 217 tls: 218 - host: helm-guestbook.example.com 219 secretName: my-tls-secret 220 status: 221 loadBalancer: 222 ingress: 223 - ip: 107.178.210.11`) 224 225 testIstioVirtualService = strToUnstructured(` 226 apiVersion: networking.istio.io/v1alpha3 227 kind: VirtualService 228 metadata: 229 name: hello-world 230 namespace: demo 231 spec: 232 http: 233 - match: 234 - uri: 235 prefix: "/1" 236 route: 237 - destination: 238 host: service_full.demo.svc.cluster.local 239 - destination: 240 host: service_namespace.namespace 241 - match: 242 - uri: 243 prefix: "/2" 244 route: 245 - destination: 246 host: service 247 `) 248 249 testIstioServiceEntry = strToUnstructured(` 250 apiVersion: networking.istio.io/v1beta1 251 kind: ServiceEntry 252 metadata: 253 name: echo 254 spec: 255 exportTo: 256 - '*' 257 hosts: 258 - echo.internal 259 location: MESH_INTERNAL 260 ports: 261 - name: http 262 number: 80 263 protocol: HTTP 264 targetPort: 5678 265 resolution: DNS 266 267 workloadSelector: 268 labels: 269 app.kubernetes.io/name: echo-2 270 `) 271 ) 272 273 // These tests are equivalent to tests in ui/src/app/applications/components/utils.test.tsx. If you update tests here, 274 // please make sure to update the equivalent tests in the UI. 275 func TestGetPodInfo(t *testing.T) { 276 t.Parallel() 277 278 t.Run("TestGetPodInfo", func(t *testing.T) { 279 t.Parallel() 280 281 pod := strToUnstructured(` 282 apiVersion: v1 283 kind: Pod 284 metadata: 285 name: helm-guestbook-pod 286 namespace: default 287 ownerReferences: 288 - apiVersion: extensions/v1beta1 289 kind: ReplicaSet 290 name: helm-guestbook-rs 291 resourceVersion: "123" 292 labels: 293 app: guestbook 294 spec: 295 nodeName: minikube 296 containers: 297 - image: bar 298 resources: 299 requests: 300 memory: 128Mi 301 `) 302 303 info := &ResourceInfo{} 304 populateNodeInfo(pod, info, []string{}) 305 assert.Equal(t, []v1alpha1.InfoItem{ 306 {Name: "Node", Value: "minikube"}, 307 {Name: "Containers", Value: "0/1"}, 308 {Name: common.PodRequestsCPU, Value: "0"}, // strings imported from common 309 {Name: common.PodRequestsMEM, Value: "134217728000"}, 310 }, info.Info) 311 assert.Equal(t, []string{"bar"}, info.Images) 312 assert.Equal(t, &PodInfo{ 313 NodeName: "minikube", 314 ResourceRequests: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("128Mi")}, 315 }, info.PodInfo) 316 assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{Labels: map[string]string{"app": "guestbook"}}, info.NetworkingInfo) 317 }) 318 319 t.Run("TestGetPodWithInitialContainerInfo", func(t *testing.T) { 320 pod := strToUnstructured(` 321 apiVersion: "v1" 322 kind: "Pod" 323 metadata: 324 labels: 325 app: "app-with-initial-container" 326 name: "app-with-initial-container-5f46976fdb-vd6rv" 327 namespace: "default" 328 ownerReferences: 329 - apiVersion: "apps/v1" 330 kind: "ReplicaSet" 331 name: "app-with-initial-container-5f46976fdb" 332 spec: 333 containers: 334 - image: "alpine:latest" 335 imagePullPolicy: "Always" 336 name: "app-with-initial-container" 337 initContainers: 338 - image: "alpine:latest" 339 imagePullPolicy: "Always" 340 name: "app-with-initial-container-logshipper" 341 nodeName: "minikube" 342 status: 343 containerStatuses: 344 - image: "alpine:latest" 345 name: "app-with-initial-container" 346 ready: true 347 restartCount: 0 348 started: true 349 state: 350 running: 351 startedAt: "2024-10-08T08:44:25Z" 352 initContainerStatuses: 353 - image: "alpine:latest" 354 name: "app-with-initial-container-logshipper" 355 ready: true 356 restartCount: 0 357 started: false 358 state: 359 terminated: 360 exitCode: 0 361 reason: "Completed" 362 phase: "Running" 363 `) 364 365 info := &ResourceInfo{} 366 populateNodeInfo(pod, info, []string{}) 367 assert.Equal(t, []v1alpha1.InfoItem{ 368 {Name: "Status Reason", Value: "Running"}, 369 {Name: "Node", Value: "minikube"}, 370 {Name: "Containers", Value: "1/1"}, 371 {Name: common.PodRequestsCPU, Value: "0"}, 372 {Name: common.PodRequestsMEM, Value: "0"}, 373 }, info.Info) 374 }) 375 376 t.Run("TestGetPodWithInitialContainerInfoWithResources", func(t *testing.T) { 377 pod := strToUnstructured(` 378 apiVersion: "v1" 379 kind: "Pod" 380 metadata: 381 labels: 382 app: "app-with-initial-container" 383 name: "app-with-initial-container-5f46976fdb-vd6rv" 384 namespace: "default" 385 ownerReferences: 386 - apiVersion: "apps/v1" 387 kind: "ReplicaSet" 388 name: "app-with-initial-container-5f46976fdb" 389 spec: 390 containers: 391 - image: "alpine:latest" 392 imagePullPolicy: "Always" 393 name: "app-with-initial-container" 394 resources: 395 requests: 396 cpu: "100m" 397 memory: "128Mi" 398 limits: 399 cpu: "500m" 400 memory: "512Mi" 401 initContainers: 402 - image: "alpine:latest" 403 imagePullPolicy: "Always" 404 name: "app-with-initial-container-logshipper" 405 resources: 406 requests: 407 cpu: "50m" 408 memory: "64Mi" 409 limits: 410 cpu: "250m" 411 memory: "256Mi" 412 nodeName: "minikube" 413 status: 414 containerStatuses: 415 - image: "alpine:latest" 416 name: "app-with-initial-container" 417 ready: true 418 restartCount: 0 419 started: true 420 state: 421 running: 422 startedAt: "2024-10-08T08:44:25Z" 423 initContainerStatuses: 424 - image: "alpine:latest" 425 name: "app-with-initial-container-logshipper" 426 ready: true 427 restartCount: 0 428 started: false 429 state: 430 terminated: 431 exitCode: 0 432 reason: "Completed" 433 phase: "Running" 434 `) 435 436 info := &ResourceInfo{} 437 populateNodeInfo(pod, info, []string{}) 438 assert.Equal(t, []v1alpha1.InfoItem{ 439 {Name: "Status Reason", Value: "Running"}, 440 {Name: "Node", Value: "minikube"}, 441 {Name: "Containers", Value: "1/1"}, 442 {Name: common.PodRequestsCPU, Value: "100"}, 443 {Name: common.PodRequestsMEM, Value: "134217728000"}, 444 }, info.Info) 445 }) 446 t.Run("TestGetPodInfoWithSidecar", func(t *testing.T) { 447 t.Parallel() 448 449 pod := strToUnstructured(` 450 apiVersion: v1 451 kind: Pod 452 metadata: 453 labels: 454 app: app-with-sidecar 455 name: app-with-sidecar-6664cc788c-lqlrp 456 namespace: default 457 ownerReferences: 458 - apiVersion: apps/v1 459 kind: ReplicaSet 460 name: app-with-sidecar-6664cc788c 461 spec: 462 containers: 463 - image: 'docker.m.daocloud.io/library/alpine:latest' 464 imagePullPolicy: Always 465 name: app-with-sidecar 466 initContainers: 467 - image: 'docker.m.daocloud.io/library/alpine:latest' 468 imagePullPolicy: Always 469 name: logshipper 470 restartPolicy: Always 471 nodeName: minikube 472 status: 473 containerStatuses: 474 - image: 'docker.m.daocloud.io/library/alpine:latest' 475 name: app-with-sidecar 476 ready: true 477 restartCount: 0 478 started: true 479 state: 480 running: 481 startedAt: '2024-10-08T08:39:43Z' 482 initContainerStatuses: 483 - image: 'docker.m.daocloud.io/library/alpine:latest' 484 name: logshipper 485 ready: true 486 restartCount: 0 487 started: true 488 state: 489 running: 490 startedAt: '2024-10-08T08:39:40Z' 491 phase: Running 492 `) 493 494 info := &ResourceInfo{} 495 populateNodeInfo(pod, info, []string{}) 496 assert.Equal(t, []v1alpha1.InfoItem{ 497 {Name: "Status Reason", Value: "Running"}, 498 {Name: "Node", Value: "minikube"}, 499 {Name: "Containers", Value: "2/2"}, 500 {Name: common.PodRequestsCPU, Value: "0"}, 501 {Name: common.PodRequestsMEM, Value: "0"}, 502 }, info.Info) 503 }) 504 505 t.Run("TestGetPodInfoWithInitialContainer", func(t *testing.T) { 506 t.Parallel() 507 508 pod := strToUnstructured(` 509 apiVersion: v1 510 kind: Pod 511 metadata: 512 generateName: myapp-long-exist-56b7d8794d- 513 labels: 514 app: myapp-long-exist 515 name: myapp-long-exist-56b7d8794d-pbgrd 516 namespace: linghao 517 ownerReferences: 518 - apiVersion: apps/v1 519 kind: ReplicaSet 520 name: myapp-long-exist-56b7d8794d 521 spec: 522 containers: 523 - image: alpine:latest 524 imagePullPolicy: Always 525 name: myapp-long-exist 526 initContainers: 527 - image: alpine:latest 528 imagePullPolicy: Always 529 name: myapp-long-exist-logshipper 530 nodeName: minikube 531 status: 532 containerStatuses: 533 - image: alpine:latest 534 name: myapp-long-exist 535 ready: false 536 restartCount: 0 537 started: false 538 state: 539 waiting: 540 reason: PodInitializing 541 initContainerStatuses: 542 - image: alpine:latest 543 name: myapp-long-exist-logshipper 544 ready: false 545 restartCount: 0 546 started: true 547 state: 548 running: 549 startedAt: '2024-10-09T08:03:45Z' 550 phase: Pending 551 startTime: '2024-10-09T08:02:39Z' 552 `) 553 554 info := &ResourceInfo{} 555 populateNodeInfo(pod, info, []string{}) 556 assert.Equal(t, []v1alpha1.InfoItem{ 557 {Name: "Status Reason", Value: "Init:0/1"}, 558 {Name: "Node", Value: "minikube"}, 559 {Name: "Containers", Value: "0/1"}, 560 {Name: common.PodRequestsCPU, Value: "0"}, 561 {Name: common.PodRequestsMEM, Value: "0"}, 562 }, info.Info) 563 }) 564 565 // Test pod has 2 restartable init containers, the first one running but not started. 566 t.Run("TestGetPodInfoWithRestartableInitContainer", func(t *testing.T) { 567 t.Parallel() 568 569 pod := strToUnstructured(` 570 apiVersion: v1 571 kind: Pod 572 metadata: 573 name: test1 574 spec: 575 initContainers: 576 - name: restartable-init-1 577 restartPolicy: Always 578 - name: restartable-init-2 579 restartPolicy: Always 580 containers: 581 - name: container 582 nodeName: minikube 583 status: 584 phase: Pending 585 initContainerStatuses: 586 - name: restartable-init-1 587 ready: false 588 restartCount: 3 589 state: 590 running: {} 591 started: false 592 lastTerminationState: 593 terminated: 594 finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time 595 - name: restartable-init-2 596 ready: false 597 state: 598 waiting: {} 599 started: false 600 containerStatuses: 601 - ready: false 602 restartCount: 0 603 state: 604 waiting: {} 605 conditions: 606 - type: ContainersReady 607 status: "False" 608 - type: Initialized 609 status: "False" 610 `) 611 612 info := &ResourceInfo{} 613 populateNodeInfo(pod, info, []string{}) 614 assert.Equal(t, []v1alpha1.InfoItem{ 615 {Name: "Status Reason", Value: "Init:0/2"}, 616 {Name: "Node", Value: "minikube"}, 617 {Name: "Containers", Value: "0/3"}, 618 {Name: "Restart Count", Value: "3"}, 619 {Name: common.PodRequestsCPU, Value: "0"}, 620 {Name: common.PodRequestsMEM, Value: "0"}, 621 }, info.Info) 622 }) 623 624 // Test pod has 2 restartable init containers, the first one started and the second one running but not started. 625 t.Run("TestGetPodInfoWithPartiallyStartedInitContainers", func(t *testing.T) { 626 t.Parallel() 627 628 pod := strToUnstructured(` 629 apiVersion: v1 630 kind: Pod 631 metadata: 632 name: test1 633 spec: 634 initContainers: 635 - name: restartable-init-1 636 restartPolicy: Always 637 - name: restartable-init-2 638 restartPolicy: Always 639 containers: 640 - name: container 641 nodeName: minikube 642 status: 643 phase: Pending 644 initContainerStatuses: 645 - name: restartable-init-1 646 ready: false 647 restartCount: 3 648 state: 649 running: {} 650 started: true 651 lastTerminationState: 652 terminated: 653 finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time 654 - name: restartable-init-2 655 ready: false 656 state: 657 running: {} 658 started: false 659 containerStatuses: 660 - ready: false 661 restartCount: 0 662 state: 663 waiting: {} 664 conditions: 665 - type: ContainersReady 666 status: "False" 667 - type: Initialized 668 status: "False" 669 `) 670 671 info := &ResourceInfo{} 672 populateNodeInfo(pod, info, []string{}) 673 assert.Equal(t, []v1alpha1.InfoItem{ 674 {Name: "Status Reason", Value: "Init:1/2"}, 675 {Name: "Node", Value: "minikube"}, 676 {Name: "Containers", Value: "0/3"}, 677 {Name: "Restart Count", Value: "3"}, 678 {Name: common.PodRequestsCPU, Value: "0"}, 679 {Name: common.PodRequestsMEM, Value: "0"}, 680 }, info.Info) 681 }) 682 683 // Test pod has 2 restartable init containers started and 1 container running 684 t.Run("TestGetPodInfoWithStartedInitContainers", func(t *testing.T) { 685 t.Parallel() 686 687 pod := strToUnstructured(` 688 apiVersion: v1 689 kind: Pod 690 metadata: 691 name: test2 692 spec: 693 initContainers: 694 - name: restartable-init-1 695 restartPolicy: Always 696 - name: restartable-init-2 697 restartPolicy: Always 698 containers: 699 - name: container 700 nodeName: minikube 701 status: 702 phase: Running 703 initContainerStatuses: 704 - name: restartable-init-1 705 ready: false 706 restartCount: 3 707 state: 708 running: {} 709 started: true 710 lastTerminationState: 711 terminated: 712 finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time 713 - name: restartable-init-2 714 ready: false 715 state: 716 running: {} 717 started: true 718 containerStatuses: 719 - ready: true 720 restartCount: 4 721 state: 722 running: {} 723 lastTerminationState: 724 terminated: 725 finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time 726 conditions: 727 - type: ContainersReady 728 status: "False" 729 - type: Initialized 730 status: "True" 731 `) 732 733 info := &ResourceInfo{} 734 populateNodeInfo(pod, info, []string{}) 735 assert.Equal(t, []v1alpha1.InfoItem{ 736 {Name: "Status Reason", Value: "Running"}, 737 {Name: "Node", Value: "minikube"}, 738 {Name: "Containers", Value: "1/3"}, 739 {Name: "Restart Count", Value: "7"}, 740 {Name: common.PodRequestsCPU, Value: "0"}, 741 {Name: common.PodRequestsMEM, Value: "0"}, 742 }, info.Info) 743 }) 744 745 // Test pod has 1 init container restarting and 1 container not running 746 t.Run("TestGetPodInfoWithNormalInitContainer", func(t *testing.T) { 747 t.Parallel() 748 749 pod := strToUnstructured(` 750 apiVersion: v1 751 kind: Pod 752 metadata: 753 name: test7 754 spec: 755 initContainers: 756 - name: init-container 757 containers: 758 - name: main-container 759 nodeName: minikube 760 status: 761 phase: podPhase 762 initContainerStatuses: 763 - ready: false 764 restartCount: 3 765 state: 766 running: {} 767 lastTerminationState: 768 terminated: 769 finishedAt: "2023-10-01T00:00:00Z" # Replace with the actual time 770 containerStatuses: 771 - ready: false 772 restartCount: 0 773 state: 774 waiting: {} 775 `) 776 777 info := &ResourceInfo{} 778 populateNodeInfo(pod, info, []string{}) 779 assert.Equal(t, []v1alpha1.InfoItem{ 780 {Name: "Status Reason", Value: "Init:0/1"}, 781 {Name: "Node", Value: "minikube"}, 782 {Name: "Containers", Value: "0/1"}, 783 {Name: "Restart Count", Value: "3"}, 784 {Name: common.PodRequestsCPU, Value: "0"}, 785 {Name: common.PodRequestsMEM, Value: "0"}, 786 }, info.Info) 787 }) 788 789 // Test pod condition succeed 790 t.Run("TestPodConditionSucceeded", func(t *testing.T) { 791 t.Parallel() 792 793 pod := strToUnstructured(` 794 apiVersion: v1 795 kind: Pod 796 metadata: 797 name: test8 798 spec: 799 nodeName: minikube 800 containers: 801 - name: container 802 status: 803 phase: Succeeded 804 containerStatuses: 805 - ready: false 806 restartCount: 0 807 state: 808 terminated: 809 reason: Completed 810 exitCode: 0 811 `) 812 info := &ResourceInfo{} 813 populateNodeInfo(pod, info, []string{}) 814 assert.Equal(t, []v1alpha1.InfoItem{ 815 {Name: "Status Reason", Value: "Completed"}, 816 {Name: "Node", Value: "minikube"}, 817 {Name: "Containers", Value: "0/1"}, 818 }, info.Info) 819 }) 820 821 // Test pod condition succeed which had some allocated resources 822 t.Run("TestPodConditionSucceededWithResources", func(t *testing.T) { 823 t.Parallel() 824 825 pod := strToUnstructured(` 826 apiVersion: v1 827 kind: Pod 828 metadata: 829 name: test8 830 spec: 831 nodeName: minikube 832 containers: 833 - name: container 834 resources: 835 requests: 836 cpu: "50m" 837 memory: "64Mi" 838 limits: 839 cpu: "250m" 840 memory: "256Mi" 841 status: 842 phase: Succeeded 843 containerStatuses: 844 - ready: false 845 restartCount: 0 846 state: 847 terminated: 848 reason: Completed 849 exitCode: 0 850 `) 851 info := &ResourceInfo{} 852 populateNodeInfo(pod, info, []string{}) 853 assert.Equal(t, []v1alpha1.InfoItem{ 854 {Name: "Status Reason", Value: "Completed"}, 855 {Name: "Node", Value: "minikube"}, 856 {Name: "Containers", Value: "0/1"}, 857 }, info.Info) 858 }) 859 860 // Test pod condition failed 861 t.Run("TestPodConditionFailed", func(t *testing.T) { 862 t.Parallel() 863 864 pod := strToUnstructured(` 865 apiVersion: v1 866 kind: Pod 867 metadata: 868 name: test9 869 spec: 870 nodeName: minikube 871 containers: 872 - name: container 873 status: 874 phase: Failed 875 containerStatuses: 876 - ready: false 877 restartCount: 0 878 state: 879 terminated: 880 reason: Error 881 exitCode: 1 882 `) 883 info := &ResourceInfo{} 884 populateNodeInfo(pod, info, []string{}) 885 assert.Equal(t, []v1alpha1.InfoItem{ 886 {Name: "Status Reason", Value: "Error"}, 887 {Name: "Node", Value: "minikube"}, 888 {Name: "Containers", Value: "0/1"}, 889 }, info.Info) 890 }) 891 892 // Test pod condition failed with allocated resources 893 894 t.Run("TestPodConditionFailedWithResources", func(t *testing.T) { 895 t.Parallel() 896 897 pod := strToUnstructured(` 898 apiVersion: v1 899 kind: Pod 900 metadata: 901 name: test9 902 spec: 903 nodeName: minikube 904 containers: 905 - name: container 906 resources: 907 requests: 908 cpu: "50m" 909 memory: "64Mi" 910 limits: 911 cpu: "250m" 912 memory: "256Mi" 913 status: 914 phase: Failed 915 containerStatuses: 916 - ready: false 917 restartCount: 0 918 state: 919 terminated: 920 reason: Error 921 exitCode: 1 922 `) 923 info := &ResourceInfo{} 924 populateNodeInfo(pod, info, []string{}) 925 assert.Equal(t, []v1alpha1.InfoItem{ 926 {Name: "Status Reason", Value: "Error"}, 927 {Name: "Node", Value: "minikube"}, 928 {Name: "Containers", Value: "0/1"}, 929 }, info.Info) 930 }) 931 932 // Test pod condition succeed with deletion 933 t.Run("TestPodConditionSucceededWithDeletion", func(t *testing.T) { 934 t.Parallel() 935 936 pod := strToUnstructured(` 937 apiVersion: v1 938 kind: Pod 939 metadata: 940 name: test10 941 deletionTimestamp: "2023-10-01T00:00:00Z" 942 spec: 943 nodeName: minikube 944 containers: 945 - name: container 946 status: 947 phase: Succeeded 948 containerStatuses: 949 - ready: false 950 restartCount: 0 951 state: 952 terminated: 953 reason: Completed 954 exitCode: 0 955 `) 956 info := &ResourceInfo{} 957 populateNodeInfo(pod, info, []string{}) 958 assert.Equal(t, []v1alpha1.InfoItem{ 959 {Name: "Status Reason", Value: "Completed"}, 960 {Name: "Node", Value: "minikube"}, 961 {Name: "Containers", Value: "0/1"}, 962 }, info.Info) 963 }) 964 965 // Test pod condition running with deletion 966 t.Run("TestPodConditionRunningWithDeletion", func(t *testing.T) { 967 t.Parallel() 968 969 pod := strToUnstructured(` 970 apiVersion: v1 971 kind: Pod 972 metadata: 973 name: test11 974 deletionTimestamp: "2023-10-01T00:00:00Z" 975 spec: 976 nodeName: minikube 977 containers: 978 - name: container 979 status: 980 phase: Running 981 containerStatuses: 982 - ready: false 983 restartCount: 0 984 state: 985 running: {} 986 `) 987 info := &ResourceInfo{} 988 populateNodeInfo(pod, info, []string{}) 989 assert.Equal(t, []v1alpha1.InfoItem{ 990 {Name: "Status Reason", Value: "Terminating"}, 991 {Name: "Node", Value: "minikube"}, 992 {Name: "Containers", Value: "0/1"}, 993 {Name: common.PodRequestsCPU, Value: "0"}, 994 {Name: common.PodRequestsMEM, Value: "0"}, 995 }, info.Info) 996 }) 997 998 // Test pod condition pending with deletion 999 t.Run("TestPodConditionPendingWithDeletion", func(t *testing.T) { 1000 t.Parallel() 1001 1002 pod := strToUnstructured(` 1003 apiVersion: v1 1004 kind: Pod 1005 metadata: 1006 name: test12 1007 deletionTimestamp: "2023-10-01T00:00:00Z" 1008 spec: 1009 nodeName: minikube 1010 containers: 1011 - name: container 1012 status: 1013 phase: Pending 1014 `) 1015 info := &ResourceInfo{} 1016 populateNodeInfo(pod, info, []string{}) 1017 assert.Equal(t, []v1alpha1.InfoItem{ 1018 {Name: "Status Reason", Value: "Terminating"}, 1019 {Name: "Node", Value: "minikube"}, 1020 {Name: "Containers", Value: "0/1"}, 1021 {Name: common.PodRequestsCPU, Value: "0"}, 1022 {Name: common.PodRequestsMEM, Value: "0"}, 1023 }, info.Info) 1024 }) 1025 1026 // Test PodScheduled condition with reason SchedulingGated 1027 t.Run("TestPodScheduledWithSchedulingGated", func(t *testing.T) { 1028 t.Parallel() 1029 1030 pod := strToUnstructured(` 1031 apiVersion: v1 1032 kind: Pod 1033 metadata: 1034 name: test13 1035 spec: 1036 nodeName: minikube 1037 containers: 1038 - name: container1 1039 - name: container2 1040 status: 1041 phase: podPhase 1042 conditions: 1043 - type: PodScheduled 1044 status: "False" 1045 reason: SchedulingGated 1046 `) 1047 info := &ResourceInfo{} 1048 populateNodeInfo(pod, info, []string{}) 1049 assert.Equal(t, []v1alpha1.InfoItem{ 1050 {Name: "Status Reason", Value: "SchedulingGated"}, 1051 {Name: "Node", Value: "minikube"}, 1052 {Name: "Containers", Value: "0/2"}, 1053 {Name: common.PodRequestsCPU, Value: "0"}, 1054 {Name: common.PodRequestsMEM, Value: "0"}, 1055 }, info.Info) 1056 }) 1057 } 1058 1059 func TestGetNodeInfo(t *testing.T) { 1060 node := strToUnstructured(` 1061 apiVersion: v1 1062 kind: Node 1063 metadata: 1064 name: minikube 1065 labels: 1066 foo: bar 1067 spec: {} 1068 status: 1069 capacity: 1070 cpu: "6" 1071 memory: 6091320Ki 1072 nodeInfo: 1073 architecture: amd64 1074 operatingSystem: linux 1075 osImage: Ubuntu 20.04 LTS 1076 `) 1077 1078 info := &ResourceInfo{} 1079 populateNodeInfo(node, info, []string{}) 1080 assert.Equal(t, &NodeInfo{ 1081 Name: "minikube", 1082 Capacity: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("6091320Ki"), corev1.ResourceCPU: resource.MustParse("6")}, 1083 SystemInfo: corev1.NodeSystemInfo{Architecture: "amd64", OperatingSystem: "linux", OSImage: "Ubuntu 20.04 LTS"}, 1084 Labels: map[string]string{"foo": "bar"}, 1085 }, info.NodeInfo) 1086 } 1087 1088 func TestGetServiceInfo(t *testing.T) { 1089 info := &ResourceInfo{} 1090 populateNodeInfo(testService, info, []string{}) 1091 assert.Empty(t, info.Info) 1092 assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ 1093 TargetLabels: map[string]string{"app": "guestbook"}, 1094 Ingress: []corev1.LoadBalancerIngress{{Hostname: "localhost"}}, 1095 }, info.NetworkingInfo) 1096 } 1097 1098 func TestGetLinkAnnotatedServiceInfo(t *testing.T) { 1099 info := &ResourceInfo{} 1100 populateNodeInfo(testLinkAnnotatedService, info, []string{}) 1101 assert.Empty(t, info.Info) 1102 assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ 1103 TargetLabels: map[string]string{"app": "guestbook"}, 1104 Ingress: []corev1.LoadBalancerIngress{{Hostname: "localhost"}}, 1105 ExternalURLs: []string{"http://my-grafana.example.com/pre-generated-link"}, 1106 }, info.NetworkingInfo) 1107 } 1108 1109 func TestGetIstioVirtualServiceInfo(t *testing.T) { 1110 info := &ResourceInfo{} 1111 populateNodeInfo(testIstioVirtualService, info, []string{}) 1112 assert.Empty(t, info.Info) 1113 require.NotNil(t, info.NetworkingInfo) 1114 require.NotNil(t, info.NetworkingInfo.TargetRefs) 1115 assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ 1116 Kind: kube.ServiceKind, 1117 Name: "service_full", 1118 Namespace: "demo", 1119 }) 1120 assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ 1121 Kind: kube.ServiceKind, 1122 Name: "service_namespace", 1123 Namespace: "namespace", 1124 }) 1125 assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ 1126 Kind: kube.ServiceKind, 1127 Name: "service", 1128 Namespace: "demo", 1129 }) 1130 } 1131 1132 func TestGetIstioServiceEntryInfo(t *testing.T) { 1133 info := &ResourceInfo{} 1134 populateNodeInfo(testIstioServiceEntry, info, []string{}) 1135 assert.Empty(t, info.Info) 1136 require.NotNil(t, info.NetworkingInfo) 1137 require.NotNil(t, info.NetworkingInfo.TargetRefs) 1138 assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{ 1139 Kind: kube.PodKind, 1140 }) 1141 1142 assert.Equal(t, map[string]string{ 1143 "app.kubernetes.io/name": "echo-2", 1144 }, info.NetworkingInfo.TargetLabels) 1145 } 1146 1147 func TestGetIngressInfo(t *testing.T) { 1148 tests := []struct { 1149 Ingress *unstructured.Unstructured 1150 }{ 1151 {testIngress}, 1152 {testIngressNetworkingV1}, 1153 } 1154 for _, tc := range tests { 1155 info := &ResourceInfo{} 1156 populateNodeInfo(tc.Ingress, info, []string{}) 1157 assert.Empty(t, info.Info) 1158 sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool { 1159 return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name 1160 }) 1161 assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ 1162 Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}}, 1163 TargetRefs: []v1alpha1.ResourceRef{{ 1164 Namespace: "default", 1165 Group: "", 1166 Kind: kube.ServiceKind, 1167 Name: "helm-guestbook", 1168 }, { 1169 Namespace: "default", 1170 Group: "", 1171 Kind: kube.ServiceKind, 1172 Name: "not-found-service", 1173 }}, 1174 ExternalURLs: []string{"https://helm-guestbook.example.com/"}, 1175 }, info.NetworkingInfo) 1176 } 1177 } 1178 1179 func TestGetLinkAnnotatedIngressInfo(t *testing.T) { 1180 info := &ResourceInfo{} 1181 populateNodeInfo(testLinkAnnotatedIngress, info, []string{}) 1182 assert.Empty(t, info.Info) 1183 sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool { 1184 return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name 1185 }) 1186 assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ 1187 Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}}, 1188 TargetRefs: []v1alpha1.ResourceRef{{ 1189 Namespace: "default", 1190 Group: "", 1191 Kind: kube.ServiceKind, 1192 Name: "helm-guestbook", 1193 }, { 1194 Namespace: "default", 1195 Group: "", 1196 Kind: kube.ServiceKind, 1197 Name: "not-found-service", 1198 }}, 1199 ExternalURLs: []string{"http://my-grafana.example.com/ingress-link", "https://helm-guestbook.example.com/"}, 1200 }, info.NetworkingInfo) 1201 } 1202 1203 func TestGetIngressInfoWildCardPath(t *testing.T) { 1204 info := &ResourceInfo{} 1205 populateNodeInfo(testIngressWildCardPath, info, []string{}) 1206 assert.Empty(t, info.Info) 1207 sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool { 1208 return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name 1209 }) 1210 assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ 1211 Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}}, 1212 TargetRefs: []v1alpha1.ResourceRef{{ 1213 Namespace: "default", 1214 Group: "", 1215 Kind: kube.ServiceKind, 1216 Name: "helm-guestbook", 1217 }, { 1218 Namespace: "default", 1219 Group: "", 1220 Kind: kube.ServiceKind, 1221 Name: "not-found-service", 1222 }}, 1223 ExternalURLs: []string{"https://helm-guestbook.example.com/"}, 1224 }, info.NetworkingInfo) 1225 } 1226 1227 func TestGetIngressInfoWithoutTls(t *testing.T) { 1228 info := &ResourceInfo{} 1229 populateNodeInfo(testIngressWithoutTLS, info, []string{}) 1230 assert.Empty(t, info.Info) 1231 sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool { 1232 return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name 1233 }) 1234 assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ 1235 Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}}, 1236 TargetRefs: []v1alpha1.ResourceRef{{ 1237 Namespace: "default", 1238 Group: "", 1239 Kind: kube.ServiceKind, 1240 Name: "helm-guestbook", 1241 }, { 1242 Namespace: "default", 1243 Group: "", 1244 Kind: kube.ServiceKind, 1245 Name: "not-found-service", 1246 }}, 1247 ExternalURLs: []string{"http://helm-guestbook.example.com/"}, 1248 }, info.NetworkingInfo) 1249 } 1250 1251 func TestGetIngressInfoWithHost(t *testing.T) { 1252 ingress := strToUnstructured(` 1253 apiVersion: extensions/v1beta1 1254 kind: Ingress 1255 metadata: 1256 name: helm-guestbook 1257 namespace: default 1258 spec: 1259 rules: 1260 - http: 1261 paths: 1262 - backend: 1263 serviceName: helm-guestbook 1264 servicePort: 443 1265 path: / 1266 tls: 1267 - secretName: my-tls 1268 status: 1269 loadBalancer: 1270 ingress: 1271 - ip: 107.178.210.11`) 1272 1273 info := &ResourceInfo{} 1274 populateNodeInfo(ingress, info, []string{}) 1275 1276 assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ 1277 Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}}, 1278 TargetRefs: []v1alpha1.ResourceRef{{ 1279 Namespace: "default", 1280 Group: "", 1281 Kind: kube.ServiceKind, 1282 Name: "helm-guestbook", 1283 }}, 1284 ExternalURLs: []string{"https://107.178.210.11/"}, 1285 }, info.NetworkingInfo) 1286 } 1287 1288 func TestGetIngressInfoNoHost(t *testing.T) { 1289 ingress := strToUnstructured(` 1290 apiVersion: extensions/v1beta1 1291 kind: Ingress 1292 metadata: 1293 name: helm-guestbook 1294 namespace: default 1295 spec: 1296 rules: 1297 - http: 1298 paths: 1299 - backend: 1300 serviceName: helm-guestbook 1301 servicePort: 443 1302 path: / 1303 tls: 1304 - secretName: my-tls 1305 `) 1306 1307 info := &ResourceInfo{} 1308 populateNodeInfo(ingress, info, []string{}) 1309 1310 assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{ 1311 TargetRefs: []v1alpha1.ResourceRef{{ 1312 Namespace: "default", 1313 Group: "", 1314 Kind: kube.ServiceKind, 1315 Name: "helm-guestbook", 1316 }}, 1317 }, info.NetworkingInfo) 1318 assert.Empty(t, info.NetworkingInfo.ExternalURLs) 1319 } 1320 1321 func TestExternalUrlWithSubPath(t *testing.T) { 1322 ingress := strToUnstructured(` 1323 apiVersion: networking.k8s.io/v1 1324 kind: Ingress 1325 metadata: 1326 name: helm-guestbook 1327 namespace: default 1328 spec: 1329 rules: 1330 - http: 1331 paths: 1332 - backend: 1333 serviceName: helm-guestbook 1334 servicePort: 443 1335 path: /my/sub/path/ 1336 tls: 1337 - secretName: my-tls 1338 status: 1339 loadBalancer: 1340 ingress: 1341 - ip: 107.178.210.11`) 1342 1343 info := &ResourceInfo{} 1344 populateNodeInfo(ingress, info, []string{}) 1345 1346 expectedExternalUrls := []string{"https://107.178.210.11/my/sub/path/"} 1347 assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs) 1348 } 1349 1350 func TestExternalUrlWithMultipleSubPaths(t *testing.T) { 1351 ingress := strToUnstructured(` 1352 apiVersion: networking.k8s.io/v1 1353 kind: Ingress 1354 metadata: 1355 name: helm-guestbook 1356 namespace: default 1357 spec: 1358 rules: 1359 - host: helm-guestbook.example.com 1360 http: 1361 paths: 1362 - backend: 1363 serviceName: helm-guestbook 1364 servicePort: 443 1365 path: /my/sub/path/ 1366 - backend: 1367 serviceName: helm-guestbook-2 1368 servicePort: 443 1369 path: /my/sub/path/2 1370 - backend: 1371 serviceName: helm-guestbook-3 1372 servicePort: 443 1373 tls: 1374 - secretName: my-tls 1375 status: 1376 loadBalancer: 1377 ingress: 1378 - ip: 107.178.210.11`) 1379 1380 info := &ResourceInfo{} 1381 populateNodeInfo(ingress, info, []string{}) 1382 1383 expectedExternalUrls := []string{"https://helm-guestbook.example.com/my/sub/path/", "https://helm-guestbook.example.com/my/sub/path/2", "https://helm-guestbook.example.com"} 1384 actualURLs := info.NetworkingInfo.ExternalURLs 1385 sort.Strings(expectedExternalUrls) 1386 sort.Strings(actualURLs) 1387 assert.Equal(t, expectedExternalUrls, actualURLs) 1388 } 1389 1390 func TestExternalUrlWithNoSubPath(t *testing.T) { 1391 ingress := strToUnstructured(` 1392 apiVersion: networking.k8s.io/v1 1393 kind: Ingress 1394 metadata: 1395 name: helm-guestbook 1396 namespace: default 1397 spec: 1398 rules: 1399 - http: 1400 paths: 1401 - backend: 1402 serviceName: helm-guestbook 1403 servicePort: 443 1404 tls: 1405 - secretName: my-tls 1406 status: 1407 loadBalancer: 1408 ingress: 1409 - ip: 107.178.210.11`) 1410 1411 info := &ResourceInfo{} 1412 populateNodeInfo(ingress, info, []string{}) 1413 1414 expectedExternalUrls := []string{"https://107.178.210.11"} 1415 assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs) 1416 } 1417 1418 func TestExternalUrlWithNetworkingApi(t *testing.T) { 1419 ingress := strToUnstructured(` 1420 apiVersion: networking.k8s.io/v1beta1 1421 kind: Ingress 1422 metadata: 1423 name: helm-guestbook 1424 namespace: default 1425 spec: 1426 rules: 1427 - http: 1428 paths: 1429 - backend: 1430 serviceName: helm-guestbook 1431 servicePort: 443 1432 tls: 1433 - secretName: my-tls 1434 status: 1435 loadBalancer: 1436 ingress: 1437 - ip: 107.178.210.11`) 1438 1439 info := &ResourceInfo{} 1440 populateNodeInfo(ingress, info, []string{}) 1441 1442 expectedExternalUrls := []string{"https://107.178.210.11"} 1443 assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs) 1444 } 1445 1446 func TestCustomLabel(t *testing.T) { 1447 configmap := strToUnstructured(` 1448 apiVersion: v1 1449 kind: ConfigMap 1450 metadata: 1451 name: cm`) 1452 1453 info := &ResourceInfo{} 1454 populateNodeInfo(configmap, info, []string{"my-label"}) 1455 1456 assert.Empty(t, info.Info) 1457 1458 configmap = strToUnstructured(` 1459 apiVersion: v1 1460 kind: ConfigMap 1461 metadata: 1462 name: cm 1463 labels: 1464 my-label: value`) 1465 1466 info = &ResourceInfo{} 1467 populateNodeInfo(configmap, info, []string{"my-label", "other-label"}) 1468 1469 assert.Len(t, info.Info, 1) 1470 assert.Equal(t, "my-label", info.Info[0].Name) 1471 assert.Equal(t, "value", info.Info[0].Value) 1472 1473 configmap = strToUnstructured(` 1474 apiVersion: v1 1475 kind: ConfigMap 1476 metadata: 1477 name: cm 1478 labels: 1479 my-label: value 1480 other-label: value2`) 1481 1482 info = &ResourceInfo{} 1483 populateNodeInfo(configmap, info, []string{"my-label", "other-label"}) 1484 1485 assert.Len(t, info.Info, 2) 1486 assert.Equal(t, "my-label", info.Info[0].Name) 1487 assert.Equal(t, "value", info.Info[0].Value) 1488 assert.Equal(t, "other-label", info.Info[1].Name) 1489 assert.Equal(t, "value2", info.Info[1].Value) 1490 } 1491 1492 func TestManifestHash(t *testing.T) { 1493 manifest := strToUnstructured(` 1494 apiVersion: v1 1495 kind: Pod 1496 metadata: 1497 name: helm-guestbook-pod 1498 namespace: default 1499 ownerReferences: 1500 - apiVersion: extensions/v1beta1 1501 kind: ReplicaSet 1502 name: helm-guestbook-rs 1503 resourceVersion: "123" 1504 labels: 1505 app: guestbook 1506 spec: 1507 nodeName: minikube 1508 containers: 1509 - image: bar 1510 resources: 1511 requests: 1512 memory: 128Mi 1513 `) 1514 1515 ignores := []v1alpha1.ResourceIgnoreDifferences{ 1516 { 1517 Group: "*", 1518 Kind: "*", 1519 JSONPointers: []string{"/metadata/resourceVersion"}, 1520 }, 1521 } 1522 1523 data, _ := strToUnstructured(` 1524 apiVersion: v1 1525 kind: Pod 1526 metadata: 1527 name: helm-guestbook-pod 1528 namespace: default 1529 ownerReferences: 1530 - apiVersion: extensions/v1beta1 1531 kind: ReplicaSet 1532 name: helm-guestbook-rs 1533 labels: 1534 app: guestbook 1535 spec: 1536 nodeName: minikube 1537 containers: 1538 - image: bar 1539 resources: 1540 requests: 1541 memory: 128Mi 1542 `).MarshalJSON() 1543 1544 expected := hash(data) 1545 1546 hash, err := generateManifestHash(manifest, ignores, nil, normalizers.IgnoreNormalizerOpts{}) 1547 assert.Equal(t, expected, hash) 1548 assert.NoError(t, err) 1549 }