istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/virtualservice_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 model 16 17 import ( 18 "fmt" 19 "reflect" 20 "testing" 21 "time" 22 23 fuzz "github.com/google/gofuzz" 24 "google.golang.org/protobuf/types/known/durationpb" 25 "google.golang.org/protobuf/types/known/wrapperspb" 26 27 networking "istio.io/api/networking/v1alpha3" 28 "istio.io/istio/pilot/pkg/serviceregistry/provider" 29 "istio.io/istio/pkg/config" 30 "istio.io/istio/pkg/config/constants" 31 "istio.io/istio/pkg/config/host" 32 "istio.io/istio/pkg/config/protocol" 33 "istio.io/istio/pkg/config/schema/gvk" 34 "istio.io/istio/pkg/config/schema/kind" 35 "istio.io/istio/pkg/config/visibility" 36 "istio.io/istio/pkg/test/util/assert" 37 "istio.io/istio/pkg/util/sets" 38 ) 39 40 const wildcardIP = "0.0.0.0" 41 42 func TestMergeVirtualServices(t *testing.T) { 43 independentVs := config.Config{ 44 Meta: config.Meta{ 45 GroupVersionKind: gvk.VirtualService, 46 Name: "virtual-service", 47 Namespace: "default", 48 }, 49 Spec: &networking.VirtualService{ 50 Hosts: []string{"example.org"}, 51 Gateways: []string{"gateway"}, 52 Http: []*networking.HTTPRoute{ 53 { 54 Route: []*networking.HTTPRouteDestination{ 55 { 56 Destination: &networking.Destination{ 57 Host: "example.org", 58 Port: &networking.PortSelector{ 59 Number: 80, 60 }, 61 }, 62 }, 63 }, 64 }, 65 }, 66 }, 67 } 68 69 rootVs := config.Config{ 70 Meta: config.Meta{ 71 GroupVersionKind: gvk.VirtualService, 72 Name: "root-vs", 73 Namespace: "istio-system", 74 }, 75 Spec: &networking.VirtualService{ 76 Hosts: []string{"*.org"}, 77 Gateways: []string{"gateway"}, 78 Http: []*networking.HTTPRoute{ 79 { 80 Match: []*networking.HTTPMatchRequest{ 81 { 82 Uri: &networking.StringMatch{ 83 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 84 }, 85 }, 86 { 87 Uri: &networking.StringMatch{ 88 MatchType: &networking.StringMatch_Exact{Exact: "/login"}, 89 }, 90 }, 91 }, 92 Delegate: &networking.Delegate{ 93 Name: "productpage-vs", 94 Namespace: "default", 95 }, 96 }, 97 { 98 Route: []*networking.HTTPRouteDestination{ 99 { 100 Destination: &networking.Destination{ 101 Host: "example.org", 102 Port: &networking.PortSelector{ 103 Number: 80, 104 }, 105 }, 106 }, 107 }, 108 }, 109 }, 110 }, 111 } 112 113 defaultVs := config.Config{ 114 Meta: config.Meta{ 115 GroupVersionKind: gvk.VirtualService, 116 Name: "default-vs", 117 Namespace: "default", 118 }, 119 Spec: &networking.VirtualService{ 120 Hosts: []string{"*.org"}, 121 Gateways: []string{"gateway"}, 122 Http: []*networking.HTTPRoute{ 123 { 124 Match: []*networking.HTTPMatchRequest{ 125 { 126 Uri: &networking.StringMatch{ 127 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 128 }, 129 }, 130 { 131 Uri: &networking.StringMatch{ 132 MatchType: &networking.StringMatch_Exact{Exact: "/login"}, 133 }, 134 }, 135 }, 136 Delegate: &networking.Delegate{ 137 Name: "productpage-vs", 138 }, 139 }, 140 { 141 Route: []*networking.HTTPRouteDestination{ 142 { 143 Destination: &networking.Destination{ 144 Host: "example.org", 145 Port: &networking.PortSelector{ 146 Number: 80, 147 }, 148 }, 149 }, 150 }, 151 }, 152 }, 153 }, 154 } 155 156 oneRoot := config.Config{ 157 Meta: config.Meta{ 158 GroupVersionKind: gvk.VirtualService, 159 Name: "root-vs", 160 Namespace: "istio-system", 161 }, 162 Spec: &networking.VirtualService{ 163 Hosts: []string{"*.org"}, 164 Gateways: []string{"gateway"}, 165 Http: []*networking.HTTPRoute{ 166 { 167 Route: []*networking.HTTPRouteDestination{ 168 { 169 Destination: &networking.Destination{ 170 Host: "example.org", 171 Port: &networking.PortSelector{ 172 Number: 80, 173 }, 174 }, 175 }, 176 }, 177 }, 178 }, 179 }, 180 } 181 182 createDelegateVs := func(name, namespace string, exportTo []string) config.Config { 183 return config.Config{ 184 Meta: config.Meta{ 185 GroupVersionKind: gvk.VirtualService, 186 Name: name, 187 Namespace: namespace, 188 }, 189 Spec: &networking.VirtualService{ 190 Hosts: []string{}, 191 Gateways: []string{"gateway"}, 192 ExportTo: exportTo, 193 Http: []*networking.HTTPRoute{ 194 { 195 Match: []*networking.HTTPMatchRequest{ 196 { 197 Uri: &networking.StringMatch{ 198 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"}, 199 }, 200 }, 201 }, 202 Route: []*networking.HTTPRouteDestination{ 203 { 204 Destination: &networking.Destination{ 205 Host: "productpage.org", 206 Port: &networking.PortSelector{ 207 Number: 80, 208 }, 209 Subset: "v1", 210 }, 211 }, 212 }, 213 }, 214 { 215 Match: []*networking.HTTPMatchRequest{ 216 { 217 Uri: &networking.StringMatch{ 218 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 219 }, 220 }, 221 }, 222 Route: []*networking.HTTPRouteDestination{ 223 { 224 Destination: &networking.Destination{ 225 Host: "productpage.org", 226 Port: &networking.PortSelector{ 227 Number: 80, 228 }, 229 Subset: "v2", 230 }, 231 }, 232 }, 233 }, 234 { 235 Route: []*networking.HTTPRouteDestination{ 236 { 237 Destination: &networking.Destination{ 238 Host: "productpage.org", 239 Port: &networking.PortSelector{ 240 Number: 80, 241 }, 242 Subset: "v3", 243 }, 244 }, 245 }, 246 }, 247 }, 248 }, 249 } 250 } 251 252 delegateVs := createDelegateVs("productpage-vs", "default", []string{"istio-system"}) 253 delegateVsExportedToAll := createDelegateVs("productpage-vs", "default", []string{}) 254 255 delegateVsNotExported := config.Config{ 256 Meta: config.Meta{ 257 GroupVersionKind: gvk.VirtualService, 258 Name: "productpage-vs", 259 Namespace: "default2", 260 }, 261 Spec: &networking.VirtualService{ 262 Hosts: []string{}, 263 Gateways: []string{"gateway"}, 264 ExportTo: []string{"."}, 265 }, 266 } 267 268 mergedVs := config.Config{ 269 Meta: config.Meta{ 270 GroupVersionKind: gvk.VirtualService, 271 Name: "root-vs", 272 Namespace: "istio-system", 273 }, 274 Spec: &networking.VirtualService{ 275 Hosts: []string{"*.org"}, 276 Gateways: []string{"gateway"}, 277 Http: []*networking.HTTPRoute{ 278 { 279 Match: []*networking.HTTPMatchRequest{ 280 { 281 Uri: &networking.StringMatch{ 282 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"}, 283 }, 284 }, 285 }, 286 Route: []*networking.HTTPRouteDestination{ 287 { 288 Destination: &networking.Destination{ 289 Host: "productpage.org", 290 Port: &networking.PortSelector{ 291 Number: 80, 292 }, 293 Subset: "v1", 294 }, 295 }, 296 }, 297 }, 298 { 299 Match: []*networking.HTTPMatchRequest{ 300 { 301 Uri: &networking.StringMatch{ 302 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 303 }, 304 }, 305 }, 306 Route: []*networking.HTTPRouteDestination{ 307 { 308 Destination: &networking.Destination{ 309 Host: "productpage.org", 310 Port: &networking.PortSelector{ 311 Number: 80, 312 }, 313 Subset: "v2", 314 }, 315 }, 316 }, 317 }, 318 { 319 Match: []*networking.HTTPMatchRequest{ 320 { 321 Uri: &networking.StringMatch{ 322 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 323 }, 324 }, 325 { 326 Uri: &networking.StringMatch{ 327 MatchType: &networking.StringMatch_Exact{Exact: "/login"}, 328 }, 329 }, 330 }, 331 Route: []*networking.HTTPRouteDestination{ 332 { 333 Destination: &networking.Destination{ 334 Host: "productpage.org", 335 Port: &networking.PortSelector{ 336 Number: 80, 337 }, 338 Subset: "v3", 339 }, 340 }, 341 }, 342 }, 343 { 344 Route: []*networking.HTTPRouteDestination{ 345 { 346 Destination: &networking.Destination{ 347 Host: "example.org", 348 Port: &networking.PortSelector{ 349 Number: 80, 350 }, 351 }, 352 }, 353 }, 354 }, 355 }, 356 }, 357 } 358 359 mergedVsInDefault := mergedVs.DeepCopy() 360 mergedVsInDefault.Name = "default-vs" 361 mergedVsInDefault.Namespace = "default" 362 363 // invalid delegate, match condition conflicts with root 364 delegateVs2 := config.Config{ 365 Meta: config.Meta{ 366 GroupVersionKind: gvk.VirtualService, 367 Name: "productpage-vs", 368 Namespace: "default", 369 }, 370 Spec: &networking.VirtualService{ 371 Hosts: []string{}, 372 Gateways: []string{"gateway"}, 373 Http: []*networking.HTTPRoute{ 374 { 375 Match: []*networking.HTTPMatchRequest{ 376 { 377 Uri: &networking.StringMatch{ 378 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"}, 379 }, 380 }, 381 }, 382 Route: []*networking.HTTPRouteDestination{ 383 { 384 Destination: &networking.Destination{ 385 Host: "productpage.org", 386 Port: &networking.PortSelector{ 387 Number: 80, 388 }, 389 Subset: "v1", 390 }, 391 }, 392 }, 393 }, 394 { 395 Match: []*networking.HTTPMatchRequest{ 396 { 397 Uri: &networking.StringMatch{ 398 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 399 }, 400 }, 401 }, 402 Route: []*networking.HTTPRouteDestination{ 403 { 404 Destination: &networking.Destination{ 405 Host: "productpage.org", 406 Port: &networking.PortSelector{ 407 Number: 80, 408 }, 409 Subset: "v2", 410 }, 411 }, 412 }, 413 }, 414 { 415 // mismatch, this route will be ignored 416 Match: []*networking.HTTPMatchRequest{ 417 { 418 Name: "mismatch", 419 Uri: &networking.StringMatch{ 420 // conflicts with root's HTTPMatchRequest 421 MatchType: &networking.StringMatch_Prefix{Prefix: "/mis-match/path"}, 422 }, 423 }, 424 }, 425 Route: []*networking.HTTPRouteDestination{ 426 { 427 Destination: &networking.Destination{ 428 Host: "productpage.org", 429 Port: &networking.PortSelector{ 430 Number: 80, 431 }, 432 Subset: "v3", 433 }, 434 }, 435 }, 436 }, 437 }, 438 }, 439 } 440 441 mergedVs2 := config.Config{ 442 Meta: config.Meta{ 443 GroupVersionKind: gvk.VirtualService, 444 Name: "root-vs", 445 Namespace: "istio-system", 446 }, 447 Spec: &networking.VirtualService{ 448 Hosts: []string{"*.org"}, 449 Gateways: []string{"gateway"}, 450 Http: []*networking.HTTPRoute{ 451 { 452 Match: []*networking.HTTPMatchRequest{ 453 { 454 Uri: &networking.StringMatch{ 455 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"}, 456 }, 457 }, 458 }, 459 Route: []*networking.HTTPRouteDestination{ 460 { 461 Destination: &networking.Destination{ 462 Host: "productpage.org", 463 Port: &networking.PortSelector{ 464 Number: 80, 465 }, 466 Subset: "v1", 467 }, 468 }, 469 }, 470 }, 471 { 472 Match: []*networking.HTTPMatchRequest{ 473 { 474 Uri: &networking.StringMatch{ 475 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 476 }, 477 }, 478 }, 479 Route: []*networking.HTTPRouteDestination{ 480 { 481 Destination: &networking.Destination{ 482 Host: "productpage.org", 483 Port: &networking.PortSelector{ 484 Number: 80, 485 }, 486 Subset: "v2", 487 }, 488 }, 489 }, 490 }, 491 { 492 Route: []*networking.HTTPRouteDestination{ 493 { 494 Destination: &networking.Destination{ 495 Host: "example.org", 496 Port: &networking.PortSelector{ 497 Number: 80, 498 }, 499 }, 500 }, 501 }, 502 }, 503 }, 504 }, 505 } 506 507 // multiple routes delegate to one single sub VS 508 multiRoutes := config.Config{ 509 Meta: config.Meta{ 510 GroupVersionKind: gvk.VirtualService, 511 Name: "root-vs", 512 Namespace: "istio-system", 513 }, 514 Spec: &networking.VirtualService{ 515 Hosts: []string{"*.org"}, 516 Gateways: []string{"gateway"}, 517 Http: []*networking.HTTPRoute{ 518 { 519 Match: []*networking.HTTPMatchRequest{ 520 { 521 Uri: &networking.StringMatch{ 522 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 523 }, 524 }, 525 }, 526 Delegate: &networking.Delegate{ 527 Name: "productpage-vs", 528 Namespace: "default", 529 }, 530 }, 531 { 532 Match: []*networking.HTTPMatchRequest{ 533 { 534 Uri: &networking.StringMatch{ 535 MatchType: &networking.StringMatch_Prefix{Prefix: "/legacy/path"}, 536 }, 537 }, 538 }, 539 Rewrite: &networking.HTTPRewrite{ 540 Uri: "/productpage", 541 }, 542 Delegate: &networking.Delegate{ 543 Name: "productpage-vs", 544 Namespace: "default", 545 }, 546 }, 547 }, 548 }, 549 } 550 551 singleDelegate := config.Config{ 552 Meta: config.Meta{ 553 GroupVersionKind: gvk.VirtualService, 554 Name: "productpage-vs", 555 Namespace: "default", 556 }, 557 Spec: &networking.VirtualService{ 558 Hosts: []string{}, 559 Gateways: []string{"gateway"}, 560 Http: []*networking.HTTPRoute{ 561 { 562 Route: []*networking.HTTPRouteDestination{ 563 { 564 Destination: &networking.Destination{ 565 Host: "productpage.org", 566 Port: &networking.PortSelector{ 567 Number: 80, 568 }, 569 Subset: "v1", 570 }, 571 }, 572 }, 573 }, 574 }, 575 }, 576 } 577 578 mergedVs3 := config.Config{ 579 Meta: config.Meta{ 580 GroupVersionKind: gvk.VirtualService, 581 Name: "root-vs", 582 Namespace: "istio-system", 583 }, 584 Spec: &networking.VirtualService{ 585 Hosts: []string{"*.org"}, 586 Gateways: []string{"gateway"}, 587 Http: []*networking.HTTPRoute{ 588 { 589 Match: []*networking.HTTPMatchRequest{ 590 { 591 Uri: &networking.StringMatch{ 592 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 593 }, 594 }, 595 }, 596 Route: []*networking.HTTPRouteDestination{ 597 { 598 Destination: &networking.Destination{ 599 Host: "productpage.org", 600 Port: &networking.PortSelector{ 601 Number: 80, 602 }, 603 Subset: "v1", 604 }, 605 }, 606 }, 607 }, 608 { 609 Match: []*networking.HTTPMatchRequest{ 610 { 611 Uri: &networking.StringMatch{ 612 MatchType: &networking.StringMatch_Prefix{Prefix: "/legacy/path"}, 613 }, 614 }, 615 }, 616 Rewrite: &networking.HTTPRewrite{ 617 Uri: "/productpage", 618 }, 619 Route: []*networking.HTTPRouteDestination{ 620 { 621 Destination: &networking.Destination{ 622 Host: "productpage.org", 623 Port: &networking.PortSelector{ 624 Number: 80, 625 }, 626 Subset: "v1", 627 }, 628 }, 629 }, 630 }, 631 }, 632 }, 633 } 634 635 cases := []struct { 636 name string 637 virtualServices []config.Config 638 expectedVirtualServices []config.Config 639 defaultExportTo sets.Set[visibility.Instance] 640 }{ 641 { 642 name: "one independent vs", 643 virtualServices: []config.Config{independentVs}, 644 expectedVirtualServices: []config.Config{independentVs}, 645 defaultExportTo: sets.New(visibility.Public), 646 }, 647 { 648 name: "one root vs", 649 virtualServices: []config.Config{rootVs}, 650 expectedVirtualServices: []config.Config{oneRoot}, 651 defaultExportTo: sets.New(visibility.Public), 652 }, 653 { 654 name: "one delegate vs", 655 virtualServices: []config.Config{delegateVs}, 656 expectedVirtualServices: []config.Config{}, 657 defaultExportTo: sets.New(visibility.Public), 658 }, 659 { 660 name: "root and delegate vs", 661 virtualServices: []config.Config{rootVs.DeepCopy(), delegateVs}, 662 expectedVirtualServices: []config.Config{mergedVs}, 663 defaultExportTo: sets.New(visibility.Public), 664 }, 665 { 666 name: "root and conflicted delegate vs", 667 virtualServices: []config.Config{rootVs.DeepCopy(), delegateVs2}, 668 expectedVirtualServices: []config.Config{mergedVs2}, 669 defaultExportTo: sets.New(visibility.Public), 670 }, 671 { 672 name: "multiple routes delegate to one", 673 virtualServices: []config.Config{multiRoutes.DeepCopy(), singleDelegate}, 674 expectedVirtualServices: []config.Config{mergedVs3}, 675 defaultExportTo: sets.New(visibility.Public), 676 }, 677 { 678 name: "root not specify delegate namespace default public", 679 virtualServices: []config.Config{defaultVs.DeepCopy(), delegateVsExportedToAll}, 680 expectedVirtualServices: []config.Config{mergedVsInDefault}, 681 defaultExportTo: sets.New(visibility.Public), 682 }, 683 { 684 name: "delegate not exported to root vs namespace default public", 685 virtualServices: []config.Config{rootVs, delegateVsNotExported}, 686 expectedVirtualServices: []config.Config{oneRoot}, 687 defaultExportTo: sets.New(visibility.Public), 688 }, 689 { 690 name: "root not specify delegate namespace default private", 691 virtualServices: []config.Config{defaultVs.DeepCopy(), delegateVsExportedToAll}, 692 expectedVirtualServices: []config.Config{mergedVsInDefault}, 693 defaultExportTo: sets.New(visibility.Private), 694 }, 695 { 696 name: "delegate not exported to root vs namespace default private", 697 virtualServices: []config.Config{rootVs, delegateVsNotExported}, 698 expectedVirtualServices: []config.Config{oneRoot}, 699 defaultExportTo: sets.New(visibility.Private), 700 }, 701 } 702 703 for _, tc := range cases { 704 t.Run(tc.name, func(t *testing.T) { 705 got, _ := mergeVirtualServicesIfNeeded(tc.virtualServices, tc.defaultExportTo) 706 assert.Equal(t, got, tc.expectedVirtualServices) 707 }) 708 } 709 710 t.Run("test merge order", func(t *testing.T) { 711 root := rootVs.DeepCopy() 712 delegate := delegateVs.DeepCopy() 713 normal := independentVs.DeepCopy() 714 715 // make sorting results predictable. 716 t0 := time.Now() 717 root.CreationTimestamp = t0.Add(1) 718 delegate.CreationTimestamp = t0.Add(2) 719 normal.CreationTimestamp = t0.Add(3) 720 721 checkOrder := func(got []config.Config, _ map[ConfigKey][]ConfigKey) { 722 gotOrder := make([]string, 0, len(got)) 723 for _, c := range got { 724 gotOrder = append(gotOrder, fmt.Sprintf("%s/%s", c.Namespace, c.Name)) 725 } 726 wantOrder := []string{"istio-system/root-vs", "default/virtual-service"} 727 assert.Equal(t, gotOrder, wantOrder) 728 } 729 730 vses := []config.Config{root, delegate, normal} 731 checkOrder(mergeVirtualServicesIfNeeded(vses, sets.New(visibility.Public))) 732 733 vses = []config.Config{normal, delegate, root} 734 checkOrder(mergeVirtualServicesIfNeeded(vses, sets.New(visibility.Public))) 735 }) 736 } 737 738 func TestMergeHttpRoutes(t *testing.T) { 739 dstV1 := &networking.Destination{ 740 Host: "productpage.org", 741 Port: &networking.PortSelector{ 742 Number: 80, 743 }, 744 Subset: "v1", 745 } 746 dstV2 := &networking.Destination{ 747 Host: "productpage.org", 748 Port: &networking.PortSelector{ 749 Number: 80, 750 }, 751 Subset: "v2", 752 } 753 dstV3 := &networking.Destination{ 754 Host: "productpage.org", 755 Port: &networking.PortSelector{ 756 Number: 80, 757 }, 758 Subset: "v3", 759 } 760 dstMirrorV1 := dstV1.DeepCopy() 761 dstMirrorV1.Host = "productpage-mirror.org" 762 dstMirrorV2 := dstV2.DeepCopy() 763 dstMirrorV2.Host = "productpage-mirror.org" 764 dstMirrorV3 := dstV3.DeepCopy() 765 dstMirrorV3.Host = "productpage-mirror.org" 766 767 cases := []struct { 768 name string 769 root *networking.HTTPRoute 770 delegate []*networking.HTTPRoute 771 expected []*networking.HTTPRoute 772 }{ 773 { 774 name: "root catch all", 775 root: &networking.HTTPRoute{ 776 Match: nil, // catch all 777 Timeout: &durationpb.Duration{Seconds: 10}, 778 Headers: &networking.Headers{ 779 Request: &networking.Headers_HeaderOperations{ 780 Add: map[string]string{ 781 "istio": "test", 782 }, 783 Remove: []string{"trace-id"}, 784 }, 785 }, 786 Delegate: &networking.Delegate{ 787 Name: "delegate", 788 Namespace: "default", 789 }, 790 }, 791 delegate: []*networking.HTTPRoute{ 792 { 793 Match: []*networking.HTTPMatchRequest{ 794 { 795 Uri: &networking.StringMatch{ 796 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"}, 797 }, 798 Headers: map[string]*networking.StringMatch{ 799 "version": { 800 MatchType: &networking.StringMatch_Exact{Exact: "v1"}, 801 }, 802 }, 803 }, 804 }, 805 Route: []*networking.HTTPRouteDestination{{Destination: dstV1}}, 806 }, 807 { 808 Match: []*networking.HTTPMatchRequest{ 809 { 810 Uri: &networking.StringMatch{ 811 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 812 }, 813 Headers: map[string]*networking.StringMatch{ 814 "version": { 815 MatchType: &networking.StringMatch_Exact{Exact: "v2"}, 816 }, 817 }, 818 }, 819 { 820 Headers: map[string]*networking.StringMatch{ 821 "version": { 822 MatchType: &networking.StringMatch_Exact{Exact: "v2"}, 823 }, 824 }, 825 QueryParams: map[string]*networking.StringMatch{ 826 "version": { 827 MatchType: &networking.StringMatch_Exact{Exact: "v2"}, 828 }, 829 }, 830 }, 831 }, 832 Route: []*networking.HTTPRouteDestination{{Destination: dstV2}}, 833 }, 834 }, 835 expected: []*networking.HTTPRoute{ 836 { 837 Match: []*networking.HTTPMatchRequest{ 838 { 839 Uri: &networking.StringMatch{ 840 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"}, 841 }, 842 Headers: map[string]*networking.StringMatch{ 843 "version": { 844 MatchType: &networking.StringMatch_Exact{Exact: "v1"}, 845 }, 846 }, 847 }, 848 }, 849 Route: []*networking.HTTPRouteDestination{{Destination: dstV1}}, 850 Timeout: &durationpb.Duration{Seconds: 10}, 851 Headers: &networking.Headers{ 852 Request: &networking.Headers_HeaderOperations{ 853 Add: map[string]string{ 854 "istio": "test", 855 }, 856 Remove: []string{"trace-id"}, 857 }, 858 }, 859 }, 860 { 861 Match: []*networking.HTTPMatchRequest{ 862 { 863 Uri: &networking.StringMatch{ 864 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 865 }, 866 Headers: map[string]*networking.StringMatch{ 867 "version": { 868 MatchType: &networking.StringMatch_Exact{Exact: "v2"}, 869 }, 870 }, 871 }, 872 { 873 Headers: map[string]*networking.StringMatch{ 874 "version": { 875 MatchType: &networking.StringMatch_Exact{Exact: "v2"}, 876 }, 877 }, 878 QueryParams: map[string]*networking.StringMatch{ 879 "version": { 880 MatchType: &networking.StringMatch_Exact{Exact: "v2"}, 881 }, 882 }, 883 }, 884 }, 885 Route: []*networking.HTTPRouteDestination{{Destination: dstV2}}, 886 Timeout: &durationpb.Duration{Seconds: 10}, 887 Headers: &networking.Headers{ 888 Request: &networking.Headers_HeaderOperations{ 889 Add: map[string]string{ 890 "istio": "test", 891 }, 892 Remove: []string{"trace-id"}, 893 }, 894 }, 895 }, 896 }, 897 }, 898 { 899 name: "delegate with empty match", 900 root: &networking.HTTPRoute{ 901 Match: []*networking.HTTPMatchRequest{ 902 { 903 Uri: &networking.StringMatch{ 904 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 905 }, 906 Port: 8080, 907 }, 908 }, 909 Delegate: &networking.Delegate{ 910 Name: "delegate", 911 Namespace: "default", 912 }, 913 }, 914 delegate: []*networking.HTTPRoute{ 915 { 916 Match: []*networking.HTTPMatchRequest{ 917 { 918 Uri: &networking.StringMatch{ 919 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"}, 920 }, 921 Headers: map[string]*networking.StringMatch{ 922 "version": { 923 MatchType: &networking.StringMatch_Exact{Exact: "v1"}, 924 }, 925 }, 926 }, 927 }, 928 Route: []*networking.HTTPRouteDestination{{Destination: dstV1}}, 929 }, 930 { 931 Match: []*networking.HTTPMatchRequest{ 932 { 933 Uri: &networking.StringMatch{ 934 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 935 }, 936 Headers: map[string]*networking.StringMatch{ 937 "version": { 938 MatchType: &networking.StringMatch_Exact{Exact: "v2"}, 939 }, 940 }, 941 }, 942 { 943 Headers: map[string]*networking.StringMatch{ 944 "version": { 945 MatchType: &networking.StringMatch_Exact{Exact: "v2"}, 946 }, 947 }, 948 QueryParams: map[string]*networking.StringMatch{ 949 "version": { 950 MatchType: &networking.StringMatch_Exact{Exact: "v2"}, 951 }, 952 }, 953 }, 954 }, 955 Route: []*networking.HTTPRouteDestination{{Destination: dstV2}}, 956 }, 957 { 958 // default route to v3 959 Route: []*networking.HTTPRouteDestination{{Destination: dstV3}}, 960 }, 961 }, 962 expected: []*networking.HTTPRoute{ 963 { 964 Match: []*networking.HTTPMatchRequest{ 965 { 966 Uri: &networking.StringMatch{ 967 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"}, 968 }, 969 Headers: map[string]*networking.StringMatch{ 970 "version": { 971 MatchType: &networking.StringMatch_Exact{Exact: "v1"}, 972 }, 973 }, 974 Port: 8080, 975 }, 976 }, 977 Route: []*networking.HTTPRouteDestination{{Destination: dstV1}}, 978 }, 979 { 980 Match: []*networking.HTTPMatchRequest{ 981 { 982 Uri: &networking.StringMatch{ 983 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 984 }, 985 Headers: map[string]*networking.StringMatch{ 986 "version": { 987 MatchType: &networking.StringMatch_Exact{Exact: "v2"}, 988 }, 989 }, 990 Port: 8080, 991 }, 992 { 993 Uri: &networking.StringMatch{ 994 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 995 }, 996 Headers: map[string]*networking.StringMatch{ 997 "version": { 998 MatchType: &networking.StringMatch_Exact{Exact: "v2"}, 999 }, 1000 }, 1001 QueryParams: map[string]*networking.StringMatch{ 1002 "version": { 1003 MatchType: &networking.StringMatch_Exact{Exact: "v2"}, 1004 }, 1005 }, 1006 Port: 8080, 1007 }, 1008 }, 1009 Route: []*networking.HTTPRouteDestination{{Destination: dstV2}}, 1010 }, 1011 { 1012 Match: []*networking.HTTPMatchRequest{ 1013 { 1014 Uri: &networking.StringMatch{ 1015 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1016 }, 1017 Port: 8080, 1018 }, 1019 }, 1020 // default route to v3 1021 Route: []*networking.HTTPRouteDestination{{Destination: dstV3}}, 1022 }, 1023 }, 1024 }, 1025 { 1026 name: "delegate with mirrors", 1027 root: &networking.HTTPRoute{ 1028 Match: nil, 1029 Mirrors: []*networking.HTTPMirrorPolicy{{Destination: dstMirrorV3}}, 1030 Delegate: &networking.Delegate{ 1031 Name: "delegate", 1032 Namespace: "default", 1033 }, 1034 }, 1035 delegate: []*networking.HTTPRoute{ 1036 { 1037 Match: []*networking.HTTPMatchRequest{ 1038 { 1039 Uri: &networking.StringMatch{ 1040 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"}, 1041 }, 1042 }, 1043 }, 1044 Mirrors: []*networking.HTTPMirrorPolicy{{Destination: dstMirrorV1}}, 1045 Route: []*networking.HTTPRouteDestination{{Destination: dstV1}}, 1046 }, 1047 { 1048 Match: []*networking.HTTPMatchRequest{ 1049 { 1050 Uri: &networking.StringMatch{ 1051 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 1052 }, 1053 }, 1054 }, 1055 Mirrors: []*networking.HTTPMirrorPolicy{{Destination: dstMirrorV2}}, 1056 Route: []*networking.HTTPRouteDestination{{Destination: dstV2}}, 1057 }, 1058 { 1059 // default route to v3 with no specified mirrors 1060 Route: []*networking.HTTPRouteDestination{{Destination: dstV3}}, 1061 }, 1062 }, 1063 expected: []*networking.HTTPRoute{ 1064 { 1065 Match: []*networking.HTTPMatchRequest{ 1066 { 1067 Uri: &networking.StringMatch{ 1068 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"}, 1069 }, 1070 }, 1071 }, 1072 Mirrors: []*networking.HTTPMirrorPolicy{{Destination: dstMirrorV1}}, 1073 Route: []*networking.HTTPRouteDestination{{Destination: dstV1}}, 1074 }, 1075 { 1076 Match: []*networking.HTTPMatchRequest{ 1077 { 1078 Uri: &networking.StringMatch{ 1079 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 1080 }, 1081 }, 1082 }, 1083 Mirrors: []*networking.HTTPMirrorPolicy{{Destination: dstMirrorV2}}, 1084 Route: []*networking.HTTPRouteDestination{{Destination: dstV2}}, 1085 }, 1086 { 1087 // default route to v3 1088 Mirrors: []*networking.HTTPMirrorPolicy{{Destination: dstMirrorV3}}, 1089 Route: []*networking.HTTPRouteDestination{{Destination: dstV3}}, 1090 }, 1091 }, 1092 }, 1093 { 1094 name: "multiple header merge", 1095 root: &networking.HTTPRoute{ 1096 Match: []*networking.HTTPMatchRequest{ 1097 { 1098 Headers: map[string]*networking.StringMatch{ 1099 "header1": { 1100 MatchType: &networking.StringMatch_Regex{ 1101 Regex: "regex", 1102 }, 1103 }, 1104 }, 1105 }, 1106 { 1107 Headers: map[string]*networking.StringMatch{ 1108 "header2": { 1109 MatchType: &networking.StringMatch_Exact{ 1110 Exact: "exact", 1111 }, 1112 }, 1113 }, 1114 }, 1115 }, 1116 Delegate: &networking.Delegate{ 1117 Name: "delegate", 1118 Namespace: "default", 1119 }, 1120 }, 1121 delegate: []*networking.HTTPRoute{ 1122 { 1123 Match: []*networking.HTTPMatchRequest{ 1124 { 1125 Uri: &networking.StringMatch{ 1126 MatchType: &networking.StringMatch_Prefix{ 1127 Prefix: "/", 1128 }, 1129 }, 1130 }, 1131 }, 1132 }, 1133 }, 1134 expected: []*networking.HTTPRoute{ 1135 { 1136 Match: []*networking.HTTPMatchRequest{ 1137 { 1138 Uri: &networking.StringMatch{ 1139 MatchType: &networking.StringMatch_Prefix{ 1140 Prefix: "/", 1141 }, 1142 }, 1143 Headers: map[string]*networking.StringMatch{ 1144 "header1": { 1145 MatchType: &networking.StringMatch_Regex{ 1146 Regex: "regex", 1147 }, 1148 }, 1149 }, 1150 }, 1151 { 1152 Uri: &networking.StringMatch{ 1153 MatchType: &networking.StringMatch_Prefix{ 1154 Prefix: "/", 1155 }, 1156 }, 1157 Headers: map[string]*networking.StringMatch{ 1158 "header2": { 1159 MatchType: &networking.StringMatch_Exact{ 1160 Exact: "exact", 1161 }, 1162 }, 1163 }, 1164 }, 1165 }, 1166 }, 1167 }, 1168 }, 1169 } 1170 1171 for _, tc := range cases { 1172 t.Run(tc.name, func(t *testing.T) { 1173 got := mergeHTTPRoutes(tc.root, tc.delegate) 1174 assert.Equal(t, got, tc.expected) 1175 }) 1176 } 1177 } 1178 1179 func TestMergeHTTPMatchRequests(t *testing.T) { 1180 cases := []struct { 1181 name string 1182 root []*networking.HTTPMatchRequest 1183 delegate []*networking.HTTPMatchRequest 1184 expected []*networking.HTTPMatchRequest 1185 }{ 1186 { 1187 name: "url match", 1188 root: []*networking.HTTPMatchRequest{ 1189 { 1190 Uri: &networking.StringMatch{ 1191 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1192 }, 1193 }, 1194 }, 1195 delegate: []*networking.HTTPMatchRequest{ 1196 { 1197 Uri: &networking.StringMatch{ 1198 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"}, 1199 }, 1200 }, 1201 }, 1202 expected: []*networking.HTTPMatchRequest{ 1203 { 1204 Uri: &networking.StringMatch{ 1205 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"}, 1206 }, 1207 }, 1208 }, 1209 }, 1210 { 1211 name: "url regex noconflict", 1212 root: []*networking.HTTPMatchRequest{ 1213 { 1214 Uri: &networking.StringMatch{ 1215 MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"}, 1216 }, 1217 }, 1218 }, 1219 delegate: []*networking.HTTPMatchRequest{ 1220 {}, 1221 }, 1222 expected: []*networking.HTTPMatchRequest{ 1223 { 1224 Uri: &networking.StringMatch{ 1225 MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"}, 1226 }, 1227 }, 1228 }, 1229 }, 1230 { 1231 name: "url regex conflict", 1232 root: []*networking.HTTPMatchRequest{ 1233 { 1234 Uri: &networking.StringMatch{ 1235 MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"}, 1236 }, 1237 }, 1238 }, 1239 delegate: []*networking.HTTPMatchRequest{ 1240 { 1241 Uri: &networking.StringMatch{ 1242 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1243 }, 1244 }, 1245 }, 1246 expected: nil, 1247 }, 1248 { 1249 name: "multi url match", 1250 root: []*networking.HTTPMatchRequest{ 1251 { 1252 Uri: &networking.StringMatch{ 1253 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1254 }, 1255 }, 1256 { 1257 Uri: &networking.StringMatch{ 1258 MatchType: &networking.StringMatch_Prefix{Prefix: "/reviews"}, 1259 }, 1260 }, 1261 }, 1262 delegate: []*networking.HTTPMatchRequest{ 1263 { 1264 Uri: &networking.StringMatch{ 1265 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"}, 1266 }, 1267 }, 1268 { 1269 Uri: &networking.StringMatch{ 1270 MatchType: &networking.StringMatch_Exact{Exact: "/reviews/v1"}, 1271 }, 1272 }, 1273 }, 1274 expected: []*networking.HTTPMatchRequest{ 1275 { 1276 Uri: &networking.StringMatch{ 1277 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"}, 1278 }, 1279 }, 1280 { 1281 Uri: &networking.StringMatch{ 1282 MatchType: &networking.StringMatch_Exact{Exact: "/reviews/v1"}, 1283 }, 1284 }, 1285 }, 1286 }, 1287 { 1288 name: "url mismatch", 1289 root: []*networking.HTTPMatchRequest{ 1290 { 1291 Uri: &networking.StringMatch{ 1292 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1293 }, 1294 }, 1295 }, 1296 delegate: []*networking.HTTPMatchRequest{ 1297 { 1298 Uri: &networking.StringMatch{ 1299 MatchType: &networking.StringMatch_Exact{Exact: "/reviews"}, 1300 }, 1301 }, 1302 }, 1303 expected: nil, 1304 }, 1305 { 1306 name: "url match", 1307 root: []*networking.HTTPMatchRequest{ 1308 { 1309 Uri: &networking.StringMatch{ 1310 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1311 }, 1312 }, 1313 }, 1314 delegate: []*networking.HTTPMatchRequest{ 1315 { 1316 Uri: &networking.StringMatch{ 1317 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"}, 1318 }, 1319 }, 1320 { 1321 Uri: &networking.StringMatch{ 1322 // conflicts 1323 MatchType: &networking.StringMatch_Exact{Exact: "/reviews"}, 1324 }, 1325 }, 1326 }, 1327 expected: nil, 1328 }, 1329 { 1330 name: "headers", 1331 root: []*networking.HTTPMatchRequest{ 1332 { 1333 Headers: map[string]*networking.StringMatch{ 1334 "header": { 1335 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1336 }, 1337 }, 1338 }, 1339 }, 1340 delegate: []*networking.HTTPMatchRequest{}, 1341 expected: []*networking.HTTPMatchRequest{ 1342 { 1343 Headers: map[string]*networking.StringMatch{ 1344 "header": { 1345 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1346 }, 1347 }, 1348 }, 1349 }, 1350 }, 1351 { 1352 name: "headers conflict", 1353 root: []*networking.HTTPMatchRequest{ 1354 { 1355 Headers: map[string]*networking.StringMatch{ 1356 "header": { 1357 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1358 }, 1359 }, 1360 }, 1361 }, 1362 delegate: []*networking.HTTPMatchRequest{ 1363 { 1364 Headers: map[string]*networking.StringMatch{ 1365 "header": { 1366 MatchType: &networking.StringMatch_Exact{Exact: "h2"}, 1367 }, 1368 }, 1369 }, 1370 }, 1371 expected: nil, 1372 }, 1373 { 1374 name: "headers", 1375 root: []*networking.HTTPMatchRequest{ 1376 { 1377 Headers: map[string]*networking.StringMatch{ 1378 "header": { 1379 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1380 }, 1381 }, 1382 }, 1383 }, 1384 delegate: []*networking.HTTPMatchRequest{ 1385 { 1386 Headers: map[string]*networking.StringMatch{ 1387 "header-2": { 1388 MatchType: &networking.StringMatch_Exact{Exact: "h2"}, 1389 }, 1390 }, 1391 }, 1392 }, 1393 expected: []*networking.HTTPMatchRequest{ 1394 { 1395 Headers: map[string]*networking.StringMatch{ 1396 "header": { 1397 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1398 }, 1399 "header-2": { 1400 MatchType: &networking.StringMatch_Exact{Exact: "h2"}, 1401 }, 1402 }, 1403 }, 1404 }, 1405 }, 1406 { 1407 name: "complicated merge", 1408 root: []*networking.HTTPMatchRequest{ 1409 { 1410 Uri: &networking.StringMatch{ 1411 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1412 }, 1413 Headers: map[string]*networking.StringMatch{ 1414 "header": { 1415 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1416 }, 1417 }, 1418 Port: 8080, 1419 Authority: &networking.StringMatch{ 1420 MatchType: &networking.StringMatch_Exact{Exact: "productpage.com"}, 1421 }, 1422 }, 1423 }, 1424 delegate: []*networking.HTTPMatchRequest{ 1425 { 1426 Uri: &networking.StringMatch{ 1427 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"}, 1428 }, 1429 }, 1430 { 1431 Uri: &networking.StringMatch{ 1432 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"}, 1433 }, 1434 }, 1435 }, 1436 expected: []*networking.HTTPMatchRequest{ 1437 { 1438 Uri: &networking.StringMatch{ 1439 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"}, 1440 }, 1441 Headers: map[string]*networking.StringMatch{ 1442 "header": { 1443 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1444 }, 1445 }, 1446 Port: 8080, 1447 Authority: &networking.StringMatch{ 1448 MatchType: &networking.StringMatch_Exact{Exact: "productpage.com"}, 1449 }, 1450 }, 1451 { 1452 Uri: &networking.StringMatch{ 1453 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"}, 1454 }, 1455 Headers: map[string]*networking.StringMatch{ 1456 "header": { 1457 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1458 }, 1459 }, 1460 Port: 8080, 1461 Authority: &networking.StringMatch{ 1462 MatchType: &networking.StringMatch_Exact{Exact: "productpage.com"}, 1463 }, 1464 }, 1465 }, 1466 }, 1467 { 1468 name: "conflicted merge", 1469 root: []*networking.HTTPMatchRequest{ 1470 { 1471 Uri: &networking.StringMatch{ 1472 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1473 }, 1474 Headers: map[string]*networking.StringMatch{ 1475 "header": { 1476 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1477 }, 1478 }, 1479 Port: 8080, 1480 Authority: &networking.StringMatch{ 1481 MatchType: &networking.StringMatch_Exact{Exact: "productpage.com"}, 1482 }, 1483 }, 1484 }, 1485 delegate: []*networking.HTTPMatchRequest{ 1486 { 1487 Uri: &networking.StringMatch{ 1488 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"}, 1489 }, 1490 }, 1491 { 1492 Uri: &networking.StringMatch{ 1493 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"}, 1494 }, 1495 Port: 9090, // conflicts 1496 }, 1497 }, 1498 expected: nil, 1499 }, 1500 { 1501 name: "gateway merge", 1502 root: []*networking.HTTPMatchRequest{ 1503 { 1504 Uri: &networking.StringMatch{ 1505 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1506 }, 1507 Gateways: []string{"ingress-gateway", "mesh"}, 1508 }, 1509 }, 1510 delegate: []*networking.HTTPMatchRequest{ 1511 { 1512 Uri: &networking.StringMatch{ 1513 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"}, 1514 }, 1515 Gateways: []string{"ingress-gateway"}, 1516 }, 1517 { 1518 Uri: &networking.StringMatch{ 1519 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"}, 1520 }, 1521 Gateways: []string{"mesh"}, 1522 }, 1523 }, 1524 expected: []*networking.HTTPMatchRequest{ 1525 { 1526 Uri: &networking.StringMatch{ 1527 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"}, 1528 }, 1529 Gateways: []string{"ingress-gateway"}, 1530 }, 1531 { 1532 Uri: &networking.StringMatch{ 1533 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"}, 1534 }, 1535 Gateways: []string{"mesh"}, 1536 }, 1537 }, 1538 }, 1539 { 1540 name: "gateway conflicted merge", 1541 root: []*networking.HTTPMatchRequest{ 1542 { 1543 Uri: &networking.StringMatch{ 1544 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1545 }, 1546 Gateways: []string{"ingress-gateway"}, 1547 }, 1548 }, 1549 delegate: []*networking.HTTPMatchRequest{ 1550 { 1551 Uri: &networking.StringMatch{ 1552 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"}, 1553 }, 1554 Gateways: []string{"ingress-gateway"}, 1555 }, 1556 { 1557 Uri: &networking.StringMatch{ 1558 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"}, 1559 }, 1560 Gateways: []string{"mesh"}, 1561 }, 1562 }, 1563 expected: nil, 1564 }, 1565 { 1566 name: "source labels merge", 1567 root: []*networking.HTTPMatchRequest{ 1568 { 1569 SourceLabels: map[string]string{"app": "test"}, 1570 }, 1571 }, 1572 delegate: []*networking.HTTPMatchRequest{ 1573 { 1574 SourceLabels: map[string]string{"version": "v1"}, 1575 }, 1576 }, 1577 expected: []*networking.HTTPMatchRequest{ 1578 { 1579 SourceLabels: map[string]string{"app": "test", "version": "v1"}, 1580 }, 1581 }, 1582 }, 1583 } 1584 1585 for _, tc := range cases { 1586 t.Run(tc.name, func(t *testing.T) { 1587 tc.delegate = config.DeepCopy(tc.delegate).([]*networking.HTTPMatchRequest) 1588 got, _ := mergeHTTPMatchRequests(tc.root, tc.delegate) 1589 assert.Equal(t, got, tc.expected) 1590 }) 1591 } 1592 } 1593 1594 func TestHasConflict(t *testing.T) { 1595 cases := []struct { 1596 name string 1597 root *networking.HTTPMatchRequest 1598 leaf *networking.HTTPMatchRequest 1599 expected bool 1600 }{ 1601 { 1602 name: "regex uri", 1603 root: &networking.HTTPMatchRequest{ 1604 Uri: &networking.StringMatch{ 1605 MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"}, 1606 }, 1607 }, 1608 leaf: &networking.HTTPMatchRequest{ 1609 Uri: &networking.StringMatch{ 1610 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 1611 }, 1612 }, 1613 expected: true, 1614 }, 1615 { 1616 name: "regex uri in root and delegate does not have uri", 1617 root: &networking.HTTPMatchRequest{ 1618 Uri: &networking.StringMatch{ 1619 MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"}, 1620 }, 1621 }, 1622 leaf: &networking.HTTPMatchRequest{}, 1623 expected: false, 1624 }, 1625 { 1626 name: "regex uri in delegate and root does not have uri", 1627 root: &networking.HTTPMatchRequest{}, 1628 leaf: &networking.HTTPMatchRequest{ 1629 Uri: &networking.StringMatch{ 1630 MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"}, 1631 }, 1632 }, 1633 expected: false, 1634 }, 1635 { 1636 name: "regex uri in root and delegate has conflicting uri match", 1637 root: &networking.HTTPMatchRequest{ 1638 Uri: &networking.StringMatch{ 1639 MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"}, 1640 }, 1641 }, 1642 leaf: &networking.HTTPMatchRequest{ 1643 Uri: &networking.StringMatch{ 1644 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1645 }, 1646 }, 1647 expected: true, 1648 }, 1649 { 1650 name: "regex uri in delegate and root has conflicting uri match", 1651 root: &networking.HTTPMatchRequest{ 1652 Uri: &networking.StringMatch{ 1653 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1654 }, 1655 }, 1656 leaf: &networking.HTTPMatchRequest{ 1657 Uri: &networking.StringMatch{ 1658 MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"}, 1659 }, 1660 }, 1661 expected: true, 1662 }, 1663 { 1664 name: "match uri", 1665 root: &networking.HTTPMatchRequest{ 1666 Uri: &networking.StringMatch{ 1667 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1668 }, 1669 }, 1670 leaf: &networking.HTTPMatchRequest{ 1671 Uri: &networking.StringMatch{ 1672 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 1673 }, 1674 }, 1675 expected: false, 1676 }, 1677 { 1678 name: "mismatch uri", 1679 root: &networking.HTTPMatchRequest{ 1680 Uri: &networking.StringMatch{ 1681 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 1682 }, 1683 }, 1684 leaf: &networking.HTTPMatchRequest{ 1685 Uri: &networking.StringMatch{ 1686 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 1687 }, 1688 }, 1689 expected: true, 1690 }, 1691 { 1692 name: "match uri", 1693 root: &networking.HTTPMatchRequest{ 1694 Uri: &networking.StringMatch{ 1695 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"}, 1696 }, 1697 }, 1698 leaf: &networking.HTTPMatchRequest{ 1699 Uri: &networking.StringMatch{ 1700 MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"}, 1701 }, 1702 }, 1703 expected: false, 1704 }, 1705 { 1706 name: "headers not equal", 1707 root: &networking.HTTPMatchRequest{ 1708 Headers: map[string]*networking.StringMatch{ 1709 "header": { 1710 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1711 }, 1712 }, 1713 }, 1714 leaf: &networking.HTTPMatchRequest{ 1715 Headers: map[string]*networking.StringMatch{ 1716 "header": { 1717 MatchType: &networking.StringMatch_Exact{Exact: "h2"}, 1718 }, 1719 }, 1720 }, 1721 expected: true, 1722 }, 1723 { 1724 name: "headers equal", 1725 root: &networking.HTTPMatchRequest{ 1726 Headers: map[string]*networking.StringMatch{ 1727 "header": { 1728 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1729 }, 1730 }, 1731 }, 1732 leaf: &networking.HTTPMatchRequest{ 1733 Headers: map[string]*networking.StringMatch{ 1734 "header": { 1735 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1736 }, 1737 }, 1738 }, 1739 expected: false, 1740 }, 1741 { 1742 name: "headers match", 1743 root: &networking.HTTPMatchRequest{ 1744 Headers: map[string]*networking.StringMatch{ 1745 "header": { 1746 MatchType: &networking.StringMatch_Prefix{Prefix: "h1"}, 1747 }, 1748 }, 1749 }, 1750 leaf: &networking.HTTPMatchRequest{ 1751 Headers: map[string]*networking.StringMatch{ 1752 "header": { 1753 MatchType: &networking.StringMatch_Exact{Exact: "h1-v1"}, 1754 }, 1755 }, 1756 }, 1757 expected: false, 1758 }, 1759 { 1760 name: "headers mismatch", 1761 root: &networking.HTTPMatchRequest{ 1762 Headers: map[string]*networking.StringMatch{ 1763 "header": { 1764 MatchType: &networking.StringMatch_Prefix{Prefix: "h1"}, 1765 }, 1766 }, 1767 }, 1768 leaf: &networking.HTTPMatchRequest{ 1769 Headers: map[string]*networking.StringMatch{ 1770 "header": { 1771 MatchType: &networking.StringMatch_Exact{Exact: "h2"}, 1772 }, 1773 }, 1774 }, 1775 expected: true, 1776 }, 1777 { 1778 name: "headers prefix mismatch", 1779 root: &networking.HTTPMatchRequest{ 1780 Headers: map[string]*networking.StringMatch{ 1781 "header": { 1782 MatchType: &networking.StringMatch_Prefix{Prefix: "h1"}, 1783 }, 1784 }, 1785 }, 1786 leaf: &networking.HTTPMatchRequest{ 1787 Headers: map[string]*networking.StringMatch{ 1788 "header": { 1789 MatchType: &networking.StringMatch_Prefix{Prefix: "h2"}, 1790 }, 1791 }, 1792 }, 1793 expected: true, 1794 }, 1795 { 1796 name: "diff headers", 1797 root: &networking.HTTPMatchRequest{ 1798 Headers: map[string]*networking.StringMatch{ 1799 "header": { 1800 MatchType: &networking.StringMatch_Exact{Exact: "h1"}, 1801 }, 1802 }, 1803 }, 1804 leaf: &networking.HTTPMatchRequest{ 1805 Headers: map[string]*networking.StringMatch{ 1806 "header-2": { 1807 MatchType: &networking.StringMatch_Exact{Exact: "h2"}, 1808 }, 1809 }, 1810 }, 1811 expected: false, 1812 }, 1813 { 1814 name: "withoutHeaders mismatch", 1815 root: &networking.HTTPMatchRequest{ 1816 WithoutHeaders: map[string]*networking.StringMatch{ 1817 "header": { 1818 MatchType: &networking.StringMatch_Prefix{Prefix: "h1"}, 1819 }, 1820 }, 1821 }, 1822 leaf: &networking.HTTPMatchRequest{ 1823 WithoutHeaders: map[string]*networking.StringMatch{ 1824 "header": { 1825 MatchType: &networking.StringMatch_Exact{Exact: "h2"}, 1826 }, 1827 }, 1828 }, 1829 expected: true, 1830 }, 1831 { 1832 name: "port", 1833 root: &networking.HTTPMatchRequest{ 1834 Port: 0, 1835 }, 1836 leaf: &networking.HTTPMatchRequest{ 1837 Port: 8080, 1838 }, 1839 expected: false, 1840 }, 1841 { 1842 name: "port", 1843 root: &networking.HTTPMatchRequest{ 1844 Port: 8080, 1845 }, 1846 leaf: &networking.HTTPMatchRequest{ 1847 Port: 0, 1848 }, 1849 expected: false, 1850 }, 1851 { 1852 name: "port", 1853 root: &networking.HTTPMatchRequest{ 1854 Port: 8080, 1855 }, 1856 leaf: &networking.HTTPMatchRequest{ 1857 Port: 8090, 1858 }, 1859 expected: true, 1860 }, 1861 { 1862 name: "sourceLabels mismatch", 1863 root: &networking.HTTPMatchRequest{ 1864 SourceLabels: map[string]string{"a": "b"}, 1865 }, 1866 leaf: &networking.HTTPMatchRequest{ 1867 SourceLabels: map[string]string{"a": "c"}, 1868 }, 1869 expected: true, 1870 }, 1871 { 1872 name: "sourceNamespace mismatch", 1873 root: &networking.HTTPMatchRequest{ 1874 SourceNamespace: "test1", 1875 }, 1876 leaf: &networking.HTTPMatchRequest{ 1877 SourceNamespace: "test2", 1878 }, 1879 expected: true, 1880 }, 1881 { 1882 name: "root has less gateways than delegate", 1883 root: &networking.HTTPMatchRequest{ 1884 Gateways: []string{"ingress-gateway"}, 1885 }, 1886 leaf: &networking.HTTPMatchRequest{ 1887 Gateways: []string{"ingress-gateway", "mesh"}, 1888 }, 1889 expected: true, 1890 }, 1891 } 1892 1893 for _, tc := range cases { 1894 t.Run(tc.name, func(t *testing.T) { 1895 got := hasConflict(tc.root, tc.leaf) 1896 if got != tc.expected { 1897 t.Errorf("got %v, expected %v", got, tc.expected) 1898 } 1899 }) 1900 } 1901 } 1902 1903 // Note: this is to prevent missing merge new added HTTPRoute fields 1904 func TestFuzzMergeHttpRoute(t *testing.T) { 1905 f := fuzz.New().NilChance(0.5).NumElements(0, 1).Funcs( 1906 func(r *networking.HTTPRoute, c fuzz.Continue) { 1907 c.FuzzNoCustom(r) 1908 r.Match = []*networking.HTTPMatchRequest{{}} 1909 r.Route = nil 1910 r.Redirect = nil 1911 r.Delegate = nil 1912 r.Mirrors = []*networking.HTTPMirrorPolicy{{}} 1913 }, 1914 func(r *networking.HTTPMatchRequest, c fuzz.Continue) { 1915 *r = networking.HTTPMatchRequest{} 1916 }, 1917 func(r *networking.HTTPRouteDestination, c fuzz.Continue) { 1918 *r = networking.HTTPRouteDestination{} 1919 }, 1920 func(r *networking.HTTPRedirect, c fuzz.Continue) { 1921 *r = networking.HTTPRedirect{} 1922 }, 1923 func(r *networking.HTTPDirectResponse, c fuzz.Continue) { 1924 *r = networking.HTTPDirectResponse{} 1925 }, 1926 func(r *networking.Delegate, c fuzz.Continue) { 1927 *r = networking.Delegate{} 1928 }, 1929 1930 func(r *networking.HTTPRewrite, c fuzz.Continue) { 1931 *r = networking.HTTPRewrite{} 1932 }, 1933 1934 func(r *durationpb.Duration, c fuzz.Continue) { 1935 *r = durationpb.Duration{} 1936 }, 1937 func(r *networking.HTTPRetry, c fuzz.Continue) { 1938 *r = networking.HTTPRetry{} 1939 }, 1940 func(r *networking.HTTPFaultInjection, c fuzz.Continue) { 1941 *r = networking.HTTPFaultInjection{} 1942 }, 1943 func(r *networking.Destination, c fuzz.Continue) { 1944 *r = networking.Destination{} 1945 }, 1946 func(r *wrapperspb.UInt32Value, c fuzz.Continue) { 1947 *r = wrapperspb.UInt32Value{} 1948 }, 1949 func(r *networking.Percent, c fuzz.Continue) { 1950 *r = networking.Percent{} 1951 }, 1952 func(r *networking.CorsPolicy, c fuzz.Continue) { 1953 *r = networking.CorsPolicy{} 1954 }, 1955 func(r *networking.Headers, c fuzz.Continue) { 1956 *r = networking.Headers{} 1957 }, 1958 func(r *networking.HTTPMirrorPolicy, c fuzz.Continue) { 1959 *r = networking.HTTPMirrorPolicy{} 1960 }) 1961 1962 root := &networking.HTTPRoute{} 1963 f.Fuzz(root) 1964 1965 delegate := &networking.HTTPRoute{} 1966 expected := mergeHTTPRoute(root, delegate) 1967 assert.Equal(t, expected, root) 1968 } 1969 1970 // Note: this is to prevent missing merge new added HTTPMatchRequest fields 1971 func TestFuzzMergeHttpMatchRequest(t *testing.T) { 1972 f := fuzz.New().NilChance(0.5).NumElements(1, 1).Funcs( 1973 func(r *networking.StringMatch, c fuzz.Continue) { 1974 *r = networking.StringMatch{ 1975 MatchType: &networking.StringMatch_Exact{ 1976 Exact: "fuzz", 1977 }, 1978 } 1979 }, 1980 func(m *map[string]*networking.StringMatch, c fuzz.Continue) { 1981 *m = map[string]*networking.StringMatch{ 1982 "test": nil, 1983 } 1984 }, 1985 func(m *map[string]string, c fuzz.Continue) { 1986 *m = map[string]string{"test": "fuzz"} 1987 }) 1988 1989 root := &networking.HTTPMatchRequest{} 1990 f.Fuzz(root) 1991 root.SourceNamespace = "" 1992 root.SourceLabels = nil 1993 root.Gateways = nil 1994 root.IgnoreUriCase = false 1995 delegate := &networking.HTTPMatchRequest{} 1996 merged := mergeHTTPMatchRequest(root, delegate) 1997 1998 assert.Equal(t, merged, root) 1999 } 2000 2001 var gatewayNameTests = []struct { 2002 gateway string 2003 namespace string 2004 resolved string 2005 }{ 2006 { 2007 "./gateway", 2008 "default", 2009 "default/gateway", 2010 }, 2011 { 2012 "gateway", 2013 "default", 2014 "default/gateway", 2015 }, 2016 { 2017 "default/gateway", 2018 "foo", 2019 "default/gateway", 2020 }, 2021 { 2022 "gateway.default", 2023 "default", 2024 "default/gateway", 2025 }, 2026 { 2027 "gateway.default", 2028 "foo", 2029 "default/gateway", 2030 }, 2031 { 2032 "private.ingress.svc.cluster.local", 2033 "foo", 2034 "ingress/private", 2035 }, 2036 } 2037 2038 func TestResolveGatewayName(t *testing.T) { 2039 for _, tt := range gatewayNameTests { 2040 t.Run(fmt.Sprintf("%s-%s", tt.gateway, tt.namespace), func(t *testing.T) { 2041 if got := resolveGatewayName(tt.gateway, config.Meta{Namespace: tt.namespace}); got != tt.resolved { 2042 t.Fatalf("expected %q got %q", tt.resolved, got) 2043 } 2044 }) 2045 } 2046 } 2047 2048 func BenchmarkResolveGatewayName(b *testing.B) { 2049 for i := 0; i < b.N; i++ { 2050 for _, tt := range gatewayNameTests { 2051 _ = resolveGatewayName(tt.gateway, config.Meta{Namespace: tt.namespace}) 2052 } 2053 } 2054 } 2055 2056 func TestSelectVirtualService(t *testing.T) { 2057 services := []*Service{ 2058 buildHTTPService("bookinfo.com", visibility.Public, wildcardIP, "default", 9999, 70), 2059 buildHTTPService("private.com", visibility.Private, wildcardIP, "default", 9999, 80), 2060 buildHTTPService("test.com", visibility.Public, "8.8.8.8", "not-default", 8080), 2061 buildHTTPService("test-private.com", visibility.Private, "9.9.9.9", "not-default", 80, 70), 2062 buildHTTPService("test-private-2.com", visibility.Private, "9.9.9.10", "not-default", 60), 2063 buildHTTPService("test-headless.com", visibility.Public, wildcardIP, "not-default", 8888), 2064 buildHTTPService("test-headless-someother.com", visibility.Public, wildcardIP, "some-other-ns", 8888), 2065 buildHTTPService("a.test1.wildcard.com", visibility.Public, wildcardIP, "default", 8888), 2066 buildHTTPService("*.test2.wildcard.com", visibility.Public, wildcardIP, "default", 8888), 2067 } 2068 2069 hostsByNamespace := make(map[string]hostClassification) 2070 for _, svc := range services { 2071 ns := svc.Attributes.Namespace 2072 if _, exists := hostsByNamespace[ns]; !exists { 2073 hostsByNamespace[ns] = hostClassification{exactHosts: sets.New[host.Name](), allHosts: make([]host.Name, 0)} 2074 } 2075 2076 hc := hostsByNamespace[ns] 2077 hc.allHosts = append(hc.allHosts, svc.Hostname) 2078 hostsByNamespace[ns] = hc 2079 2080 if !svc.Hostname.IsWildCarded() { 2081 hostsByNamespace[ns].exactHosts.Insert(svc.Hostname) 2082 } 2083 } 2084 2085 virtualServiceSpec1 := &networking.VirtualService{ 2086 Hosts: []string{"test-private-2.com"}, 2087 Gateways: []string{"mesh"}, 2088 Http: []*networking.HTTPRoute{ 2089 { 2090 Route: []*networking.HTTPRouteDestination{ 2091 { 2092 Destination: &networking.Destination{ 2093 // Subset: "some-subset", 2094 Host: "example.org", 2095 Port: &networking.PortSelector{ 2096 Number: 61, 2097 }, 2098 }, 2099 Weight: 100, 2100 }, 2101 }, 2102 }, 2103 }, 2104 } 2105 virtualServiceSpec2 := &networking.VirtualService{ 2106 Hosts: []string{"test-private-2.com"}, 2107 Gateways: []string{"mesh"}, 2108 Http: []*networking.HTTPRoute{ 2109 { 2110 Route: []*networking.HTTPRouteDestination{ 2111 { 2112 Destination: &networking.Destination{ 2113 Host: "test.org", 2114 Port: &networking.PortSelector{ 2115 Number: 62, 2116 }, 2117 }, 2118 Weight: 100, 2119 }, 2120 }, 2121 }, 2122 }, 2123 } 2124 virtualServiceSpec3 := &networking.VirtualService{ 2125 Hosts: []string{"test-private-3.com"}, 2126 Gateways: []string{"mesh"}, 2127 Http: []*networking.HTTPRoute{ 2128 { 2129 Route: []*networking.HTTPRouteDestination{ 2130 { 2131 Destination: &networking.Destination{ 2132 Host: "test.org", 2133 Port: &networking.PortSelector{ 2134 Number: 63, 2135 }, 2136 }, 2137 Weight: 100, 2138 }, 2139 }, 2140 }, 2141 }, 2142 } 2143 virtualServiceSpec4 := &networking.VirtualService{ 2144 Hosts: []string{"test-headless.com", "example.com"}, 2145 Gateways: []string{"mesh"}, 2146 Http: []*networking.HTTPRoute{ 2147 { 2148 Route: []*networking.HTTPRouteDestination{ 2149 { 2150 Destination: &networking.Destination{ 2151 Host: "test.org", 2152 Port: &networking.PortSelector{ 2153 Number: 64, 2154 }, 2155 }, 2156 Weight: 100, 2157 }, 2158 }, 2159 }, 2160 }, 2161 } 2162 virtualServiceSpec5 := &networking.VirtualService{ 2163 Hosts: []string{"test-svc.testns.svc.cluster.local"}, 2164 Gateways: []string{"mesh"}, 2165 Http: []*networking.HTTPRoute{ 2166 { 2167 Route: []*networking.HTTPRouteDestination{ 2168 { 2169 Destination: &networking.Destination{ 2170 Host: "test-svc.testn.svc.cluster.local", 2171 }, 2172 Weight: 100, 2173 }, 2174 }, 2175 }, 2176 }, 2177 } 2178 virtualServiceSpec6 := &networking.VirtualService{ 2179 Hosts: []string{"match-no-service"}, 2180 Gateways: []string{"mesh"}, 2181 Http: []*networking.HTTPRoute{ 2182 { 2183 Route: []*networking.HTTPRouteDestination{ 2184 { 2185 Destination: &networking.Destination{ 2186 Host: "non-exist-service", 2187 }, 2188 Weight: 100, 2189 }, 2190 }, 2191 }, 2192 }, 2193 } 2194 virtualServiceSpec7 := &networking.VirtualService{ 2195 Hosts: []string{"test-headless-someother.com"}, 2196 Gateways: []string{"mesh"}, 2197 Http: []*networking.HTTPRoute{ 2198 { 2199 Route: []*networking.HTTPRouteDestination{ 2200 { 2201 Destination: &networking.Destination{ 2202 Host: "test.org", 2203 Port: &networking.PortSelector{ 2204 Number: 64, 2205 }, 2206 }, 2207 Weight: 100, 2208 }, 2209 }, 2210 }, 2211 }, 2212 } 2213 virtualServiceSpec8 := &networking.VirtualService{ 2214 Hosts: []string{"*.test1.wildcard.com"}, // match: a.test1.wildcard.com 2215 Gateways: []string{"mesh"}, 2216 Http: []*networking.HTTPRoute{ 2217 { 2218 Route: []*networking.HTTPRouteDestination{ 2219 { 2220 Destination: &networking.Destination{ 2221 Host: "test.org", 2222 Port: &networking.PortSelector{ 2223 Number: 64, 2224 }, 2225 }, 2226 Weight: 100, 2227 }, 2228 }, 2229 }, 2230 }, 2231 } 2232 virtualServiceSpec9 := &networking.VirtualService{ 2233 Hosts: []string{"foo.test2.wildcard.com"}, // match: *.test2.wildcard.com 2234 Gateways: []string{"mesh"}, 2235 Http: []*networking.HTTPRoute{ 2236 { 2237 Route: []*networking.HTTPRouteDestination{ 2238 { 2239 Destination: &networking.Destination{ 2240 Host: "test.org", 2241 Port: &networking.PortSelector{ 2242 Number: 64, 2243 }, 2244 }, 2245 Weight: 100, 2246 }, 2247 }, 2248 }, 2249 }, 2250 } 2251 virtualService1 := config.Config{ 2252 Meta: config.Meta{ 2253 GroupVersionKind: gvk.VirtualService, 2254 Name: "acme2-v1", 2255 Namespace: "not-default", 2256 }, 2257 Spec: virtualServiceSpec1, 2258 } 2259 virtualService2 := config.Config{ 2260 Meta: config.Meta{ 2261 GroupVersionKind: gvk.VirtualService, 2262 Name: "acme-v2", 2263 Namespace: "not-default", 2264 }, 2265 Spec: virtualServiceSpec2, 2266 } 2267 virtualService3 := config.Config{ 2268 Meta: config.Meta{ 2269 GroupVersionKind: gvk.VirtualService, 2270 Name: "acme-v3", 2271 Namespace: "not-default", 2272 }, 2273 Spec: virtualServiceSpec3, 2274 } 2275 virtualService4 := config.Config{ 2276 Meta: config.Meta{ 2277 GroupVersionKind: gvk.VirtualService, 2278 Name: "acme-v4", 2279 Namespace: "not-default", 2280 }, 2281 Spec: virtualServiceSpec4, 2282 } 2283 virtualService5 := config.Config{ 2284 Meta: config.Meta{ 2285 GroupVersionKind: gvk.VirtualService, 2286 Name: "acme-v3", 2287 Namespace: "not-default", 2288 }, 2289 Spec: virtualServiceSpec5, 2290 } 2291 virtualService6 := config.Config{ 2292 Meta: config.Meta{ 2293 GroupVersionKind: gvk.VirtualService, 2294 Name: "acme-v3", 2295 Namespace: "not-default", 2296 }, 2297 Spec: virtualServiceSpec6, 2298 } 2299 virtualService7 := config.Config{ 2300 Meta: config.Meta{ 2301 GroupVersionKind: gvk.VirtualService, 2302 Name: "acme2-v1", 2303 Namespace: "some-other-ns", 2304 }, 2305 Spec: virtualServiceSpec7, 2306 } 2307 virtualService8 := config.Config{ 2308 Meta: config.Meta{ 2309 GroupVersionKind: gvk.VirtualService, 2310 Name: "vs-wildcard-v1", 2311 Namespace: "default", 2312 }, 2313 Spec: virtualServiceSpec8, 2314 } 2315 virtualService9 := config.Config{ 2316 Meta: config.Meta{ 2317 GroupVersionKind: gvk.VirtualService, 2318 Name: "service-wildcard-v1", 2319 Namespace: "default", 2320 }, 2321 Spec: virtualServiceSpec9, 2322 } 2323 2324 index := virtualServiceIndex{ 2325 publicByGateway: map[string][]config.Config{ 2326 constants.IstioMeshGateway: { 2327 virtualService1, 2328 virtualService2, 2329 virtualService3, 2330 virtualService4, 2331 virtualService5, 2332 virtualService6, 2333 virtualService7, 2334 virtualService8, 2335 virtualService9, 2336 }, 2337 }, 2338 } 2339 2340 configs := SelectVirtualServices(index, "some-ns", hostsByNamespace) 2341 expectedVS := []string{ 2342 virtualService1.Name, virtualService2.Name, virtualService4.Name, virtualService7.Name, 2343 virtualService8.Name, virtualService9.Name, 2344 } 2345 if len(expectedVS) != len(configs) { 2346 t.Fatalf("Unexpected virtualService, got %d, expected %d", len(configs), len(expectedVS)) 2347 } 2348 for i, config := range configs { 2349 if config.Name != expectedVS[i] { 2350 t.Fatalf("Unexpected virtualService, got %s, expected %s", config.Name, expectedVS[i]) 2351 } 2352 } 2353 } 2354 2355 func buildHTTPService(hostname string, v visibility.Instance, ip, namespace string, ports ...int) *Service { 2356 service := &Service{ 2357 CreationTime: time.Now(), 2358 Hostname: host.Name(hostname), 2359 DefaultAddress: ip, 2360 Resolution: DNSLB, 2361 Attributes: ServiceAttributes{ 2362 ServiceRegistry: provider.Kubernetes, 2363 Namespace: namespace, 2364 ExportTo: sets.New(v), 2365 }, 2366 } 2367 if ip == wildcardIP { 2368 service.Resolution = Passthrough 2369 } 2370 2371 Ports := make([]*Port, 0) 2372 2373 for _, p := range ports { 2374 Ports = append(Ports, &Port{ 2375 Name: fmt.Sprintf("http-%d", p), 2376 Port: p, 2377 Protocol: protocol.HTTP, 2378 }) 2379 } 2380 2381 service.Ports = Ports 2382 return service 2383 } 2384 2385 func TestVirtualServiceDependencies(t *testing.T) { 2386 tests := []struct { 2387 name string 2388 vs config.Config 2389 want []ConfigKey 2390 }{ 2391 { 2392 name: "normal vs", 2393 vs: config.Config{ 2394 Meta: config.Meta{ 2395 Namespace: "ns", 2396 Name: "foo", 2397 }, 2398 }, 2399 want: []ConfigKey{ 2400 { 2401 Kind: kind.VirtualService, 2402 Namespace: "ns", 2403 Name: "foo", 2404 }, 2405 }, 2406 }, 2407 { 2408 name: "internal vs generated from http routes", 2409 vs: config.Config{ 2410 Meta: config.Meta{ 2411 Namespace: "ns", 2412 Name: "foo-0-istio-autogenerated-k8s-gateway", 2413 Annotations: map[string]string{ 2414 constants.InternalRouteSemantics: constants.RouteSemanticsGateway, 2415 constants.InternalParentNames: "HTTPRoute/foo.ns,HTTPRoute/bar.ns", 2416 }, 2417 }, 2418 }, 2419 want: []ConfigKey{ 2420 { 2421 Kind: kind.HTTPRoute, 2422 Namespace: "ns", 2423 Name: "foo", 2424 }, 2425 { 2426 Kind: kind.HTTPRoute, 2427 Namespace: "ns", 2428 Name: "bar", 2429 }, 2430 }, 2431 }, 2432 } 2433 for _, tt := range tests { 2434 t.Run(tt.name, func(t *testing.T) { 2435 if got := VirtualServiceDependencies(tt.vs); !reflect.DeepEqual(got, tt.want) { 2436 t.Errorf("VirtualServiceDependencies got %v, want %v", got, tt.want) 2437 } 2438 }) 2439 } 2440 }