istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/describe/describe_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package describe 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "os" 22 "strings" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 klabels "k8s.io/apimachinery/pkg/labels" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/util/intstr" 31 32 apiannotation "istio.io/api/annotation" 33 v1alpha32 "istio.io/api/networking/v1alpha3" 34 "istio.io/client-go/pkg/apis/networking/v1alpha3" 35 "istio.io/istio/istioctl/pkg/cli" 36 "istio.io/istio/istioctl/pkg/util/configdump" 37 "istio.io/istio/pilot/test/util" 38 "istio.io/istio/pkg/test/util/assert" 39 ) 40 41 // execAndK8sConfigTestCase lets a test case hold some Envoy, Istio, and Kubernetes configuration 42 type execAndK8sConfigTestCase struct { 43 k8sConfigs []runtime.Object // Canned K8s configuration 44 istioConfigs []runtime.Object // Canned Istio configuration 45 configDumps map[string][]byte 46 namespace string 47 istioNamespace string 48 49 args []string 50 51 // Typically use one of the three 52 expectedOutput string // Expected constant output 53 expectedString string // String output is expected to contain 54 55 wantException bool 56 } 57 58 // Tests Pilot /debug 59 func TestDescribe(t *testing.T) { 60 productPageConfigPath := "testdata/describe/http_config.json" 61 config, err := os.ReadFile(productPageConfigPath) 62 if err != nil { 63 t.Fatalf("failed to read %s: %v", productPageConfigPath, err) 64 } 65 cases := []execAndK8sConfigTestCase{ 66 { // case 0 67 args: []string{}, 68 expectedString: "Describe resource and related Istio configuration", 69 }, 70 { // case 2 no pod 71 args: strings.Split("pod", " "), 72 expectedString: "Error: expecting pod name", 73 wantException: true, // "istioctl experimental inspect pod" should fail 74 }, 75 { // case 3 unknown pod 76 args: strings.Split("po not-a-pod", " "), 77 expectedString: "pods \"not-a-pod\" not found", 78 wantException: true, // "istioctl experimental describe pod not-a-pod" should fail 79 }, 80 { // case 8 unknown service 81 args: strings.Split("service not-a-service", " "), 82 expectedString: "services \"not-a-service\" not found", 83 wantException: true, // "istioctl experimental describe service not-a-service" should fail 84 }, 85 { 86 k8sConfigs: []runtime.Object{ 87 &corev1.Service{ 88 ObjectMeta: metav1.ObjectMeta{ 89 Name: "productpage", 90 Namespace: "default", 91 }, 92 Spec: corev1.ServiceSpec{ 93 Selector: map[string]string{ 94 "app": "productpage", 95 }, 96 Ports: []corev1.ServicePort{ 97 { 98 Name: "http", 99 Port: 9080, 100 TargetPort: intstr.FromInt32(9080), 101 }, 102 }, 103 }, 104 }, 105 &corev1.Service{ 106 ObjectMeta: metav1.ObjectMeta{ 107 Name: "ingress", 108 Namespace: "default", 109 Labels: map[string]string{ 110 "istio": "ingressgateway", 111 }, 112 }, 113 Spec: corev1.ServiceSpec{ 114 Selector: map[string]string{ 115 "istio": "ingressgateway", 116 }, 117 Ports: []corev1.ServicePort{ 118 { 119 Name: "http", 120 Port: 80, 121 TargetPort: intstr.FromInt32(80), 122 }, 123 }, 124 }, 125 }, 126 &corev1.Pod{ 127 ObjectMeta: metav1.ObjectMeta{ 128 Name: "productpage-v1-1234567890", 129 Namespace: "default", 130 Labels: map[string]string{ 131 "app": "productpage", 132 }, 133 }, 134 Spec: corev1.PodSpec{ 135 Containers: []corev1.Container{ 136 { 137 Name: "productpage", 138 Ports: []corev1.ContainerPort{ 139 { 140 Name: "http", 141 ContainerPort: 9080, 142 }, 143 }, 144 }, 145 { 146 Name: "istio-proxy", 147 }, 148 }, 149 }, 150 Status: corev1.PodStatus{ 151 Phase: corev1.PodRunning, 152 ContainerStatuses: []corev1.ContainerStatus{ 153 { 154 Name: "istio-proxy", 155 Ready: true, 156 }, 157 }, 158 }, 159 }, 160 &corev1.Pod{ 161 ObjectMeta: metav1.ObjectMeta{ 162 Name: "ingress", 163 Namespace: "default", 164 Labels: map[string]string{ 165 "istio": "ingressgateway", 166 }, 167 }, 168 Spec: corev1.PodSpec{ 169 Containers: []corev1.Container{ 170 { 171 Name: "istio-proxy", 172 }, 173 }, 174 }, 175 Status: corev1.PodStatus{ 176 Phase: corev1.PodRunning, 177 ContainerStatuses: []corev1.ContainerStatus{ 178 { 179 Name: "istio-proxy", 180 Ready: true, 181 }, 182 }, 183 }, 184 }, 185 }, 186 istioConfigs: []runtime.Object{ 187 &v1alpha3.VirtualService{ 188 ObjectMeta: metav1.ObjectMeta{ 189 Name: "bookinfo", 190 Namespace: "default", 191 }, 192 Spec: v1alpha32.VirtualService{ 193 Hosts: []string{"productpage"}, 194 Gateways: []string{"fake-gw"}, 195 Http: []*v1alpha32.HTTPRoute{ 196 { 197 Match: []*v1alpha32.HTTPMatchRequest{ 198 { 199 Uri: &v1alpha32.StringMatch{ 200 MatchType: &v1alpha32.StringMatch_Prefix{ 201 Prefix: "/prefix", 202 }, 203 }, 204 }, 205 }, 206 Route: []*v1alpha32.HTTPRouteDestination{ 207 { 208 Destination: &v1alpha32.Destination{ 209 Host: "productpage", 210 }, 211 Weight: 30, 212 }, 213 { 214 Destination: &v1alpha32.Destination{ 215 Host: "productpage2", 216 }, 217 Weight: 20, 218 }, 219 { 220 Destination: &v1alpha32.Destination{ 221 Host: "productpage3", 222 }, 223 Weight: 50, 224 }, 225 }, 226 }, 227 }, 228 }, 229 }, 230 &v1alpha3.DestinationRule{ 231 ObjectMeta: metav1.ObjectMeta{ 232 Name: "productpage", 233 Namespace: "default", 234 }, 235 Spec: v1alpha32.DestinationRule{ 236 Host: "productpage", 237 Subsets: []*v1alpha32.Subset{ 238 { 239 Name: "v1", 240 Labels: map[string]string{"version": "v1"}, 241 }, 242 }, 243 }, 244 }, 245 }, 246 configDumps: map[string][]byte{ 247 "productpage-v1-1234567890": config, 248 "ingress": []byte("{}"), 249 }, 250 namespace: "default", 251 istioNamespace: "default", 252 // case 9, vs route to multiple hosts 253 args: strings.Split("service productpage", " "), 254 expectedOutput: `Service: productpage 255 DestinationRule: productpage for "productpage" 256 WARNING POD DOES NOT MATCH ANY SUBSETS. (Non matching subsets v1) 257 Matching subsets: 258 (Non-matching subsets v1) 259 No Traffic Policy 260 VirtualService: bookinfo 261 Route to host "productpage" with weight 30% 262 Route to host "productpage2" with weight 20% 263 Route to host "productpage3" with weight 50% 264 Match: /prefix* 265 `, 266 }, 267 { 268 k8sConfigs: []runtime.Object{ 269 &corev1.Service{ 270 ObjectMeta: metav1.ObjectMeta{ 271 Name: "productpage", 272 Namespace: "default", 273 }, 274 Spec: corev1.ServiceSpec{ 275 Selector: map[string]string{ 276 "app": "productpage", 277 }, 278 Ports: []corev1.ServicePort{ 279 { 280 Name: "http", 281 Port: 9080, 282 TargetPort: intstr.FromInt(9080), 283 }, 284 }, 285 }, 286 }, 287 &corev1.Service{ 288 ObjectMeta: metav1.ObjectMeta{ 289 Name: "istio-ingressgateway", 290 Namespace: "default", 291 }, 292 Spec: corev1.ServiceSpec{ 293 Selector: map[string]string{ 294 "istio": "ingressgateway", 295 }, 296 Ports: []corev1.ServicePort{ 297 { 298 Name: "http", 299 Port: 80, 300 TargetPort: intstr.FromInt(80), 301 }, 302 }, 303 }, 304 }, 305 &corev1.Pod{ 306 ObjectMeta: metav1.ObjectMeta{ 307 Name: "productpage-v1-1234567890", 308 Namespace: "default", 309 Labels: map[string]string{ 310 "app": "productpage", 311 }, 312 }, 313 Spec: corev1.PodSpec{ 314 Containers: []corev1.Container{ 315 { 316 Name: "productpage", 317 Ports: []corev1.ContainerPort{ 318 { 319 Name: "http", 320 ContainerPort: 9080, 321 }, 322 }, 323 }, 324 { 325 Name: "istio-proxy", 326 }, 327 }, 328 }, 329 Status: corev1.PodStatus{ 330 Phase: corev1.PodRunning, 331 ContainerStatuses: []corev1.ContainerStatus{ 332 { 333 Name: "istio-proxy", 334 Ready: true, 335 }, 336 }, 337 }, 338 }, 339 &corev1.Pod{ 340 ObjectMeta: metav1.ObjectMeta{ 341 Name: "ingress", 342 Namespace: "default", 343 Labels: map[string]string{ 344 "istio": "ingressgateway", 345 }, 346 }, 347 Spec: corev1.PodSpec{ 348 Containers: []corev1.Container{ 349 { 350 Name: "istio-proxy", 351 }, 352 }, 353 }, 354 Status: corev1.PodStatus{ 355 Phase: corev1.PodRunning, 356 ContainerStatuses: []corev1.ContainerStatus{ 357 { 358 Name: "istio-proxy", 359 Ready: true, 360 }, 361 }, 362 }, 363 }, 364 }, 365 istioConfigs: []runtime.Object{ 366 &v1alpha3.VirtualService{ 367 ObjectMeta: metav1.ObjectMeta{ 368 Name: "bookinfo", 369 Namespace: "default", 370 }, 371 Spec: v1alpha32.VirtualService{ 372 Hosts: []string{"productpage"}, 373 Gateways: []string{"fake-gw"}, 374 Http: []*v1alpha32.HTTPRoute{ 375 { 376 Match: []*v1alpha32.HTTPMatchRequest{ 377 { 378 Uri: &v1alpha32.StringMatch{ 379 MatchType: &v1alpha32.StringMatch_Prefix{ 380 Prefix: "/prefix", 381 }, 382 }, 383 }, 384 }, 385 Route: []*v1alpha32.HTTPRouteDestination{ 386 { 387 Destination: &v1alpha32.Destination{ 388 Host: "productpage", 389 }, 390 Weight: 30, 391 }, 392 { 393 Destination: &v1alpha32.Destination{ 394 Host: "productpage2", 395 }, 396 Weight: 20, 397 }, 398 { 399 Destination: &v1alpha32.Destination{ 400 Host: "productpage3", 401 }, 402 Weight: 50, 403 }, 404 }, 405 }, 406 }, 407 }, 408 }, 409 &v1alpha3.DestinationRule{ 410 ObjectMeta: metav1.ObjectMeta{ 411 Name: "productpage", 412 Namespace: "default", 413 }, 414 Spec: v1alpha32.DestinationRule{ 415 Host: "productpage", 416 Subsets: []*v1alpha32.Subset{ 417 { 418 Name: "v1", 419 Labels: map[string]string{"version": "v1"}, 420 }, 421 }, 422 }, 423 }, 424 }, 425 configDumps: map[string][]byte{ 426 "productpage-v1-1234567890": config, 427 "ingress": []byte("{}"), 428 }, 429 namespace: "default", 430 istioNamespace: "default", 431 // case 9, vs route to multiple hosts 432 args: strings.Split("service productpage", " "), 433 expectedOutput: `Service: productpage 434 DestinationRule: productpage for "productpage" 435 WARNING POD DOES NOT MATCH ANY SUBSETS. (Non matching subsets v1) 436 Matching subsets: 437 (Non-matching subsets v1) 438 No Traffic Policy 439 VirtualService: bookinfo 440 Route to host "productpage" with weight 30% 441 Route to host "productpage2" with weight 20% 442 Route to host "productpage3" with weight 50% 443 Match: /prefix* 444 `, 445 }, 446 // have traffic policy 447 { 448 k8sConfigs: []runtime.Object{ 449 &corev1.Service{ 450 ObjectMeta: metav1.ObjectMeta{ 451 Name: "productpage", 452 Namespace: "default", 453 }, 454 Spec: corev1.ServiceSpec{ 455 Selector: map[string]string{ 456 "app": "productpage", 457 }, 458 Ports: []corev1.ServicePort{ 459 { 460 Name: "http", 461 Port: 9080, 462 TargetPort: intstr.FromInt32(9080), 463 }, 464 }, 465 }, 466 }, 467 &corev1.Service{ 468 ObjectMeta: metav1.ObjectMeta{ 469 Name: "ingress", 470 Namespace: "default", 471 Labels: map[string]string{ 472 "istio": "ingressgateway", 473 }, 474 }, 475 Spec: corev1.ServiceSpec{ 476 Selector: map[string]string{ 477 "istio": "ingressgateway", 478 }, 479 Ports: []corev1.ServicePort{ 480 { 481 Name: "http", 482 Port: 80, 483 TargetPort: intstr.FromInt32(80), 484 }, 485 }, 486 }, 487 }, 488 &corev1.Pod{ 489 ObjectMeta: metav1.ObjectMeta{ 490 Name: "productpage-v1-1234567890", 491 Namespace: "default", 492 Labels: map[string]string{ 493 "app": "productpage", 494 }, 495 }, 496 Spec: corev1.PodSpec{ 497 Containers: []corev1.Container{ 498 { 499 Name: "productpage", 500 Ports: []corev1.ContainerPort{ 501 { 502 Name: "http", 503 ContainerPort: 9080, 504 }, 505 }, 506 }, 507 { 508 Name: "istio-proxy", 509 }, 510 }, 511 }, 512 Status: corev1.PodStatus{ 513 Phase: corev1.PodRunning, 514 ContainerStatuses: []corev1.ContainerStatus{ 515 { 516 Name: "istio-proxy", 517 Ready: true, 518 }, 519 }, 520 }, 521 }, 522 &corev1.Pod{ 523 ObjectMeta: metav1.ObjectMeta{ 524 Name: "ingress", 525 Namespace: "default", 526 Labels: map[string]string{ 527 "istio": "ingressgateway", 528 }, 529 }, 530 Spec: corev1.PodSpec{ 531 Containers: []corev1.Container{ 532 { 533 Name: "istio-proxy", 534 }, 535 }, 536 }, 537 Status: corev1.PodStatus{ 538 Phase: corev1.PodRunning, 539 ContainerStatuses: []corev1.ContainerStatus{ 540 { 541 Name: "istio-proxy", 542 Ready: true, 543 }, 544 }, 545 }, 546 }, 547 }, 548 istioConfigs: []runtime.Object{ 549 &v1alpha3.VirtualService{ 550 ObjectMeta: metav1.ObjectMeta{ 551 Name: "bookinfo", 552 Namespace: "default", 553 }, 554 Spec: v1alpha32.VirtualService{ 555 Hosts: []string{"productpage"}, 556 Gateways: []string{"fake-gw"}, 557 Http: []*v1alpha32.HTTPRoute{ 558 { 559 Match: []*v1alpha32.HTTPMatchRequest{ 560 { 561 Uri: &v1alpha32.StringMatch{ 562 MatchType: &v1alpha32.StringMatch_Prefix{ 563 Prefix: "/prefix", 564 }, 565 }, 566 }, 567 }, 568 Route: []*v1alpha32.HTTPRouteDestination{ 569 { 570 Destination: &v1alpha32.Destination{ 571 Host: "productpage", 572 }, 573 Weight: 30, 574 }, 575 { 576 Destination: &v1alpha32.Destination{ 577 Host: "productpage2", 578 }, 579 Weight: 20, 580 }, 581 { 582 Destination: &v1alpha32.Destination{ 583 Host: "productpage3", 584 }, 585 Weight: 50, 586 }, 587 }, 588 }, 589 }, 590 }, 591 }, 592 &v1alpha3.DestinationRule{ 593 ObjectMeta: metav1.ObjectMeta{ 594 Name: "productpage", 595 Namespace: "default", 596 }, 597 Spec: v1alpha32.DestinationRule{ 598 Host: "productpage", 599 Subsets: []*v1alpha32.Subset{ 600 { 601 Name: "v1", 602 Labels: map[string]string{"version": "v1"}, 603 }, 604 }, 605 TrafficPolicy: &v1alpha32.TrafficPolicy{ 606 LoadBalancer: &v1alpha32.LoadBalancerSettings{ 607 LbPolicy: &v1alpha32.LoadBalancerSettings_Simple{Simple: v1alpha32.LoadBalancerSettings_LEAST_REQUEST}, 608 }, 609 ConnectionPool: &v1alpha32.ConnectionPoolSettings{Tcp: &v1alpha32.ConnectionPoolSettings_TCPSettings{MaxConnections: 10}}, 610 OutlierDetection: &v1alpha32.OutlierDetection{MinHealthPercent: 10}, 611 Tls: &v1alpha32.ClientTLSSettings{Mode: v1alpha32.ClientTLSSettings_ISTIO_MUTUAL}, 612 PortLevelSettings: []*v1alpha32.TrafficPolicy_PortTrafficPolicy{ 613 { 614 LoadBalancer: &v1alpha32.LoadBalancerSettings{ 615 LbPolicy: &v1alpha32.LoadBalancerSettings_Simple{Simple: v1alpha32.LoadBalancerSettings_LEAST_REQUEST}, 616 }, 617 Port: &v1alpha32.PortSelector{Number: 8080}, 618 Tls: &v1alpha32.ClientTLSSettings{Mode: v1alpha32.ClientTLSSettings_DISABLE}, 619 ConnectionPool: &v1alpha32.ConnectionPoolSettings{Tcp: &v1alpha32.ConnectionPoolSettings_TCPSettings{MaxConnections: 10}}, 620 OutlierDetection: &v1alpha32.OutlierDetection{MinHealthPercent: 10}, 621 }, 622 }, 623 Tunnel: nil, 624 ProxyProtocol: nil, 625 }, 626 }, 627 }, 628 }, 629 configDumps: map[string][]byte{ 630 "productpage-v1-1234567890": config, 631 "ingress": []byte("{}"), 632 }, 633 namespace: "default", 634 istioNamespace: "default", 635 // case 9, vs route to multiple hosts 636 args: strings.Split("service productpage", " "), 637 expectedOutput: `Service: productpage 638 DestinationRule: productpage for "productpage" 639 WARNING POD DOES NOT MATCH ANY SUBSETS. (Non matching subsets v1) 640 Matching subsets: 641 (Non-matching subsets v1) 642 Traffic Policy TLS Mode: ISTIO_MUTUAL 643 Policies: load balancer/connection pool/outlier detection 644 Port Level Settings: 645 8080: 646 TLS Mode: DISABLE 647 Policies: load balancer/connection pool/outlier detection 648 VirtualService: bookinfo 649 Route to host "productpage" with weight 30% 650 Route to host "productpage2" with weight 20% 651 Route to host "productpage3" with weight 50% 652 Match: /prefix* 653 `, 654 }, 655 // have ingress gateway 656 { 657 k8sConfigs: []runtime.Object{ 658 &corev1.Service{ 659 ObjectMeta: metav1.ObjectMeta{ 660 Name: "productpage", 661 Namespace: "default", 662 }, 663 Spec: corev1.ServiceSpec{ 664 Selector: map[string]string{ 665 "app": "productpage", 666 }, 667 Ports: []corev1.ServicePort{ 668 { 669 Name: "http", 670 Port: 9080, 671 TargetPort: intstr.FromInt32(9080), 672 }, 673 }, 674 }, 675 }, 676 &corev1.Service{ 677 ObjectMeta: metav1.ObjectMeta{ 678 Name: "ingress", 679 Namespace: "default", 680 Labels: map[string]string{ 681 "istio": "ingressgateway", 682 "app": "ingress", 683 }, 684 }, 685 Spec: corev1.ServiceSpec{ 686 Selector: map[string]string{ 687 "istio": "ingressgateway", 688 }, 689 Ports: []corev1.ServicePort{ 690 { 691 Name: "http", 692 Port: 80, 693 TargetPort: intstr.FromInt32(80), 694 Protocol: corev1.ProtocolTCP, 695 }, 696 }, 697 }, 698 Status: corev1.ServiceStatus{ 699 LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{ 700 { 701 IP: "2.2.2.2", 702 }, 703 }}, 704 }, 705 }, 706 &corev1.Service{ 707 ObjectMeta: metav1.ObjectMeta{ 708 Name: "ingress", 709 Namespace: "istio-ingress", 710 Labels: map[string]string{ 711 "istio": "ingressgateway", 712 }, 713 }, 714 Spec: corev1.ServiceSpec{ 715 Selector: map[string]string{ 716 "istio": "ingressgateway", 717 }, 718 Ports: []corev1.ServicePort{ 719 { 720 Name: "http", 721 Port: 80, 722 TargetPort: intstr.FromInt32(80), 723 Protocol: corev1.ProtocolTCP, 724 }, 725 }, 726 }, 727 Status: corev1.ServiceStatus{ 728 LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{ 729 { 730 IP: "1.1.1.1", 731 }, 732 }}, 733 }, 734 }, 735 &corev1.Pod{ 736 ObjectMeta: metav1.ObjectMeta{ 737 Name: "productpage-v1-1234567890", 738 Namespace: "default", 739 Labels: map[string]string{ 740 "app": "productpage", 741 }, 742 }, 743 Spec: corev1.PodSpec{ 744 Containers: []corev1.Container{ 745 { 746 Name: "productpage", 747 Ports: []corev1.ContainerPort{ 748 { 749 Name: "http", 750 ContainerPort: 9080, 751 }, 752 }, 753 }, 754 { 755 Name: "istio-proxy", 756 }, 757 }, 758 }, 759 Status: corev1.PodStatus{ 760 Phase: corev1.PodRunning, 761 ContainerStatuses: []corev1.ContainerStatus{ 762 { 763 Name: "istio-proxy", 764 Ready: true, 765 }, 766 }, 767 }, 768 }, 769 &corev1.Pod{ 770 ObjectMeta: metav1.ObjectMeta{ 771 Name: "ingress", 772 Namespace: "default", 773 Labels: map[string]string{ 774 "istio": "ingressgateway", 775 }, 776 }, 777 Spec: corev1.PodSpec{ 778 Containers: []corev1.Container{ 779 { 780 Name: "istio-proxy", 781 }, 782 }, 783 }, 784 Status: corev1.PodStatus{ 785 Phase: corev1.PodRunning, 786 ContainerStatuses: []corev1.ContainerStatus{ 787 { 788 Name: "istio-proxy", 789 Ready: true, 790 }, 791 }, 792 }, 793 }, 794 &corev1.Pod{ 795 ObjectMeta: metav1.ObjectMeta{ 796 Name: "ingress", 797 Namespace: "istio-ingress", 798 Labels: map[string]string{ 799 "istio": "ingressgateway", 800 }, 801 }, 802 Spec: corev1.PodSpec{ 803 Containers: []corev1.Container{ 804 { 805 Name: "istio-proxy", 806 }, 807 }, 808 }, 809 Status: corev1.PodStatus{ 810 Phase: corev1.PodRunning, 811 ContainerStatuses: []corev1.ContainerStatus{ 812 { 813 Name: "istio-proxy", 814 Ready: true, 815 }, 816 }, 817 }, 818 }, 819 }, 820 istioConfigs: []runtime.Object{ 821 &v1alpha3.VirtualService{ 822 ObjectMeta: metav1.ObjectMeta{ 823 Name: "bookinfo", 824 Namespace: "default", 825 }, 826 Spec: v1alpha32.VirtualService{ 827 Hosts: []string{ 828 "productpage", 829 "productpage.exapple.com", 830 }, 831 Gateways: []string{"fake-gw"}, 832 Http: []*v1alpha32.HTTPRoute{ 833 { 834 Match: []*v1alpha32.HTTPMatchRequest{ 835 { 836 Uri: &v1alpha32.StringMatch{ 837 MatchType: &v1alpha32.StringMatch_Prefix{ 838 Prefix: "/prefix", 839 }, 840 }, 841 }, 842 }, 843 Route: []*v1alpha32.HTTPRouteDestination{ 844 { 845 Destination: &v1alpha32.Destination{ 846 Host: "productpage", 847 }, 848 Weight: 30, 849 }, 850 { 851 Destination: &v1alpha32.Destination{ 852 Host: "productpage2", 853 }, 854 Weight: 20, 855 }, 856 { 857 Destination: &v1alpha32.Destination{ 858 Host: "productpage3", 859 }, 860 Weight: 50, 861 }, 862 }, 863 }, 864 }, 865 }, 866 }, 867 &v1alpha3.DestinationRule{ 868 ObjectMeta: metav1.ObjectMeta{ 869 Name: "productpage", 870 Namespace: "default", 871 }, 872 Spec: v1alpha32.DestinationRule{ 873 Host: "productpage", 874 Subsets: []*v1alpha32.Subset{ 875 { 876 Name: "v1", 877 Labels: map[string]string{"version": "v1"}, 878 }, 879 }, 880 TrafficPolicy: &v1alpha32.TrafficPolicy{ 881 LoadBalancer: &v1alpha32.LoadBalancerSettings{ 882 LbPolicy: &v1alpha32.LoadBalancerSettings_Simple{Simple: v1alpha32.LoadBalancerSettings_LEAST_REQUEST}, 883 }, 884 ConnectionPool: &v1alpha32.ConnectionPoolSettings{Tcp: &v1alpha32.ConnectionPoolSettings_TCPSettings{MaxConnections: 10}}, 885 OutlierDetection: &v1alpha32.OutlierDetection{MinHealthPercent: 10}, 886 Tls: &v1alpha32.ClientTLSSettings{Mode: v1alpha32.ClientTLSSettings_ISTIO_MUTUAL}, 887 PortLevelSettings: []*v1alpha32.TrafficPolicy_PortTrafficPolicy{ 888 { 889 LoadBalancer: &v1alpha32.LoadBalancerSettings{ 890 LbPolicy: &v1alpha32.LoadBalancerSettings_Simple{Simple: v1alpha32.LoadBalancerSettings_LEAST_REQUEST}, 891 }, 892 Port: &v1alpha32.PortSelector{Number: 8080}, 893 Tls: &v1alpha32.ClientTLSSettings{Mode: v1alpha32.ClientTLSSettings_DISABLE}, 894 ConnectionPool: &v1alpha32.ConnectionPoolSettings{Tcp: &v1alpha32.ConnectionPoolSettings_TCPSettings{MaxConnections: 10}}, 895 OutlierDetection: &v1alpha32.OutlierDetection{MinHealthPercent: 10}, 896 }, 897 }, 898 Tunnel: nil, 899 ProxyProtocol: nil, 900 }, 901 }, 902 }, 903 &v1alpha3.Gateway{ 904 ObjectMeta: metav1.ObjectMeta{ 905 Name: "fake-gw", 906 Namespace: "default", 907 }, 908 Spec: v1alpha32.Gateway{ 909 Servers: []*v1alpha32.Server{ 910 { 911 Port: &v1alpha32.Port{ 912 Number: 80, 913 Name: "default", 914 Protocol: "HTTP", 915 }, 916 Hosts: []string{ 917 "productpage.exapple.com", 918 }, 919 }, 920 }, 921 Selector: map[string]string{ 922 "istio": "ingressgateway", 923 }, 924 }, 925 }, 926 }, 927 configDumps: map[string][]byte{ 928 "productpage-v1-1234567890": config, 929 "ingress": config, 930 }, 931 namespace: "default", 932 istioNamespace: "default", 933 // case 9, vs route to multiple hosts 934 args: strings.Split("service productpage", " "), 935 expectedOutput: `Service: productpage 936 DestinationRule: productpage for "productpage" 937 WARNING POD DOES NOT MATCH ANY SUBSETS. (Non matching subsets v1) 938 Matching subsets: 939 (Non-matching subsets v1) 940 Traffic Policy TLS Mode: ISTIO_MUTUAL 941 Policies: load balancer/connection pool/outlier detection 942 Port Level Settings: 943 8080: 944 TLS Mode: DISABLE 945 Policies: load balancer/connection pool/outlier detection 946 VirtualService: bookinfo 947 Route to host "productpage" with weight 30% 948 Route to host "productpage2" with weight 20% 949 Route to host "productpage3" with weight 50% 950 Match: /prefix* 951 -------------------- 952 Exposed on Ingress Gateway http://1.1.1.1 953 Exposed on Ingress Gateway http://2.2.2.2 954 VirtualService: bookinfo 955 Route to host "productpage" with weight 30% 956 Route to host "productpage2" with weight 20% 957 Route to host "productpage3" with weight 50% 958 Match: /prefix* 959 `, 960 }, 961 } 962 963 for i, c := range cases { 964 t.Run(fmt.Sprintf("case %d %s", i, strings.Join(c.args, " ")), func(t *testing.T) { 965 verifyExecAndK8sConfigTestCaseTestOutput(t, c) 966 }) 967 } 968 } 969 970 func TestGetRevisionFromPodAnnotation(t *testing.T) { 971 cases := []struct { 972 anno klabels.Set 973 974 expected string 975 }{ 976 { 977 anno: klabels.Set{ 978 apiannotation.SidecarStatus.Name: "", 979 }, 980 expected: "", 981 }, 982 { 983 anno: klabels.Set{}, 984 expected: "", 985 }, 986 { 987 anno: klabels.Set{ 988 apiannotation.SidecarStatus.Name: ` 989 { 990 "initContainers": [ 991 "istio-init" 992 ], 993 "containers": [ 994 "istio-proxy" 995 ], 996 "volumes": [ 997 "istio-envoy", 998 "istio-data", 999 "istio-podinfo", 1000 "istio-token", 1001 "istiod-ca-cert" 1002 ], 1003 "imagePullSecrets": null, 1004 "revision": "1-13-2" 1005 }`, 1006 }, 1007 expected: "1-13-2", 1008 }, 1009 } 1010 1011 for _, tc := range cases { 1012 t.Run("", func(t *testing.T) { 1013 got := GetRevisionFromPodAnnotation(tc.anno) 1014 assert.Equal(t, tc.expected, got) 1015 }) 1016 } 1017 } 1018 1019 func TestFindProtocolForPort(t *testing.T) { 1020 http := "HTTP" 1021 cases := []struct { 1022 port corev1.ServicePort 1023 expectedProtocol string 1024 }{ 1025 { 1026 port: corev1.ServicePort{ 1027 Name: "http", 1028 Protocol: corev1.ProtocolTCP, 1029 }, 1030 expectedProtocol: "HTTP", 1031 }, 1032 { 1033 port: corev1.ServicePort{ 1034 Name: "GRPC-port", 1035 Protocol: corev1.ProtocolTCP, 1036 }, 1037 expectedProtocol: "GRPC", 1038 }, 1039 { 1040 port: corev1.ServicePort{ 1041 AppProtocol: &http, 1042 Protocol: corev1.ProtocolTCP, 1043 }, 1044 expectedProtocol: "HTTP", 1045 }, 1046 { 1047 port: corev1.ServicePort{ 1048 Protocol: corev1.ProtocolTCP, 1049 Port: 80, 1050 }, 1051 expectedProtocol: "auto-detect", 1052 }, 1053 { 1054 port: corev1.ServicePort{ 1055 Protocol: corev1.ProtocolUDP, 1056 Port: 80, 1057 }, 1058 expectedProtocol: "UDP", 1059 }, 1060 } 1061 1062 for _, tc := range cases { 1063 protocol := findProtocolForPort(&tc.port) 1064 if protocol != tc.expectedProtocol { 1065 t.Fatalf("Output didn't match for the port protocol: got %s want %s", protocol, tc.expectedProtocol) 1066 } 1067 } 1068 } 1069 1070 func verifyExecAndK8sConfigTestCaseTestOutput(t *testing.T, c execAndK8sConfigTestCase) { 1071 t.Helper() 1072 1073 ctx := cli.NewFakeContext(&cli.NewFakeContextOption{ 1074 Namespace: c.namespace, 1075 IstioNamespace: c.istioNamespace, 1076 Results: c.configDumps, 1077 }) 1078 client, err := ctx.CLIClient() 1079 assert.NoError(t, err) 1080 // Override the Istio config factory 1081 for i := range c.istioConfigs { 1082 switch t := c.istioConfigs[i].(type) { 1083 case *v1alpha3.DestinationRule: 1084 client.Istio().NetworkingV1alpha3().DestinationRules(t.Namespace).Create(context.TODO(), t, metav1.CreateOptions{}) 1085 case *v1alpha3.Gateway: 1086 client.Istio().NetworkingV1alpha3().Gateways(t.Namespace).Create(context.TODO(), t, metav1.CreateOptions{}) 1087 case *v1alpha3.VirtualService: 1088 client.Istio().NetworkingV1alpha3().VirtualServices(t.Namespace).Create(context.TODO(), t, metav1.CreateOptions{}) 1089 } 1090 } 1091 for i := range c.k8sConfigs { 1092 switch t := c.k8sConfigs[i].(type) { 1093 case *corev1.Service: 1094 client.Kube().CoreV1().Services(t.Namespace).Create(context.TODO(), t, metav1.CreateOptions{}) 1095 case *corev1.Pod: 1096 client.Kube().CoreV1().Pods(t.Namespace).Create(context.TODO(), t, metav1.CreateOptions{}) 1097 } 1098 } 1099 1100 if c.configDumps == nil { 1101 c.configDumps = map[string][]byte{} 1102 } 1103 1104 var out bytes.Buffer 1105 rootCmd := Cmd(ctx) 1106 rootCmd.SetArgs(c.args) 1107 1108 rootCmd.SetOut(&out) 1109 rootCmd.SetErr(&out) 1110 1111 if c.namespace != "" { 1112 describeNamespace = c.namespace 1113 } 1114 1115 fErr := rootCmd.Execute() 1116 output := out.String() 1117 1118 if c.expectedOutput != "" && c.expectedOutput != output { 1119 t.Fatalf("Unexpected output for 'istioctl %s'\n got: %q\nwant: %q", strings.Join(c.args, " "), output, c.expectedOutput) 1120 } 1121 1122 if c.expectedString != "" && !strings.Contains(output, c.expectedString) { 1123 t.Fatalf("Output didn't match for 'istioctl %s'\n got %v\nwant: %v", strings.Join(c.args, " "), output, c.expectedString) 1124 } 1125 1126 if c.wantException { 1127 if fErr == nil { 1128 t.Fatalf("Wanted an exception for 'istioctl %s', didn't get one, output was %q", 1129 strings.Join(c.args, " "), output) 1130 } 1131 } else { 1132 if fErr != nil { 1133 t.Fatalf("Unwanted exception for 'istioctl %s': %v", strings.Join(c.args, " "), fErr) 1134 } 1135 } 1136 } 1137 1138 func TestGetIstioVirtualServicePathForSvcFromRoute(t *testing.T) { 1139 tests := []struct { 1140 name string 1141 inputConfig string 1142 inputService corev1.Service 1143 inputPort int32 1144 expected string 1145 }{ 1146 { 1147 name: "test tls config", 1148 inputConfig: "testdata/describe/tls_config.json", 1149 inputService: corev1.Service{ 1150 TypeMeta: metav1.TypeMeta{}, 1151 ObjectMeta: metav1.ObjectMeta{ 1152 Name: "productpage", 1153 Namespace: "default", 1154 }, 1155 Spec: corev1.ServiceSpec{}, 1156 Status: corev1.ServiceStatus{}, 1157 }, 1158 inputPort: int32(9080), 1159 expected: "/apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/bookinfo", 1160 }, 1161 { 1162 name: "test http config", 1163 inputConfig: "testdata/describe/http_config.json", 1164 inputService: corev1.Service{ 1165 TypeMeta: metav1.TypeMeta{}, 1166 ObjectMeta: metav1.ObjectMeta{ 1167 Name: "productpage", 1168 Namespace: "default", 1169 }, 1170 Spec: corev1.ServiceSpec{}, 1171 Status: corev1.ServiceStatus{}, 1172 }, 1173 inputPort: int32(9080), 1174 expected: "/apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/bookinfo", 1175 }, 1176 } 1177 1178 for _, test := range tests { 1179 t.Run(test.name, func(t *testing.T) { 1180 ic := util.ReadFile(t, test.inputConfig) 1181 cd := configdump.Wrapper{} 1182 err := cd.UnmarshalJSON(ic) 1183 if err != nil { 1184 t.Fatal(err) 1185 } 1186 out, err := getIstioVirtualServicePathForSvcFromRoute(&cd, test.inputService, test.inputPort) 1187 if err != nil { 1188 t.Fatal(err) 1189 } 1190 if diff := cmp.Diff(test.expected, out); diff != "" { 1191 t.Fatalf("Diff:\n%s", diff) 1192 } 1193 }) 1194 } 1195 }