istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/httproute_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 core 16 17 import ( 18 "fmt" 19 "reflect" 20 "testing" 21 "time" 22 23 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 24 route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 25 statefulsession "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/stateful_session/v3" 26 cookiev3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/stateful_session/cookie/v3" 27 headerv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/stateful_session/header/v3" 28 httpv3 "github.com/envoyproxy/go-control-plane/envoy/type/http/v3" 29 30 meshapi "istio.io/api/mesh/v1alpha1" 31 networking "istio.io/api/networking/v1alpha3" 32 "istio.io/istio/pilot/pkg/features" 33 "istio.io/istio/pilot/pkg/model" 34 "istio.io/istio/pilot/pkg/serviceregistry/provider" 35 "istio.io/istio/pilot/pkg/util/protoconv" 36 "istio.io/istio/pilot/test/xdstest" 37 "istio.io/istio/pkg/cluster" 38 "istio.io/istio/pkg/config" 39 "istio.io/istio/pkg/config/host" 40 "istio.io/istio/pkg/config/mesh" 41 "istio.io/istio/pkg/config/protocol" 42 "istio.io/istio/pkg/config/schema/gvk" 43 "istio.io/istio/pkg/config/visibility" 44 "istio.io/istio/pkg/test" 45 "istio.io/istio/pkg/test/util/assert" 46 "istio.io/istio/pkg/util/sets" 47 ) 48 49 func TestGenerateVirtualHostDomains(t *testing.T) { 50 cases := []struct { 51 name string 52 service *model.Service 53 port int 54 node *model.Proxy 55 want []string 56 enableDualStack bool 57 }{ 58 { 59 name: "same domain", 60 service: &model.Service{ 61 Hostname: "foo.local.campus.net", 62 MeshExternal: false, 63 }, 64 port: 80, 65 node: &model.Proxy{ 66 DNSDomain: "local.campus.net", 67 }, 68 want: []string{ 69 "foo.local.campus.net", 70 "foo", 71 }, 72 }, 73 { 74 name: "different domains with some shared dns", 75 service: &model.Service{ 76 Hostname: "foo.local.campus.net", 77 MeshExternal: false, 78 }, 79 port: 80, 80 node: &model.Proxy{ 81 DNSDomain: "remote.campus.net", 82 }, 83 want: []string{ 84 "foo.local.campus.net", 85 "foo.local", 86 "foo.local.campus", 87 }, 88 }, 89 { 90 name: "different domains with no shared dns", 91 service: &model.Service{ 92 Hostname: "foo.local.campus.net", 93 MeshExternal: false, 94 }, 95 port: 80, 96 node: &model.Proxy{ 97 DNSDomain: "example.com", 98 }, 99 want: []string{"foo.local.campus.net"}, 100 }, 101 { 102 name: "k8s service with default domain", 103 service: &model.Service{ 104 Hostname: "echo.default.svc.cluster.local", 105 MeshExternal: false, 106 }, 107 port: 8123, 108 node: &model.Proxy{ 109 DNSDomain: "default.svc.cluster.local", 110 }, 111 want: []string{ 112 "echo.default.svc.cluster.local", 113 "echo", 114 "echo.default.svc", 115 "echo.default", 116 }, 117 }, 118 { 119 name: "non-k8s service", 120 service: &model.Service{ 121 Hostname: "foo.default.svc.bar.baz", 122 MeshExternal: false, 123 }, 124 port: 8123, 125 node: &model.Proxy{ 126 DNSDomain: "default.svc.cluster.local", 127 }, 128 want: []string{ 129 "foo.default.svc.bar.baz", 130 }, 131 }, 132 { 133 name: "k8s service with default domain and different namespace", 134 service: &model.Service{ 135 Hostname: "echo.default.svc.cluster.local", 136 MeshExternal: false, 137 }, 138 port: 8123, 139 node: &model.Proxy{ 140 DNSDomain: "mesh.svc.cluster.local", 141 }, 142 want: []string{ 143 "echo.default.svc.cluster.local", 144 "echo.default", 145 "echo.default.svc", 146 }, 147 }, 148 { 149 name: "k8s service with custom domain 2", 150 service: &model.Service{ 151 Hostname: "google.local", 152 MeshExternal: false, 153 }, 154 port: 8123, 155 node: &model.Proxy{ 156 DNSDomain: "foo.svc.custom.k8s.local", 157 }, 158 want: []string{"google.local"}, 159 }, 160 { 161 name: "ipv4 domain", 162 service: &model.Service{ 163 Hostname: "1.2.3.4", 164 MeshExternal: false, 165 }, 166 port: 8123, 167 node: &model.Proxy{ 168 DNSDomain: "example.com", 169 }, 170 want: []string{"1.2.3.4"}, 171 }, 172 { 173 name: "ipv6 domain", 174 service: &model.Service{ 175 Hostname: "2406:3003:2064:35b8:864:a648:4b96:e37d", 176 MeshExternal: false, 177 }, 178 port: 8123, 179 node: &model.Proxy{ 180 DNSDomain: "example.com", 181 }, 182 want: []string{"[2406:3003:2064:35b8:864:a648:4b96:e37d]"}, 183 }, 184 { 185 name: "back subset of cluster domain in address", 186 service: &model.Service{ 187 Hostname: "aaa.example.local", 188 MeshExternal: true, 189 }, 190 port: 7777, 191 node: &model.Proxy{ 192 DNSDomain: "tests.svc.cluster.local", 193 }, 194 want: []string{"aaa.example.local"}, 195 }, 196 { 197 name: "front subset of cluster domain in address", 198 service: &model.Service{ 199 Hostname: "aaa.example.my", 200 MeshExternal: true, 201 }, 202 port: 7777, 203 node: &model.Proxy{ 204 DNSDomain: "tests.svc.my.long.domain.suffix", 205 }, 206 want: []string{"aaa.example.my"}, 207 }, 208 { 209 name: "large subset of cluster domain in address", 210 service: &model.Service{ 211 Hostname: "aaa.example.my.long", 212 MeshExternal: true, 213 }, 214 port: 7777, 215 node: &model.Proxy{ 216 DNSDomain: "tests.svc.my.long.domain.suffix", 217 }, 218 want: []string{"aaa.example.my.long"}, 219 }, 220 { 221 name: "no overlap of cluster domain in address", 222 service: &model.Service{ 223 Hostname: "aaa.example.com", 224 MeshExternal: true, 225 }, 226 port: 7777, 227 node: &model.Proxy{ 228 DNSDomain: "tests.svc.cluster.local", 229 }, 230 want: []string{"aaa.example.com"}, 231 }, 232 { 233 name: "wildcard", 234 service: &model.Service{ 235 Hostname: "headless.default.svc.cluster.local", 236 MeshExternal: true, 237 Resolution: model.Passthrough, 238 Attributes: model.ServiceAttributes{ServiceRegistry: provider.Kubernetes}, 239 }, 240 port: 7777, 241 node: &model.Proxy{ 242 DNSDomain: "default.svc.cluster.local", 243 }, 244 want: []string{ 245 "headless.default.svc.cluster.local", 246 "headless", 247 "headless.default.svc", 248 "headless.default", 249 "*.headless.default.svc.cluster.local", 250 "*.headless", 251 "*.headless.default.svc", 252 "*.headless.default", 253 }, 254 }, 255 { 256 name: "dual stack k8s service with default domain", 257 service: &model.Service{ 258 Hostname: "echo.default.svc.cluster.local", 259 MeshExternal: false, 260 DefaultAddress: "1.2.3.4", 261 ClusterVIPs: model.AddressMap{ 262 Addresses: map[cluster.ID][]string{ 263 "cluster-1": {"1.2.3.4", "2406:3003:2064:35b8:864:a648:4b96:e37d"}, 264 "cluster-2": {"4.3.2.1"}, // ensure other clusters aren't being populated in domains slice 265 }, 266 }, 267 }, 268 port: 8123, 269 node: &model.Proxy{ 270 DNSDomain: "default.svc.cluster.local", 271 Metadata: &model.NodeMetadata{ 272 ClusterID: "cluster-1", 273 }, 274 }, 275 want: []string{ 276 "echo.default.svc.cluster.local", 277 "echo", 278 "echo.default.svc", 279 "echo.default", 280 "1.2.3.4", 281 "[2406:3003:2064:35b8:864:a648:4b96:e37d]", 282 }, 283 enableDualStack: true, 284 }, 285 { 286 name: "alias", 287 service: &model.Service{ 288 Hostname: "foo.local.campus.net", 289 MeshExternal: false, 290 Attributes: model.ServiceAttributes{Aliases: []model.NamespacedHostname{{Hostname: "alias.local.campus.net", Namespace: "ns"}}}, 291 }, 292 port: 80, 293 node: &model.Proxy{ 294 DNSDomain: "local.campus.net", 295 }, 296 want: []string{ 297 "foo.local.campus.net", 298 "foo", 299 "alias.local.campus.net", 300 "alias", 301 }, 302 }, 303 } 304 305 testFn := func(t test.Failer, service *model.Service, port int, node *model.Proxy, want []string) { 306 out, _ := generateVirtualHostDomains(service, port, port, node) 307 assert.Equal(t, out, want) 308 } 309 310 for _, c := range cases { 311 c := c 312 t.Run(c.name, func(t *testing.T) { 313 test.SetForTest[bool](t, &features.EnableDualStack, c.enableDualStack) 314 testFn(t, c.service, c.port, c.node, c.want) 315 }) 316 } 317 } 318 319 func TestSidecarOutboundHTTPRouteConfigWithDuplicateHosts(t *testing.T) { 320 virtualServiceSpec := &networking.VirtualService{ 321 Hosts: []string{"test-duplicate-domains.default.svc.cluster.local", "test-duplicate-domains.default"}, 322 Gateways: []string{"mesh"}, 323 Http: []*networking.HTTPRoute{ 324 { 325 Route: []*networking.HTTPRouteDestination{ 326 { 327 Destination: &networking.Destination{ 328 Host: "test-duplicate-domains.default", 329 }, 330 }, 331 }, 332 }, 333 }, 334 } 335 virtualServiceSpecDuplicate := &networking.VirtualService{ 336 Hosts: []string{"Test-duplicate-domains.default.svc.cluster.local"}, 337 Gateways: []string{"mesh"}, 338 Http: []*networking.HTTPRoute{ 339 { 340 Route: []*networking.HTTPRouteDestination{ 341 { 342 Destination: &networking.Destination{ 343 Host: "test-duplicate-domains.default", 344 }, 345 }, 346 }, 347 }, 348 }, 349 } 350 virtualServiceSpecDefault := &networking.VirtualService{ 351 Hosts: []string{"test.default"}, 352 Gateways: []string{"mesh"}, 353 Http: []*networking.HTTPRoute{ 354 { 355 Route: []*networking.HTTPRouteDestination{ 356 { 357 Destination: &networking.Destination{ 358 Host: "test.default", 359 }, 360 }, 361 }, 362 }, 363 }, 364 } 365 366 cases := []struct { 367 name string 368 services []*model.Service 369 config []config.Config 370 expectedHosts map[string][]string 371 expectedDestination map[string]string 372 }{ 373 { 374 "more exact first", 375 []*model.Service{ 376 buildHTTPService("test.local", visibility.Public, "", "default", 80), 377 buildHTTPService("test", visibility.Public, "", "default", 80), 378 }, 379 nil, 380 map[string][]string{ 381 "allow_any": {"*"}, 382 // BUG: test should be below 383 "test.local:80": {"test.local"}, 384 "test:80": {"test"}, 385 }, 386 map[string]string{ 387 "allow_any": "PassthroughCluster", 388 "test.local:80": "outbound|80||test.local", 389 "test:80": "outbound|80||test", 390 }, 391 }, 392 { 393 "more exact first with full cluster domain", 394 []*model.Service{ 395 buildHTTPService("test.default.svc.cluster.local", visibility.Public, "", "default", 80), 396 buildHTTPService("test", visibility.Public, "", "default", 80), 397 }, 398 nil, 399 map[string][]string{ 400 "allow_any": {"*"}, 401 "test.default.svc.cluster.local:80": { 402 "test.default.svc.cluster.local", 403 "test.default.svc", 404 "test.default", 405 }, 406 "test:80": {"test"}, 407 }, 408 map[string]string{ 409 "allow_any": "PassthroughCluster", 410 "test.default.svc.cluster.local:80": "outbound|80||test.default.svc.cluster.local", 411 "test:80": "outbound|80||test", 412 }, 413 }, 414 { 415 "more exact second", 416 []*model.Service{ 417 buildHTTPService("test", visibility.Public, "", "default", 80), 418 buildHTTPService("test.local", visibility.Public, "", "default", 80), 419 }, 420 nil, 421 map[string][]string{ 422 "allow_any": {"*"}, 423 "test.local:80": {"test.local"}, 424 "test:80": {"test"}, 425 }, 426 map[string]string{ 427 "allow_any": "PassthroughCluster", 428 "test.local:80": "outbound|80||test.local", 429 "test:80": "outbound|80||test", 430 }, 431 }, 432 { 433 "virtual service", 434 []*model.Service{ 435 buildHTTPService("test-duplicate-domains.default.svc.cluster.local", visibility.Public, "", "default", 80), 436 }, 437 []config.Config{{ 438 Meta: config.Meta{ 439 GroupVersionKind: gvk.VirtualService, 440 Name: "acme", 441 }, 442 Spec: virtualServiceSpec, 443 }}, 444 map[string][]string{ 445 "allow_any": {"*"}, 446 "test-duplicate-domains.default.svc.cluster.local:80": { 447 "test-duplicate-domains.default.svc.cluster.local", 448 "test-duplicate-domains", 449 "test-duplicate-domains.default.svc", 450 }, 451 "test-duplicate-domains.default:80": {"test-duplicate-domains.default"}, 452 }, 453 map[string]string{ 454 "allow_any": "PassthroughCluster", 455 // Virtual service destination takes precedence 456 "test-duplicate-domains.default.svc.cluster.local:80": "outbound|80||test-duplicate-domains.default", 457 "test-duplicate-domains.default:80": "outbound|80||test-duplicate-domains.default", 458 }, 459 }, 460 { 461 "virtual service duplicate case sensitive domains", 462 []*model.Service{ 463 buildHTTPService("test-duplicate-domains.default.svc.cluster.local", visibility.Public, "", "default", 80), 464 }, 465 []config.Config{ 466 { 467 Meta: config.Meta{ 468 GroupVersionKind: gvk.VirtualService, 469 Name: "acme", 470 }, 471 Spec: virtualServiceSpec, 472 }, 473 { 474 Meta: config.Meta{ 475 GroupVersionKind: gvk.VirtualService, 476 Name: "acme-duplicate", 477 }, 478 Spec: virtualServiceSpecDuplicate, 479 }, 480 }, 481 map[string][]string{ 482 "allow_any": {"*"}, 483 "test-duplicate-domains.default.svc.cluster.local:80": { 484 "test-duplicate-domains.default.svc.cluster.local", 485 "test-duplicate-domains", 486 "test-duplicate-domains.default.svc", 487 }, 488 "test-duplicate-domains.default:80": {"test-duplicate-domains.default"}, 489 }, 490 map[string]string{ 491 "allow_any": "PassthroughCluster", 492 // Virtual service destination takes precedence 493 "test-duplicate-domains.default.svc.cluster.local:80": "outbound|80||test-duplicate-domains.default", 494 "test-duplicate-domains.default:80": "outbound|80||test-duplicate-domains.default", 495 }, 496 }, 497 { 498 "virtual service conflict", 499 []*model.Service{ 500 buildHTTPService("test.default.svc.cluster.local", visibility.Public, "", "default", 80), 501 }, 502 []config.Config{{ 503 Meta: config.Meta{ 504 GroupVersionKind: gvk.VirtualService, 505 Name: "acme", 506 }, 507 Spec: virtualServiceSpecDefault, 508 }}, 509 map[string][]string{ 510 "allow_any": {"*"}, 511 "test.default.svc.cluster.local:80": { 512 "test.default.svc.cluster.local", 513 "test", 514 "test.default.svc", 515 }, 516 "test.default:80": {"test.default"}, 517 }, 518 map[string]string{ 519 "allow_any": "PassthroughCluster", 520 // From the service, go to the service 521 "test.default.svc.cluster.local:80": "outbound|80||test.default.svc.cluster.local", 522 "test.default:80": "outbound|80||test.default", 523 }, 524 }, 525 { 526 "multiple ports", 527 []*model.Service{ 528 buildHTTPService("test.local", visibility.Public, "", "default", 70, 80, 90), 529 }, 530 nil, 531 map[string][]string{ 532 "allow_any": {"*"}, 533 "test.local:80": {"test.local"}, 534 }, 535 map[string]string{ 536 "allow_any": "PassthroughCluster", 537 "test.local:80": "outbound|80||test.local", 538 }, 539 }, 540 } 541 for _, tt := range cases { 542 t.Run(tt.name, func(t *testing.T) { 543 // ensure services are ordered 544 t0 := time.Now() 545 for _, svc := range tt.services { 546 svc.CreationTime = t0 547 t0 = t0.Add(time.Minute) 548 } 549 cg := NewConfigGenTest(t, TestOptions{ 550 Services: tt.services, 551 Configs: tt.config, 552 }) 553 554 vHostCache := make(map[int][]*route.VirtualHost) 555 resource, _ := cg.ConfigGen.buildSidecarOutboundHTTPRouteConfig( 556 cg.SetupProxy(nil), &model.PushRequest{Push: cg.PushContext()}, "80", vHostCache, nil, nil) 557 routeCfg := &route.RouteConfiguration{} 558 resource.Resource.UnmarshalTo(routeCfg) 559 xdstest.ValidateRouteConfiguration(t, routeCfg) 560 561 got := map[string][]string{} 562 clusters := map[string]string{} 563 for _, vh := range routeCfg.VirtualHosts { 564 got[vh.Name] = vh.Domains 565 clusters[vh.Name] = vh.GetRoutes()[0].GetRoute().GetCluster() 566 } 567 568 if !reflect.DeepEqual(tt.expectedHosts, got) { 569 t.Fatalf("unexpected virtual hosts\n%v, wanted\n%v", got, tt.expectedHosts) 570 } 571 572 if !reflect.DeepEqual(tt.expectedDestination, clusters) { 573 t.Fatalf("unexpected destinations\n%v, wanted\n%v", clusters, tt.expectedDestination) 574 } 575 }) 576 } 577 } 578 579 func TestSidecarStatefulsessionFilter(t *testing.T) { 580 virtualServiceSpec := &networking.VirtualService{ 581 Hosts: []string{"test-service.default.svc.cluster.local", "test-service.svc.mesh.acme.net"}, 582 Gateways: []string{"mesh"}, 583 Http: []*networking.HTTPRoute{ 584 { 585 Route: []*networking.HTTPRouteDestination{ 586 { 587 Destination: &networking.Destination{ 588 Host: "test-service.default.svc.cluster.local", 589 }, 590 }, 591 }, 592 }, 593 }, 594 } 595 596 // TODO(ramaraochavali): Add more test cases. 597 cases := []struct { 598 name string 599 services []*model.Service 600 config []config.Config 601 expectStatefulSession *statefulsession.StatefulSessionPerRoute 602 }{ 603 { 604 "session filter with no labels on service", 605 []*model.Service{ 606 buildHTTPService("test-service.default.svc.cluster.local", visibility.Public, "", "default", 80), 607 }, 608 []config.Config{{ 609 Meta: config.Meta{ 610 GroupVersionKind: gvk.VirtualService, 611 Name: "acme", 612 }, 613 Spec: virtualServiceSpec, 614 }}, 615 nil, 616 }, 617 { 618 "session filter with header", 619 []*model.Service{ 620 buildHTTPServiceWithLabels("test-service.default.svc.cluster.local", visibility.Public, "", "default", 621 map[string]string{"istio.io/persistent-session-header": "x-session-header"}, 80), 622 }, 623 []config.Config{{ 624 Meta: config.Meta{ 625 GroupVersionKind: gvk.VirtualService, 626 Name: "acme", 627 }, 628 Spec: virtualServiceSpec, 629 }}, 630 &statefulsession.StatefulSessionPerRoute{ 631 Override: &statefulsession.StatefulSessionPerRoute_StatefulSession{ 632 StatefulSession: &statefulsession.StatefulSession{ 633 SessionState: &core.TypedExtensionConfig{ 634 Name: "envoy.http.stateful_session.header", 635 TypedConfig: protoconv.MessageToAny(&headerv3.HeaderBasedSessionState{ 636 Name: "x-session-header", 637 }), 638 }, 639 }, 640 }, 641 }, 642 }, 643 { 644 "session filter with cookie", 645 []*model.Service{ 646 buildHTTPServiceWithLabels("test-service.default.svc.cluster.local", visibility.Public, "", "default", 647 map[string]string{"istio.io/persistent-session": "x-session-id"}, 80), 648 }, 649 []config.Config{{ 650 Meta: config.Meta{ 651 GroupVersionKind: gvk.VirtualService, 652 Name: "acme", 653 }, 654 Spec: virtualServiceSpec, 655 }}, 656 &statefulsession.StatefulSessionPerRoute{ 657 Override: &statefulsession.StatefulSessionPerRoute_StatefulSession{ 658 StatefulSession: &statefulsession.StatefulSession{ 659 SessionState: &core.TypedExtensionConfig{ 660 Name: "envoy.http.stateful_session.cookie", 661 TypedConfig: protoconv.MessageToAny(&cookiev3.CookieBasedSessionState{ 662 Cookie: &httpv3.Cookie{ 663 Name: "x-session-id", 664 Path: "/", 665 }, 666 }), 667 }, 668 }, 669 }, 670 }, 671 }, 672 } 673 for _, tt := range cases { 674 t.Run(tt.name, func(t *testing.T) { 675 // ensure services are ordered 676 t0 := time.Now() 677 for _, svc := range tt.services { 678 svc.CreationTime = t0 679 t0 = t0.Add(time.Minute) 680 } 681 cg := NewConfigGenTest(t, TestOptions{ 682 Services: tt.services, 683 Configs: tt.config, 684 }) 685 686 vHostCache := make(map[int][]*route.VirtualHost) 687 resource, _ := cg.ConfigGen.buildSidecarOutboundHTTPRouteConfig( 688 cg.SetupProxy(nil), &model.PushRequest{Push: cg.PushContext()}, "80", vHostCache, nil, nil) 689 routeCfg := &route.RouteConfiguration{} 690 resource.Resource.UnmarshalTo(routeCfg) 691 xdstest.ValidateRouteConfiguration(t, routeCfg) 692 693 for _, vh := range routeCfg.VirtualHosts { 694 if vh.Name == "allow_any" { 695 continue 696 } 697 if len(vh.Routes) == 0 { 698 t.Fatalf("expected routes to be found but not %s", vh.Name) 699 } 700 for _, r := range vh.Routes { 701 if tt.expectStatefulSession == nil { 702 if r.TypedPerFilterConfig != nil && 703 r.TypedPerFilterConfig["envoy.filters.http.stateful_session"] != nil { 704 t.Fatalf("stateful session config is not expected but found for %s, %s", vh.Name, r.Name) 705 } 706 } else { 707 if r.TypedPerFilterConfig == nil && r.TypedPerFilterConfig["envoy.filters.http.stateful_session"] == nil { 708 t.Fatalf("expected stateful session config but not found for %s, %s", vh.Name, r.Name) 709 } 710 incomingStatefulSession := &statefulsession.StatefulSessionPerRoute{} 711 r.TypedPerFilterConfig["envoy.filters.http.stateful_session"].UnmarshalTo(incomingStatefulSession) 712 assert.Equal(t, incomingStatefulSession, tt.expectStatefulSession) 713 } 714 } 715 } 716 }) 717 } 718 } 719 720 func TestSidecarOutboundHTTPRouteConfig(t *testing.T) { 721 services := []*model.Service{ 722 buildHTTPService("bookinfo.com", visibility.Public, wildcardIPv4, "default", 9999, 70), 723 buildHTTPService("private.com", visibility.Private, wildcardIPv4, "default", 9999, 80), 724 buildHTTPService("test.com", visibility.Public, "8.8.8.8", "not-default", 8080), 725 buildHTTPService("test-private.com", visibility.Private, "9.9.9.9", "not-default", 80, 70), 726 buildHTTPService("test-private-2.com", visibility.Private, "9.9.9.10", "not-default", 60), 727 buildHTTPService("test-headless.com", visibility.Public, wildcardIPv4, "not-default", 8888), 728 buildHTTPService("service-A.default.svc.cluster.local", visibility.Public, "", "default", 7777), 729 } 730 731 sidecarConfig := &config.Config{ 732 Meta: config.Meta{ 733 Name: "foo", 734 Namespace: "not-default", 735 GroupVersionKind: gvk.Sidecar, 736 }, 737 Spec: &networking.Sidecar{ 738 Egress: []*networking.IstioEgressListener{ 739 { 740 Port: &networking.SidecarPort{ 741 // A port that is not in any of the services 742 Number: 9000, 743 Protocol: "HTTP", 744 Name: "something", 745 }, 746 Bind: "1.1.1.1", 747 Hosts: []string{"*/bookinfo.com"}, 748 }, 749 { 750 Port: &networking.SidecarPort{ 751 // Unix domain socket listener 752 Number: 0, 753 Protocol: "HTTP", 754 Name: "something", 755 }, 756 Bind: "unix://foo/bar/baz", 757 Hosts: []string{"*/bookinfo.com"}, 758 }, 759 { 760 Port: &networking.SidecarPort{ 761 // Unix domain socket listener 762 Number: 0, 763 Protocol: "HTTP", 764 Name: "something", 765 }, 766 Bind: "unix://foo/bar/headless", 767 Hosts: []string{"*/test-headless.com"}, 768 }, 769 { 770 Port: &networking.SidecarPort{ 771 // A port that is in one of the services 772 Number: 8080, 773 Protocol: "HTTP", 774 Name: "foo", 775 }, 776 Hosts: []string{"default/bookinfo.com", "not-default/test.com"}, 777 }, 778 { 779 // Wildcard egress importing from all namespaces 780 Hosts: []string{"*/*"}, 781 }, 782 }, 783 }, 784 } 785 sidecarConfigWithWildcard := &config.Config{ 786 Meta: config.Meta{ 787 Name: "foo", 788 Namespace: "not-default", 789 GroupVersionKind: gvk.Sidecar, 790 }, 791 Spec: &networking.Sidecar{ 792 Egress: []*networking.IstioEgressListener{ 793 { 794 Port: &networking.SidecarPort{ 795 Number: 7443, 796 Protocol: "HTTP", 797 Name: "something", 798 }, 799 Hosts: []string{"*/*"}, 800 }, 801 }, 802 }, 803 } 804 sidecarConfigWitHTTPProxy := &config.Config{ 805 Meta: config.Meta{ 806 Name: "foo", 807 Namespace: "not-default", 808 GroupVersionKind: gvk.Sidecar, 809 }, 810 Spec: &networking.Sidecar{ 811 Egress: []*networking.IstioEgressListener{ 812 { 813 Port: &networking.SidecarPort{ 814 Number: 7443, 815 Protocol: "HTTP_PROXY", 816 Name: "something", 817 }, 818 Hosts: []string{"*/*"}, 819 }, 820 }, 821 }, 822 } 823 sidecarConfigWithRegistryOnly := &config.Config{ 824 Meta: config.Meta{ 825 Name: "foo", 826 Namespace: "not-default", 827 GroupVersionKind: gvk.Sidecar, 828 }, 829 Spec: &networking.Sidecar{ 830 Egress: []*networking.IstioEgressListener{ 831 { 832 Port: &networking.SidecarPort{ 833 // A port that is not in any of the services 834 Number: 9000, 835 Protocol: "HTTP", 836 Name: "something", 837 }, 838 Bind: "1.1.1.1", 839 Hosts: []string{"*/bookinfo.com"}, 840 }, 841 { 842 Port: &networking.SidecarPort{ 843 // Unix domain socket listener 844 Number: 0, 845 Protocol: "HTTP", 846 Name: "something", 847 }, 848 Bind: "unix://foo/bar/baz", 849 Hosts: []string{"*/bookinfo.com"}, 850 }, 851 { 852 Port: &networking.SidecarPort{ 853 Number: 0, 854 Protocol: "HTTP", 855 Name: "something", 856 }, 857 Bind: "unix://foo/bar/headless", 858 Hosts: []string{"*/test-headless.com"}, 859 }, 860 { 861 Port: &networking.SidecarPort{ 862 Number: 18888, 863 Protocol: "HTTP", 864 Name: "foo", 865 }, 866 Hosts: []string{"*/test-headless.com"}, 867 }, 868 { 869 // Wildcard egress importing from all namespaces 870 Hosts: []string{"*/*"}, 871 }, 872 }, 873 OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{ 874 Mode: networking.OutboundTrafficPolicy_REGISTRY_ONLY, 875 }, 876 }, 877 } 878 sidecarConfigWithAllowAny := &config.Config{ 879 Meta: config.Meta{ 880 Name: "foo", 881 Namespace: "not-default", 882 GroupVersionKind: gvk.Sidecar, 883 }, 884 Spec: &networking.Sidecar{ 885 Egress: []*networking.IstioEgressListener{ 886 { 887 Port: &networking.SidecarPort{ 888 // A port that is not in any of the services 889 Number: 9000, 890 Protocol: "HTTP", 891 Name: "something", 892 }, 893 Bind: "1.1.1.1", 894 Hosts: []string{"*/bookinfo.com"}, 895 }, 896 { 897 Port: &networking.SidecarPort{ 898 // Unix domain socket listener 899 Number: 0, 900 Protocol: "HTTP", 901 Name: "something", 902 }, 903 Bind: "unix://foo/bar/baz", 904 Hosts: []string{"*/bookinfo.com"}, 905 }, 906 { 907 Port: &networking.SidecarPort{ 908 // A port that is in one of the services 909 Number: 8080, 910 Protocol: "HTTP", 911 Name: "foo", 912 }, 913 Hosts: []string{"default/bookinfo.com", "not-default/test.com"}, 914 }, 915 { 916 // Wildcard egress importing from all namespaces 917 Hosts: []string{"*/*"}, 918 }, 919 }, 920 OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{ 921 Mode: networking.OutboundTrafficPolicy_ALLOW_ANY, 922 }, 923 }, 924 } 925 virtualServiceSpec1 := &networking.VirtualService{ 926 Hosts: []string{"test-private-2.com"}, 927 Gateways: []string{"mesh"}, 928 Http: []*networking.HTTPRoute{ 929 { 930 Route: []*networking.HTTPRouteDestination{ 931 { 932 Destination: &networking.Destination{ 933 // Subset: "some-subset", 934 Host: "example.org", 935 Port: &networking.PortSelector{ 936 Number: 61, 937 }, 938 }, 939 Weight: 100, 940 }, 941 }, 942 }, 943 }, 944 } 945 virtualServiceSpec2 := &networking.VirtualService{ 946 Hosts: []string{"test-private-2.com"}, 947 Gateways: []string{"mesh"}, 948 Http: []*networking.HTTPRoute{ 949 { 950 Route: []*networking.HTTPRouteDestination{ 951 { 952 Destination: &networking.Destination{ 953 Host: "test.org", 954 Port: &networking.PortSelector{ 955 Number: 62, 956 }, 957 }, 958 Weight: 100, 959 }, 960 }, 961 }, 962 }, 963 } 964 virtualServiceSpec3 := &networking.VirtualService{ 965 Hosts: []string{"test-private-3.com"}, 966 Gateways: []string{"mesh"}, 967 Http: []*networking.HTTPRoute{ 968 { 969 Route: []*networking.HTTPRouteDestination{ 970 { 971 Destination: &networking.Destination{ 972 Host: "test.org", 973 Port: &networking.PortSelector{ 974 Number: 63, 975 }, 976 }, 977 Weight: 100, 978 }, 979 }, 980 }, 981 }, 982 } 983 virtualServiceSpec4 := &networking.VirtualService{ 984 Hosts: []string{"test-headless.com", "example.com"}, 985 Gateways: []string{"mesh"}, 986 Http: []*networking.HTTPRoute{ 987 { 988 Route: []*networking.HTTPRouteDestination{ 989 { 990 Destination: &networking.Destination{ 991 Host: "test.org", 992 Port: &networking.PortSelector{ 993 Number: 64, 994 }, 995 }, 996 Weight: 100, 997 }, 998 }, 999 }, 1000 }, 1001 } 1002 virtualServiceSpec5 := &networking.VirtualService{ 1003 Hosts: []string{"test-svc.testns.svc.cluster.local"}, 1004 Gateways: []string{"mesh"}, 1005 Http: []*networking.HTTPRoute{ 1006 { 1007 Route: []*networking.HTTPRouteDestination{ 1008 { 1009 Destination: &networking.Destination{ 1010 Host: "test-svc.testn.svc.cluster.local", 1011 }, 1012 Weight: 100, 1013 }, 1014 }, 1015 }, 1016 }, 1017 } 1018 virtualServiceSpec6 := &networking.VirtualService{ 1019 Hosts: []string{"match-no-service"}, 1020 Gateways: []string{"mesh"}, 1021 Http: []*networking.HTTPRoute{ 1022 { 1023 Route: []*networking.HTTPRouteDestination{ 1024 { 1025 Destination: &networking.Destination{ 1026 Host: "non-exist-service", 1027 }, 1028 Weight: 100, 1029 }, 1030 }, 1031 }, 1032 }, 1033 } 1034 virtualServiceSpec7 := &networking.VirtualService{ 1035 Hosts: []string{"service-A.default.svc.cluster.local", "service-A.v2", "service-A.v3"}, 1036 Gateways: []string{"mesh"}, 1037 Http: []*networking.HTTPRoute{ 1038 { 1039 Match: []*networking.HTTPMatchRequest{ 1040 { 1041 Headers: map[string]*networking.StringMatch{":authority": {MatchType: &networking.StringMatch_Exact{Exact: "service-A.v2"}}}, 1042 }, 1043 }, 1044 Route: []*networking.HTTPRouteDestination{ 1045 { 1046 Destination: &networking.Destination{ 1047 Host: "service-A", 1048 Subset: "v2", 1049 }, 1050 }, 1051 }, 1052 }, 1053 { 1054 Match: []*networking.HTTPMatchRequest{ 1055 { 1056 Headers: map[string]*networking.StringMatch{":authority": {MatchType: &networking.StringMatch_Exact{Exact: "service-A.v3"}}}, 1057 }, 1058 }, 1059 Route: []*networking.HTTPRouteDestination{ 1060 { 1061 Destination: &networking.Destination{ 1062 Host: "service-A", 1063 Subset: "v3", 1064 }, 1065 }, 1066 }, 1067 }, 1068 }, 1069 } 1070 virtualService1 := config.Config{ 1071 Meta: config.Meta{ 1072 GroupVersionKind: gvk.VirtualService, 1073 Name: "acme2-v1", 1074 Namespace: "not-default", 1075 }, 1076 Spec: virtualServiceSpec1, 1077 } 1078 virtualService2 := config.Config{ 1079 Meta: config.Meta{ 1080 GroupVersionKind: gvk.VirtualService, 1081 Name: "acme-v2", 1082 Namespace: "not-default", 1083 }, 1084 Spec: virtualServiceSpec2, 1085 } 1086 virtualService3 := config.Config{ 1087 Meta: config.Meta{ 1088 GroupVersionKind: gvk.VirtualService, 1089 Name: "acme-v3", 1090 Namespace: "not-default", 1091 }, 1092 Spec: virtualServiceSpec3, 1093 } 1094 virtualService4 := config.Config{ 1095 Meta: config.Meta{ 1096 GroupVersionKind: gvk.VirtualService, 1097 Name: "acme-v4", 1098 Namespace: "not-default", 1099 }, 1100 Spec: virtualServiceSpec4, 1101 } 1102 virtualService5 := config.Config{ 1103 Meta: config.Meta{ 1104 GroupVersionKind: gvk.VirtualService, 1105 Name: "acme-v3", 1106 Namespace: "not-default", 1107 }, 1108 Spec: virtualServiceSpec5, 1109 } 1110 virtualService6 := config.Config{ 1111 Meta: config.Meta{ 1112 GroupVersionKind: gvk.VirtualService, 1113 Name: "acme-v3", 1114 Namespace: "not-default", 1115 }, 1116 Spec: virtualServiceSpec6, 1117 } 1118 virtualService7 := config.Config{ 1119 Meta: config.Meta{ 1120 GroupVersionKind: gvk.VirtualService, 1121 Name: "vs-1", 1122 Namespace: "default", 1123 }, 1124 Spec: virtualServiceSpec7, 1125 } 1126 1127 // With the config above, RDS should return a valid route for the following route names 1128 // port 9000 - [bookinfo.com:9999, *.bookinfo.com:9990], [bookinfo.com:70, *.bookinfo.com:70] but no bookinfo.com 1129 // unix://foo/bar/baz - [bookinfo.com:9999, *.bookinfo.com:9999], [bookinfo.com:70, *.bookinfo.com:70] but no bookinfo.com 1130 // port 8080 - [test.com, test.com:8080, 8.8.8.8, 8.8.8.8:8080], but no bookinfo.com or test.com 1131 // port 9999 - [bookinfo.com, bookinfo.com:9999, *.bookinfo.com, *.bookinfo.com:9999] 1132 // port 80 - [test-private.com, test-private.com:80, 9.9.9.9:80, 9.9.9.9] 1133 // port 70 - [test-private.com, test-private.com:70, 9.9.9.9, 9.9.9.9:70], [bookinfo.com, bookinfo.com:70] 1134 1135 // Without sidecar config [same as wildcard egress listener], expect routes 1136 // 9999 - [bookinfo.com, bookinfo.com:9999, *.bookinfo.com, *.bookinfo.com:9999], 1137 // 8080 - [test.com, test.com:8080, 8.8.8.8, 8.8.8.8:8080] 1138 // 80 - [test-private.com, test-private.com:80, 9.9.9.9:80, 9.9.9.9] 1139 // 70 - [bookinfo.com, bookinfo.com:70, *.bookinfo.com:70],[test-private.com, test-private.com:70, 9.9.9.9:70, 9.9.9.9] 1140 cases := []struct { 1141 name string 1142 routeName string 1143 sidecarConfig *config.Config 1144 virtualServiceConfigs []*config.Config 1145 // virtualHost Name and domains 1146 expectedHosts map[string]map[string]bool 1147 expectedRoutes int 1148 registryOnly bool 1149 }{ 1150 { 1151 name: "sidecar config port that is not in any service", 1152 routeName: "9000", 1153 sidecarConfig: sidecarConfig, 1154 virtualServiceConfigs: nil, 1155 expectedHosts: map[string]map[string]bool{ 1156 "block_all": { 1157 "*": true, 1158 }, 1159 }, 1160 registryOnly: true, 1161 }, 1162 { 1163 name: "sidecar config with unix domain socket listener", 1164 routeName: "unix://foo/bar/baz", 1165 sidecarConfig: sidecarConfig, 1166 virtualServiceConfigs: nil, 1167 expectedHosts: map[string]map[string]bool{ 1168 "bookinfo.com:9999": {"bookinfo.com:9999": true, "*.bookinfo.com:9999": true}, 1169 "bookinfo.com:70": {"bookinfo.com:70": true, "*.bookinfo.com:70": true}, 1170 "allow_any": { 1171 "*": true, 1172 }, 1173 }, 1174 }, 1175 { 1176 name: "sidecar config port that is in one of the services", 1177 routeName: "8080", 1178 sidecarConfig: sidecarConfig, 1179 virtualServiceConfigs: nil, 1180 expectedHosts: map[string]map[string]bool{ 1181 "test.com:8080": {"test.com": true, "8.8.8.8": true}, 1182 "block_all": { 1183 "*": true, 1184 }, 1185 }, 1186 registryOnly: true, 1187 }, 1188 { 1189 name: "sidecar config with fallthrough and registry only and allow any mesh config", 1190 routeName: "80", 1191 sidecarConfig: sidecarConfigWithRegistryOnly, 1192 virtualServiceConfigs: nil, 1193 expectedHosts: map[string]map[string]bool{ 1194 "test-private.com:80": { 1195 "test-private.com": true, "9.9.9.9": true, 1196 }, 1197 "block_all": { 1198 "*": true, 1199 }, 1200 }, 1201 registryOnly: false, 1202 }, 1203 { 1204 name: "sidecar config with fallthrough and allow any and registry only mesh config", 1205 routeName: "80", 1206 sidecarConfig: sidecarConfigWithAllowAny, 1207 virtualServiceConfigs: nil, 1208 expectedHosts: map[string]map[string]bool{ 1209 "test-private.com:80": { 1210 "test-private.com": true, "9.9.9.9": true, 1211 }, 1212 "allow_any": { 1213 "*": true, 1214 }, 1215 }, 1216 registryOnly: false, 1217 }, 1218 1219 { 1220 name: "sidecar config with allow any and virtual service includes non existing service", 1221 routeName: "80", 1222 sidecarConfig: sidecarConfigWithAllowAny, 1223 virtualServiceConfigs: []*config.Config{&virtualService6}, 1224 expectedHosts: map[string]map[string]bool{ 1225 // does not include `match-no-service` 1226 "test-private.com:80": { 1227 "test-private.com": true, "9.9.9.9": true, 1228 }, 1229 "match-no-service.not-default:80": {"match-no-service.not-default": true}, 1230 "allow_any": { 1231 "*": true, 1232 }, 1233 }, 1234 registryOnly: false, 1235 }, 1236 1237 { 1238 name: "sidecar config with allow any and virtual service includes non existing service", 1239 routeName: "80", 1240 sidecarConfig: sidecarConfigWithAllowAny, 1241 virtualServiceConfigs: []*config.Config{&virtualService6}, 1242 expectedHosts: map[string]map[string]bool{ 1243 // does not include `match-no-service` 1244 "test-private.com:80": { 1245 "test-private.com": true, "9.9.9.9": true, 1246 }, 1247 "match-no-service.not-default:80": {"match-no-service.not-default": true}, 1248 "allow_any": { 1249 "*": true, 1250 }, 1251 }, 1252 registryOnly: false, 1253 }, 1254 1255 { 1256 name: "wildcard egress importing from all namespaces: 9999", 1257 routeName: "9999", 1258 sidecarConfig: sidecarConfig, 1259 virtualServiceConfigs: nil, 1260 expectedHosts: map[string]map[string]bool{ 1261 "bookinfo.com:9999": { 1262 "bookinfo.com": true, 1263 "*.bookinfo.com": true, 1264 }, 1265 "block_all": { 1266 "*": true, 1267 }, 1268 }, 1269 registryOnly: true, 1270 }, 1271 { 1272 name: "wildcard egress importing from all namespaces: 80", 1273 routeName: "80", 1274 sidecarConfig: sidecarConfig, 1275 virtualServiceConfigs: nil, 1276 expectedHosts: map[string]map[string]bool{ 1277 "test-private.com:80": { 1278 "test-private.com": true, "9.9.9.9": true, 1279 }, 1280 "block_all": { 1281 "*": true, 1282 }, 1283 }, 1284 registryOnly: true, 1285 }, 1286 { 1287 name: "wildcard egress importing from all namespaces: 70", 1288 routeName: "70", 1289 sidecarConfig: sidecarConfig, 1290 virtualServiceConfigs: nil, 1291 expectedHosts: map[string]map[string]bool{ 1292 "test-private.com:70": { 1293 "test-private.com": true, "9.9.9.9": true, 1294 }, 1295 "bookinfo.com:70": { 1296 "bookinfo.com": true, 1297 "*.bookinfo.com": true, 1298 }, 1299 "block_all": { 1300 "*": true, 1301 }, 1302 }, 1303 registryOnly: true, 1304 }, 1305 { 1306 name: "no sidecar config - import public service from other namespaces: 9999", 1307 routeName: "9999", 1308 sidecarConfig: nil, 1309 virtualServiceConfigs: nil, 1310 expectedHosts: map[string]map[string]bool{ 1311 "bookinfo.com:9999": { 1312 "bookinfo.com": true, 1313 "*.bookinfo.com": true, 1314 }, 1315 "block_all": { 1316 "*": true, 1317 }, 1318 }, 1319 registryOnly: true, 1320 }, 1321 { 1322 name: "no sidecar config - import public service from other namespaces: 8080", 1323 routeName: "8080", 1324 sidecarConfig: nil, 1325 virtualServiceConfigs: nil, 1326 expectedHosts: map[string]map[string]bool{ 1327 "test.com:8080": { 1328 "test.com": true, "8.8.8.8": true, 1329 }, 1330 "block_all": { 1331 "*": true, 1332 }, 1333 }, 1334 registryOnly: true, 1335 }, 1336 { 1337 name: "no sidecar config - import public services from other namespaces: 80", 1338 routeName: "80", 1339 sidecarConfig: nil, 1340 virtualServiceConfigs: nil, 1341 expectedHosts: map[string]map[string]bool{ 1342 "test-private.com:80": { 1343 "test-private.com": true, "9.9.9.9": true, 1344 }, 1345 "block_all": { 1346 "*": true, 1347 }, 1348 }, 1349 registryOnly: true, 1350 }, 1351 { 1352 name: "no sidecar config - import public services from other namespaces: 70", 1353 routeName: "70", 1354 sidecarConfig: nil, 1355 virtualServiceConfigs: nil, 1356 expectedHosts: map[string]map[string]bool{ 1357 "test-private.com:70": { 1358 "test-private.com": true, "9.9.9.9": true, 1359 }, 1360 "bookinfo.com:70": { 1361 "bookinfo.com": true, 1362 "*.bookinfo.com": true, 1363 }, 1364 "block_all": { 1365 "*": true, 1366 }, 1367 }, 1368 registryOnly: true, 1369 }, 1370 { 1371 name: "no sidecar config - import public services from other namespaces: 70 with sniffing", 1372 routeName: "test-private.com:70", 1373 sidecarConfig: nil, 1374 virtualServiceConfigs: nil, 1375 expectedHosts: map[string]map[string]bool{ 1376 "test-private.com:70": { 1377 "*": true, 1378 }, 1379 }, 1380 registryOnly: true, 1381 }, 1382 { 1383 name: "no sidecar config - import public services from other namespaces: 80 with fallthrough", 1384 routeName: "80", 1385 sidecarConfig: nil, 1386 virtualServiceConfigs: nil, 1387 expectedHosts: map[string]map[string]bool{ 1388 "test-private.com:80": { 1389 "test-private.com": true, "9.9.9.9": true, 1390 }, 1391 "allow_any": { 1392 "*": true, 1393 }, 1394 }, 1395 registryOnly: false, 1396 }, 1397 { 1398 name: "no sidecar config - import public services from other namespaces: 80 with fallthrough and registry only", 1399 routeName: "80", 1400 sidecarConfig: nil, 1401 virtualServiceConfigs: nil, 1402 expectedHosts: map[string]map[string]bool{ 1403 "test-private.com:80": { 1404 "test-private.com": true, "9.9.9.9": true, 1405 }, 1406 "block_all": { 1407 "*": true, 1408 }, 1409 }, 1410 registryOnly: true, 1411 }, 1412 { 1413 name: "no sidecar config with virtual services with duplicate entries", 1414 routeName: "60", 1415 sidecarConfig: nil, 1416 virtualServiceConfigs: []*config.Config{&virtualService1, &virtualService2}, 1417 expectedHosts: map[string]map[string]bool{ 1418 "test-private-2.com:60": { 1419 "test-private-2.com": true, "9.9.9.10": true, 1420 }, 1421 "block_all": { 1422 "*": true, 1423 }, 1424 }, 1425 registryOnly: true, 1426 }, 1427 { 1428 name: "no sidecar config with virtual services with no service in registry", 1429 routeName: "80", // no service for the host in registry; use port 80 by default 1430 sidecarConfig: nil, 1431 virtualServiceConfigs: []*config.Config{&virtualService3}, 1432 expectedHosts: map[string]map[string]bool{ 1433 "test-private.com:80": { 1434 "test-private.com": true, "9.9.9.9": true, 1435 }, 1436 "test-private-3.com:80": { 1437 "test-private-3.com": true, 1438 }, 1439 "block_all": { 1440 "*": true, 1441 }, 1442 }, 1443 registryOnly: true, 1444 }, 1445 { 1446 name: "no sidecar config - import headless service from other namespaces: 8888", 1447 routeName: "8888", 1448 sidecarConfig: nil, 1449 virtualServiceConfigs: nil, 1450 expectedHosts: map[string]map[string]bool{ 1451 "test-headless.com:8888": { 1452 "test-headless.com": true, "*.test-headless.com": true, 1453 }, 1454 "block_all": { 1455 "*": true, 1456 }, 1457 }, 1458 registryOnly: true, 1459 }, 1460 { 1461 name: "no sidecar config with virtual services - import headless service from other namespaces: 8888", 1462 routeName: "8888", 1463 sidecarConfig: nil, 1464 virtualServiceConfigs: []*config.Config{&virtualService4}, 1465 expectedHosts: map[string]map[string]bool{ 1466 "test-headless.com:8888": { 1467 "test-headless.com": true, "*.test-headless.com": true, 1468 }, 1469 "example.com:8888": { 1470 "example.com": true, 1471 }, 1472 "block_all": { 1473 "*": true, 1474 }, 1475 }, 1476 registryOnly: true, 1477 }, 1478 { 1479 name: "sidecar config with unix domain socket listener - import headless service", 1480 routeName: "unix://foo/bar/headless", 1481 sidecarConfig: sidecarConfig, 1482 virtualServiceConfigs: nil, 1483 expectedHosts: map[string]map[string]bool{ 1484 "test-headless.com:8888": {"test-headless.com:8888": true, "*.test-headless.com:8888": true}, 1485 "block_all": { 1486 "*": true, 1487 }, 1488 }, 1489 registryOnly: true, 1490 }, 1491 { 1492 name: "sidecar config port - import headless service", 1493 routeName: "18888", 1494 sidecarConfig: sidecarConfigWithRegistryOnly, 1495 virtualServiceConfigs: nil, 1496 expectedHosts: map[string]map[string]bool{ 1497 "block_all": { 1498 "*": true, 1499 }, 1500 }, 1501 registryOnly: true, 1502 }, 1503 { 1504 name: "wild card sidecar config, with non matching virtual service", 1505 routeName: "7443", 1506 sidecarConfig: sidecarConfigWithWildcard, 1507 virtualServiceConfigs: []*config.Config{&virtualService5}, 1508 expectedHosts: map[string]map[string]bool{ 1509 "block_all": { 1510 "*": true, 1511 }, 1512 }, 1513 registryOnly: true, 1514 }, 1515 { 1516 name: "http proxy sidecar config, with non matching virtual service", 1517 routeName: "7443", 1518 sidecarConfig: sidecarConfigWitHTTPProxy, 1519 virtualServiceConfigs: []*config.Config{&virtualService5}, 1520 expectedHosts: map[string]map[string]bool{ 1521 "bookinfo.com:9999": {"bookinfo.com:9999": true, "*.bookinfo.com:9999": true}, 1522 "bookinfo.com:70": {"bookinfo.com:70": true, "*.bookinfo.com:70": true}, 1523 "test-headless.com:8888": {"test-headless.com:8888": true, "*.test-headless.com:8888": true}, 1524 "test-private-2.com:60": { 1525 "test-private-2.com:60": true, "9.9.9.10:60": true, 1526 }, 1527 "test-private.com:70": { 1528 "test-private.com:70": true, "9.9.9.9:70": true, 1529 }, 1530 "test-private.com:80": { 1531 "test-private.com": true, "test-private.com:80": true, "9.9.9.9": true, "9.9.9.9:80": true, 1532 }, 1533 "test.com:8080": { 1534 "test.com:8080": true, "8.8.8.8:8080": true, 1535 }, 1536 "service-A.default.svc.cluster.local:7777": { 1537 "service-A.default.svc.cluster.local:7777": true, 1538 }, 1539 "block_all": { 1540 "*": true, 1541 }, 1542 }, 1543 registryOnly: true, 1544 }, 1545 { 1546 name: "virtual service hosts with subsets and with existing service", 1547 routeName: "7777", 1548 sidecarConfig: sidecarConfigWithAllowAny, 1549 virtualServiceConfigs: []*config.Config{&virtualService7}, 1550 expectedHosts: map[string]map[string]bool{ 1551 "allow_any": { 1552 "*": true, 1553 }, 1554 "service-A.default.svc.cluster.local:7777": { 1555 "service-A.default.svc.cluster.local": true, 1556 }, 1557 "service-A.v2:7777": { 1558 "service-A.v2": true, 1559 }, 1560 "service-A.v3:7777": { 1561 "service-A.v3": true, 1562 }, 1563 }, 1564 expectedRoutes: 7, 1565 registryOnly: false, 1566 }, 1567 } 1568 1569 for _, c := range cases { 1570 t.Run(c.name, func(t *testing.T) { 1571 testSidecarRDSVHosts(t, services, c.sidecarConfig, c.virtualServiceConfigs, 1572 c.routeName, c.expectedHosts, c.expectedRoutes, c.registryOnly) 1573 }) 1574 } 1575 } 1576 1577 func TestSelectVirtualService(t *testing.T) { 1578 services := []*model.Service{ 1579 buildHTTPService("bookinfo.com", visibility.Public, wildcardIPv4, "default", 9999, 70), 1580 buildHTTPService("private.com", visibility.Private, wildcardIPv4, "default", 9999, 80), 1581 buildHTTPService("test.com", visibility.Public, "8.8.8.8", "not-default", 8080), 1582 buildHTTPService("test-private.com", visibility.Private, "9.9.9.9", "not-default", 80, 70), 1583 buildHTTPService("test-private-2.com", visibility.Private, "9.9.9.10", "not-default", 60), 1584 buildHTTPService("test-headless.com", visibility.Public, wildcardIPv4, "not-default", 8888), 1585 } 1586 1587 servicesByName := make(map[host.Name]*model.Service, len(services)) 1588 for _, svc := range services { 1589 servicesByName[svc.Hostname] = svc 1590 } 1591 1592 virtualServiceSpec1 := &networking.VirtualService{ 1593 Hosts: []string{"test-private-2.com"}, 1594 Gateways: []string{"mesh"}, 1595 Http: []*networking.HTTPRoute{ 1596 { 1597 Route: []*networking.HTTPRouteDestination{ 1598 { 1599 Destination: &networking.Destination{ 1600 // Subset: "some-subset", 1601 Host: "example.org", 1602 Port: &networking.PortSelector{ 1603 Number: 61, 1604 }, 1605 }, 1606 Weight: 100, 1607 }, 1608 }, 1609 }, 1610 }, 1611 } 1612 virtualServiceSpec2 := &networking.VirtualService{ 1613 Hosts: []string{"test-private-2.com"}, 1614 Gateways: []string{"mesh"}, 1615 Http: []*networking.HTTPRoute{ 1616 { 1617 Route: []*networking.HTTPRouteDestination{ 1618 { 1619 Destination: &networking.Destination{ 1620 Host: "test.org", 1621 Port: &networking.PortSelector{ 1622 Number: 62, 1623 }, 1624 }, 1625 Weight: 100, 1626 }, 1627 }, 1628 }, 1629 }, 1630 } 1631 virtualServiceSpec3 := &networking.VirtualService{ 1632 Hosts: []string{"test-private-3.com"}, 1633 Gateways: []string{"mesh"}, 1634 Http: []*networking.HTTPRoute{ 1635 { 1636 Route: []*networking.HTTPRouteDestination{ 1637 { 1638 Destination: &networking.Destination{ 1639 Host: "test.org", 1640 Port: &networking.PortSelector{ 1641 Number: 63, 1642 }, 1643 }, 1644 Weight: 100, 1645 }, 1646 }, 1647 }, 1648 }, 1649 } 1650 virtualServiceSpec4 := &networking.VirtualService{ 1651 Hosts: []string{"test-headless.com", "example.com"}, 1652 Gateways: []string{"mesh"}, 1653 Http: []*networking.HTTPRoute{ 1654 { 1655 Route: []*networking.HTTPRouteDestination{ 1656 { 1657 Destination: &networking.Destination{ 1658 Host: "test.org", 1659 Port: &networking.PortSelector{ 1660 Number: 64, 1661 }, 1662 }, 1663 Weight: 100, 1664 }, 1665 }, 1666 }, 1667 }, 1668 } 1669 virtualServiceSpec5 := &networking.VirtualService{ 1670 Hosts: []string{"test-svc.testns.svc.cluster.local"}, 1671 Gateways: []string{"mesh"}, 1672 Http: []*networking.HTTPRoute{ 1673 { 1674 Route: []*networking.HTTPRouteDestination{ 1675 { 1676 Destination: &networking.Destination{ 1677 Host: "test-svc.testn.svc.cluster.local", 1678 }, 1679 Weight: 100, 1680 }, 1681 }, 1682 }, 1683 }, 1684 } 1685 virtualServiceSpec6 := &networking.VirtualService{ 1686 Hosts: []string{"match-no-service"}, 1687 Gateways: []string{"mesh"}, 1688 Http: []*networking.HTTPRoute{ 1689 { 1690 Route: []*networking.HTTPRouteDestination{ 1691 { 1692 Destination: &networking.Destination{ 1693 Host: "non-exist-service", 1694 }, 1695 Weight: 100, 1696 }, 1697 }, 1698 }, 1699 }, 1700 } 1701 virtualService1 := config.Config{ 1702 Meta: config.Meta{ 1703 GroupVersionKind: gvk.VirtualService, 1704 Name: "acme2-v1", 1705 Namespace: "not-default", 1706 }, 1707 Spec: virtualServiceSpec1, 1708 } 1709 virtualService2 := config.Config{ 1710 Meta: config.Meta{ 1711 GroupVersionKind: gvk.VirtualService, 1712 Name: "acme-v2", 1713 Namespace: "not-default", 1714 }, 1715 Spec: virtualServiceSpec2, 1716 } 1717 virtualService3 := config.Config{ 1718 Meta: config.Meta{ 1719 GroupVersionKind: gvk.VirtualService, 1720 Name: "acme-v3", 1721 Namespace: "not-default", 1722 }, 1723 Spec: virtualServiceSpec3, 1724 } 1725 virtualService4 := config.Config{ 1726 Meta: config.Meta{ 1727 GroupVersionKind: gvk.VirtualService, 1728 Name: "acme-v4", 1729 Namespace: "not-default", 1730 }, 1731 Spec: virtualServiceSpec4, 1732 } 1733 virtualService5 := config.Config{ 1734 Meta: config.Meta{ 1735 GroupVersionKind: gvk.VirtualService, 1736 Name: "acme-v3", 1737 Namespace: "not-default", 1738 }, 1739 Spec: virtualServiceSpec5, 1740 } 1741 virtualService6 := config.Config{ 1742 Meta: config.Meta{ 1743 GroupVersionKind: gvk.VirtualService, 1744 Name: "acme-v3", 1745 Namespace: "not-default", 1746 }, 1747 Spec: virtualServiceSpec6, 1748 } 1749 configs := selectVirtualServices( 1750 []config.Config{virtualService1, virtualService2, virtualService3, virtualService4, virtualService5, virtualService6}, 1751 servicesByName) 1752 expectedVS := []string{virtualService1.Name, virtualService2.Name, virtualService4.Name} 1753 if len(expectedVS) != len(configs) { 1754 t.Fatalf("Unexpected virtualService, got %d, expected %d", len(configs), len(expectedVS)) 1755 } 1756 for i, config := range configs { 1757 if config.Name != expectedVS[i] { 1758 t.Fatalf("Unexpected virtualService, got %s, expected %s", config.Name, expectedVS[i]) 1759 } 1760 } 1761 } 1762 1763 func testSidecarRDSVHosts(t *testing.T, services []*model.Service, 1764 sidecarConfig *config.Config, virtualServices []*config.Config, routeName string, 1765 expectedHosts map[string]map[string]bool, expectedRoutes int, registryOnly bool, 1766 ) { 1767 m := mesh.DefaultMeshConfig() 1768 if registryOnly { 1769 m.OutboundTrafficPolicy = &meshapi.MeshConfig_OutboundTrafficPolicy{Mode: meshapi.MeshConfig_OutboundTrafficPolicy_REGISTRY_ONLY} 1770 } 1771 cg := NewConfigGenTest(t, TestOptions{ 1772 MeshConfig: m, 1773 Services: services, 1774 ConfigPointers: append(virtualServices, sidecarConfig), 1775 }) 1776 1777 proxy := &model.Proxy{ConfigNamespace: "not-default", DNSDomain: "default.example.org"} 1778 vHostCache := make(map[int][]*route.VirtualHost) 1779 resource, _ := cg.ConfigGen.buildSidecarOutboundHTTPRouteConfig( 1780 cg.SetupProxy(proxy), &model.PushRequest{Push: cg.PushContext()}, routeName, vHostCache, nil, nil) 1781 routeCfg := &route.RouteConfiguration{} 1782 resource.Resource.UnmarshalTo(routeCfg) 1783 xdstest.ValidateRouteConfiguration(t, routeCfg) 1784 1785 if expectedRoutes == 0 { 1786 expectedRoutes = len(expectedHosts) 1787 } 1788 numberOfRoutes := 0 1789 for _, vhost := range routeCfg.VirtualHosts { 1790 numberOfRoutes += len(vhost.Routes) 1791 if _, found := expectedHosts[vhost.Name]; !found { 1792 t.Fatalf("unexpected vhost block %s for route %s", 1793 vhost.Name, routeName) 1794 } 1795 1796 for _, domain := range vhost.Domains { 1797 if !expectedHosts[vhost.Name][domain] { 1798 t.Fatalf("unexpected vhost domain %s in vhost %s, for route %s", domain, vhost.Name, routeName) 1799 } 1800 } 1801 for want := range expectedHosts[vhost.Name] { 1802 found := false 1803 for _, got := range vhost.Domains { 1804 if got == want { 1805 found = true 1806 } 1807 } 1808 if !found { 1809 t.Fatalf("expected vhost domain %s in vhost %s, for route %s not found. got domains %v", want, vhost.Name, routeName, vhost.Domains) 1810 } 1811 } 1812 1813 if !vhost.GetIncludeRequestAttemptCount() { 1814 t.Fatal("Expected that include request attempt count is set to true, but set to false") 1815 } 1816 } 1817 if (expectedRoutes >= 0) && (numberOfRoutes != expectedRoutes) { 1818 t.Errorf("Wrong number of routes. expected: %v, Got: %v", expectedRoutes, numberOfRoutes) 1819 } 1820 } 1821 1822 func buildHTTPServiceWithLabels(hostname string, v visibility.Instance, ip, namespace string, labels map[string]string, ports ...int) *model.Service { 1823 svc := buildHTTPService(hostname, v, ip, namespace, ports...) 1824 svc.Attributes.Labels = labels 1825 return svc 1826 } 1827 1828 func buildHTTPService(hostname string, v visibility.Instance, ip, namespace string, ports ...int) *model.Service { 1829 service := &model.Service{ 1830 CreationTime: tnow, 1831 Hostname: host.Name(hostname), 1832 DefaultAddress: ip, 1833 Resolution: model.DNSLB, 1834 Attributes: model.ServiceAttributes{ 1835 ServiceRegistry: provider.Kubernetes, 1836 Namespace: namespace, 1837 ExportTo: sets.New(v), 1838 }, 1839 } 1840 if ip == wildcardIPv4 { 1841 service.Resolution = model.Passthrough 1842 } 1843 1844 Ports := make([]*model.Port, 0) 1845 1846 for _, p := range ports { 1847 Ports = append(Ports, &model.Port{ 1848 Name: fmt.Sprintf("http-%d", p), 1849 Port: p, 1850 Protocol: protocol.HTTP, 1851 }) 1852 } 1853 1854 service.Ports = Ports 1855 return service 1856 }