1package xds 2 3import ( 4 "bytes" 5 "path/filepath" 6 "sort" 7 "testing" 8 "text/template" 9 "time" 10 11 envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" 12 13 testinf "github.com/mitchellh/go-testing-interface" 14 "github.com/stretchr/testify/require" 15 16 "github.com/hashicorp/consul/agent/connect" 17 "github.com/hashicorp/consul/agent/consul/discoverychain" 18 "github.com/hashicorp/consul/agent/proxycfg" 19 "github.com/hashicorp/consul/agent/structs" 20 "github.com/hashicorp/consul/agent/xds/proxysupport" 21 "github.com/hashicorp/consul/lib/stringslice" 22 "github.com/hashicorp/consul/sdk/testutil" 23 "github.com/hashicorp/consul/types" 24) 25 26func TestListenersFromSnapshot(t *testing.T) { 27 if testing.Short() { 28 t.Skip("too slow for testing.Short") 29 } 30 31 tests := []struct { 32 name string 33 create func(t testinf.T) *proxycfg.ConfigSnapshot 34 // Setup is called before the test starts. It is passed the snapshot from 35 // TestConfigSnapshot and is allowed to modify it in any way to setup the 36 // test input. 37 setup func(snap *proxycfg.ConfigSnapshot) 38 overrideGoldenName string 39 generatorSetup func(*ResourceGenerator) 40 }{ 41 { 42 name: "defaults", 43 create: proxycfg.TestConfigSnapshot, 44 setup: nil, // Default snapshot 45 }, 46 { 47 name: "listener-bind-address", 48 create: proxycfg.TestConfigSnapshot, 49 setup: func(snap *proxycfg.ConfigSnapshot) { 50 snap.Proxy.Config["bind_address"] = "127.0.0.2" 51 }, 52 }, 53 { 54 name: "listener-bind-port", 55 create: proxycfg.TestConfigSnapshot, 56 setup: func(snap *proxycfg.ConfigSnapshot) { 57 snap.Proxy.Config["bind_port"] = 8888 58 }, 59 }, 60 { 61 name: "listener-bind-address-port", 62 create: proxycfg.TestConfigSnapshot, 63 setup: func(snap *proxycfg.ConfigSnapshot) { 64 snap.Proxy.Config["bind_address"] = "127.0.0.2" 65 snap.Proxy.Config["bind_port"] = 8888 66 }, 67 }, 68 { 69 name: "listener-unix-domain-socket", 70 create: proxycfg.TestConfigSnapshot, 71 setup: func(snap *proxycfg.ConfigSnapshot) { 72 snap.Proxy.Upstreams[0].LocalBindAddress = "" 73 snap.Proxy.Upstreams[0].LocalBindPort = 0 74 snap.Proxy.Upstreams[0].LocalBindSocketPath = "/tmp/service-mesh/client-1/grpc-employee-server" 75 snap.Proxy.Upstreams[0].LocalBindSocketMode = "0640" 76 }, 77 }, 78 { 79 name: "http-public-listener", 80 create: proxycfg.TestConfigSnapshot, 81 setup: func(snap *proxycfg.ConfigSnapshot) { 82 snap.Proxy.Config["protocol"] = "http" 83 }, 84 }, 85 { 86 name: "http-listener-with-timeouts", 87 create: proxycfg.TestConfigSnapshot, 88 setup: func(snap *proxycfg.ConfigSnapshot) { 89 snap.Proxy.Config["protocol"] = "http" 90 snap.Proxy.Config["local_connect_timeout_ms"] = 1234 91 snap.Proxy.Config["local_request_timeout_ms"] = 2345 92 }, 93 }, 94 { 95 name: "http-upstream", 96 create: proxycfg.TestConfigSnapshot, 97 setup: func(snap *proxycfg.ConfigSnapshot) { 98 snap.Proxy.Upstreams[0].Config["protocol"] = "http" 99 }, 100 }, 101 { 102 name: "custom-public-listener", 103 create: proxycfg.TestConfigSnapshot, 104 setup: func(snap *proxycfg.ConfigSnapshot) { 105 snap.Proxy.Config["envoy_public_listener_json"] = 106 customListenerJSON(t, customListenerJSONOptions{ 107 Name: "custom-public-listen", 108 }) 109 }, 110 }, 111 { 112 name: "custom-public-listener-http", 113 create: proxycfg.TestConfigSnapshot, 114 setup: func(snap *proxycfg.ConfigSnapshot) { 115 snap.Proxy.Config["protocol"] = "http" 116 snap.Proxy.Config["envoy_public_listener_json"] = 117 customHTTPListenerJSON(t, customHTTPListenerJSONOptions{ 118 Name: "custom-public-listen", 119 }) 120 }, 121 }, 122 { 123 name: "custom-public-listener-http-2", 124 create: proxycfg.TestConfigSnapshot, 125 setup: func(snap *proxycfg.ConfigSnapshot) { 126 snap.Proxy.Config["protocol"] = "http" 127 snap.Proxy.Config["envoy_public_listener_json"] = 128 customHTTPListenerJSON(t, customHTTPListenerJSONOptions{ 129 Name: "custom-public-listen", 130 HTTPConnectionManagerName: httpConnectionManagerNewName, 131 }) 132 }, 133 }, 134 { 135 name: "custom-public-listener-http-missing", 136 create: proxycfg.TestConfigSnapshot, 137 setup: func(snap *proxycfg.ConfigSnapshot) { 138 snap.Proxy.Config["protocol"] = "http" 139 snap.Proxy.Config["envoy_public_listener_json"] = 140 customListenerJSON(t, customListenerJSONOptions{ 141 Name: "custom-public-listen", 142 }) 143 }, 144 }, 145 { 146 name: "custom-public-listener-ignores-tls", 147 create: proxycfg.TestConfigSnapshot, 148 overrideGoldenName: "custom-public-listener", // should be the same 149 setup: func(snap *proxycfg.ConfigSnapshot) { 150 snap.Proxy.Config["envoy_public_listener_json"] = 151 customListenerJSON(t, customListenerJSONOptions{ 152 Name: "custom-public-listen", 153 // Attempt to override the TLS context should be ignored 154 TLSContext: `"allowRenegotiation": false`, 155 }) 156 }, 157 }, 158 { 159 name: "custom-upstream", 160 create: proxycfg.TestConfigSnapshot, 161 setup: func(snap *proxycfg.ConfigSnapshot) { 162 snap.Proxy.Upstreams[0].Config["envoy_listener_json"] = 163 customListenerJSON(t, customListenerJSONOptions{ 164 Name: "custom-upstream", 165 }) 166 }, 167 }, 168 { 169 name: "custom-upstream-ignored-with-disco-chain", 170 create: proxycfg.TestConfigSnapshotDiscoveryChainWithFailover, 171 setup: func(snap *proxycfg.ConfigSnapshot) { 172 snap.Proxy.Upstreams[0].Config["envoy_listener_json"] = 173 customListenerJSON(t, customListenerJSONOptions{ 174 Name: "custom-upstream", 175 }) 176 }, 177 }, 178 { 179 name: "splitter-with-resolver-redirect", 180 create: proxycfg.TestConfigSnapshotDiscoveryChain_SplitterWithResolverRedirectMultiDC, 181 setup: nil, 182 }, 183 { 184 name: "connect-proxy-with-tcp-chain", 185 create: proxycfg.TestConfigSnapshotDiscoveryChain, 186 setup: nil, 187 }, 188 { 189 name: "connect-proxy-with-http-chain", 190 create: func(t testinf.T) *proxycfg.ConfigSnapshot { 191 return proxycfg.TestConfigSnapshotDiscoveryChainWithEntries(t, 192 &structs.ProxyConfigEntry{ 193 Kind: structs.ProxyDefaults, 194 Name: structs.ProxyConfigGlobal, 195 Config: map[string]interface{}{ 196 "protocol": "http", 197 }, 198 }, 199 ) 200 }, 201 setup: nil, 202 }, 203 { 204 name: "connect-proxy-with-http2-chain", 205 create: func(t testinf.T) *proxycfg.ConfigSnapshot { 206 return proxycfg.TestConfigSnapshotDiscoveryChainWithEntries(t, 207 &structs.ProxyConfigEntry{ 208 Kind: structs.ProxyDefaults, 209 Name: structs.ProxyConfigGlobal, 210 Config: map[string]interface{}{ 211 "protocol": "http2", 212 }, 213 }, 214 ) 215 }, 216 setup: nil, 217 }, 218 { 219 name: "connect-proxy-with-grpc-chain", 220 create: func(t testinf.T) *proxycfg.ConfigSnapshot { 221 return proxycfg.TestConfigSnapshotDiscoveryChainWithEntries(t, 222 &structs.ProxyConfigEntry{ 223 Kind: structs.ProxyDefaults, 224 Name: structs.ProxyConfigGlobal, 225 Config: map[string]interface{}{ 226 "protocol": "grpc", 227 }, 228 }, 229 ) 230 }, 231 setup: nil, 232 }, 233 { 234 name: "connect-proxy-with-chain-external-sni", 235 create: proxycfg.TestConfigSnapshotDiscoveryChainExternalSNI, 236 setup: nil, 237 }, 238 { 239 name: "connect-proxy-with-chain-and-overrides", 240 create: proxycfg.TestConfigSnapshotDiscoveryChainWithOverrides, 241 setup: nil, 242 }, 243 { 244 name: "connect-proxy-with-tcp-chain-failover-through-remote-gateway", 245 create: proxycfg.TestConfigSnapshotDiscoveryChainWithFailoverThroughRemoteGateway, 246 setup: nil, 247 }, 248 { 249 name: "connect-proxy-with-tcp-chain-failover-through-local-gateway", 250 create: proxycfg.TestConfigSnapshotDiscoveryChainWithFailoverThroughLocalGateway, 251 setup: nil, 252 }, 253 { 254 name: "expose-paths-local-app-paths", 255 create: proxycfg.TestConfigSnapshotExposeConfig, 256 }, 257 { 258 name: "expose-paths-new-cluster-http2", 259 create: proxycfg.TestConfigSnapshotExposeConfig, 260 setup: func(snap *proxycfg.ConfigSnapshot) { 261 snap.Proxy.Expose.Paths[1] = structs.ExposePath{ 262 LocalPathPort: 9090, 263 Path: "/grpc.health.v1.Health/Check", 264 ListenerPort: 21501, 265 Protocol: "http2", 266 } 267 }, 268 }, 269 { 270 // NOTE: if IPv6 is not supported in the kernel per 271 // kernelSupportsIPv6() then this test will fail because the golden 272 // files were generated assuming ipv6 support was present 273 name: "expose-checks", 274 create: proxycfg.TestConfigSnapshotExposeConfig, 275 setup: func(snap *proxycfg.ConfigSnapshot) { 276 snap.Proxy.Expose = structs.ExposeConfig{ 277 Checks: true, 278 } 279 }, 280 generatorSetup: func(s *ResourceGenerator) { 281 s.CfgFetcher = configFetcherFunc(func() string { 282 return "192.0.2.1" 283 }) 284 285 s.CheckFetcher = httpCheckFetcherFunc(func(sid structs.ServiceID) []structs.CheckType { 286 if sid != structs.NewServiceID("web", nil) { 287 return nil 288 } 289 return []structs.CheckType{{ 290 CheckID: types.CheckID("http"), 291 Name: "http", 292 HTTP: "http://127.0.0.1:8181/debug", 293 ProxyHTTP: "http://:21500/debug", 294 Method: "GET", 295 Interval: 10 * time.Second, 296 Timeout: 1 * time.Second, 297 }} 298 }) 299 }, 300 }, 301 { 302 name: "mesh-gateway", 303 create: proxycfg.TestConfigSnapshotMeshGateway, 304 }, 305 { 306 name: "mesh-gateway-using-federation-states", 307 create: proxycfg.TestConfigSnapshotMeshGatewayUsingFederationStates, 308 }, 309 { 310 name: "mesh-gateway-no-services", 311 create: proxycfg.TestConfigSnapshotMeshGatewayNoServices, 312 }, 313 { 314 name: "mesh-gateway-tagged-addresses", 315 create: proxycfg.TestConfigSnapshotMeshGateway, 316 setup: func(snap *proxycfg.ConfigSnapshot) { 317 snap.Proxy.Config = map[string]interface{}{ 318 "envoy_mesh_gateway_no_default_bind": true, 319 "envoy_mesh_gateway_bind_tagged_addresses": true, 320 } 321 }, 322 }, 323 { 324 name: "mesh-gateway-custom-addresses", 325 create: proxycfg.TestConfigSnapshotMeshGateway, 326 setup: func(snap *proxycfg.ConfigSnapshot) { 327 snap.Proxy.Config = map[string]interface{}{ 328 "envoy_mesh_gateway_bind_addresses": map[string]structs.ServiceAddress{ 329 "foo": { 330 Address: "198.17.2.3", 331 Port: 8080, 332 }, 333 "bar": { 334 Address: "2001:db8::ff", 335 Port: 9999, 336 }, 337 "baz": { 338 Address: "127.0.0.1", 339 Port: 8765, 340 }, 341 }, 342 } 343 }, 344 }, 345 { 346 name: "ingress-gateway", 347 create: proxycfg.TestConfigSnapshotIngressGateway, 348 setup: nil, 349 }, 350 { 351 name: "ingress-gateway-bind-addrs", 352 create: proxycfg.TestConfigSnapshotIngressGateway, 353 setup: func(snap *proxycfg.ConfigSnapshot) { 354 snap.TaggedAddresses = map[string]structs.ServiceAddress{ 355 "lan": {Address: "10.0.0.1"}, 356 "wan": {Address: "172.16.0.1"}, 357 } 358 snap.Proxy.Config = map[string]interface{}{ 359 "envoy_gateway_no_default_bind": true, 360 "envoy_gateway_bind_tagged_addresses": true, 361 "envoy_gateway_bind_addresses": map[string]structs.ServiceAddress{ 362 "foo": {Address: "8.8.8.8"}, 363 }, 364 } 365 }, 366 }, 367 { 368 name: "ingress-gateway-no-services", 369 create: proxycfg.TestConfigSnapshotIngressGatewayNoServices, 370 setup: nil, 371 }, 372 { 373 name: "ingress-with-chain-external-sni", 374 create: proxycfg.TestConfigSnapshotIngressExternalSNI, 375 setup: nil, 376 }, 377 { 378 name: "ingress-with-chain-and-overrides", 379 create: proxycfg.TestConfigSnapshotIngressWithOverrides, 380 setup: nil, 381 }, 382 { 383 name: "ingress-with-tcp-chain-failover-through-remote-gateway", 384 create: proxycfg.TestConfigSnapshotIngressWithFailoverThroughRemoteGateway, 385 setup: nil, 386 }, 387 { 388 name: "ingress-with-tcp-chain-failover-through-local-gateway", 389 create: proxycfg.TestConfigSnapshotIngressWithFailoverThroughLocalGateway, 390 setup: nil, 391 }, 392 { 393 name: "ingress-splitter-with-resolver-redirect", 394 create: proxycfg.TestConfigSnapshotIngress_SplitterWithResolverRedirectMultiDC, 395 setup: nil, 396 }, 397 { 398 name: "terminating-gateway", 399 create: proxycfg.TestConfigSnapshotTerminatingGateway, 400 setup: nil, 401 }, 402 { 403 name: "terminating-gateway-no-services", 404 create: proxycfg.TestConfigSnapshotTerminatingGatewayNoServices, 405 setup: nil, 406 }, 407 { 408 name: "terminating-gateway-custom-and-tagged-addresses", 409 create: proxycfg.TestConfigSnapshotTerminatingGateway, 410 setup: func(snap *proxycfg.ConfigSnapshot) { 411 snap.Proxy.Config = map[string]interface{}{ 412 "envoy_gateway_no_default_bind": true, 413 "envoy_gateway_bind_tagged_addresses": true, 414 "envoy_gateway_bind_addresses": map[string]structs.ServiceAddress{ 415 // This bind address should not get a listener due to deduplication and it sorts to the end 416 "z-duplicate-of-tagged-wan-addr": { 417 Address: "198.18.0.1", 418 Port: 443, 419 }, 420 "foo": { 421 Address: "198.17.2.3", 422 Port: 8080, 423 }, 424 }, 425 } 426 }, 427 }, 428 { 429 name: "terminating-gateway-service-subsets", 430 create: proxycfg.TestConfigSnapshotTerminatingGateway, 431 setup: func(snap *proxycfg.ConfigSnapshot) { 432 snap.TerminatingGateway.ServiceResolvers = map[structs.ServiceName]*structs.ServiceResolverConfigEntry{ 433 structs.NewServiceName("web", nil): { 434 Kind: structs.ServiceResolver, 435 Name: "web", 436 Subsets: map[string]structs.ServiceResolverSubset{ 437 "v1": { 438 Filter: "Service.Meta.version == 1", 439 }, 440 "v2": { 441 Filter: "Service.Meta.version == 2", 442 OnlyPassing: true, 443 }, 444 }, 445 }, 446 } 447 snap.TerminatingGateway.ServiceConfigs[structs.NewServiceName("web", nil)] = &structs.ServiceConfigResponse{ 448 ProxyConfig: map[string]interface{}{"protocol": "http"}, 449 } 450 }, 451 }, 452 { 453 name: "ingress-http-multiple-services", 454 create: proxycfg.TestConfigSnapshotIngress_HTTPMultipleServices, 455 setup: func(snap *proxycfg.ConfigSnapshot) { 456 snap.IngressGateway.Upstreams = map[proxycfg.IngressListenerKey]structs.Upstreams{ 457 {Protocol: "http", Port: 8080}: { 458 { 459 DestinationName: "foo", 460 LocalBindPort: 8080, 461 }, 462 { 463 DestinationName: "bar", 464 LocalBindPort: 8080, 465 }, 466 }, 467 {Protocol: "http", Port: 443}: { 468 { 469 DestinationName: "baz", 470 LocalBindPort: 443, 471 }, 472 { 473 DestinationName: "qux", 474 LocalBindPort: 443, 475 }, 476 }, 477 } 478 }, 479 }, 480 { 481 name: "terminating-gateway-no-api-cert", 482 create: proxycfg.TestConfigSnapshotTerminatingGateway, 483 setup: func(snap *proxycfg.ConfigSnapshot) { 484 snap.TerminatingGateway.ServiceLeaves[structs.NewServiceName("api", nil)] = nil 485 }, 486 }, 487 { 488 name: "ingress-with-tls-listener", 489 create: proxycfg.TestConfigSnapshotIngressWithTLSListener, 490 setup: nil, 491 }, 492 { 493 name: "transparent-proxy", 494 create: proxycfg.TestConfigSnapshot, 495 setup: func(snap *proxycfg.ConfigSnapshot) { 496 snap.Proxy.Mode = structs.ProxyModeTransparent 497 498 snap.ConnectProxy.MeshConfigSet = true 499 500 // DiscoveryChain without an UpstreamConfig should yield a filter chain when in transparent proxy mode 501 snap.ConnectProxy.DiscoveryChain["google"] = discoverychain.TestCompileConfigEntries( 502 t, "google", "default", "dc1", 503 connect.TestClusterID+".consul", "dc1", nil) 504 snap.ConnectProxy.WatchedUpstreamEndpoints["google"] = map[string]structs.CheckServiceNodes{ 505 "google.default.dc1": { 506 structs.CheckServiceNode{ 507 Node: &structs.Node{ 508 Address: "8.8.8.8", 509 Datacenter: "dc1", 510 }, 511 Service: &structs.NodeService{ 512 Service: "google", 513 Address: "9.9.9.9", 514 Port: 9090, 515 TaggedAddresses: map[string]structs.ServiceAddress{ 516 "virtual": {Address: "10.0.0.1"}, 517 }, 518 }, 519 }, 520 }, 521 // Other targets of the discovery chain should be ignored. 522 // We only match on the upstream's virtual IP, not the IPs of other targets. 523 "google-v2.default.dc1": { 524 structs.CheckServiceNode{ 525 Node: &structs.Node{ 526 Address: "7.7.7.7", 527 Datacenter: "dc1", 528 }, 529 Service: &structs.NodeService{ 530 Service: "google-v2", 531 TaggedAddresses: map[string]structs.ServiceAddress{ 532 "virtual": {Address: "10.10.10.10"}, 533 }, 534 }, 535 }, 536 }, 537 } 538 539 // DiscoveryChains without endpoints do not get a filter chain because there are no addresses to match on. 540 snap.ConnectProxy.DiscoveryChain["no-endpoints"] = discoverychain.TestCompileConfigEntries( 541 t, "no-endpoints", "default", "dc1", 542 connect.TestClusterID+".consul", "dc1", nil) 543 }, 544 }, 545 { 546 name: "transparent-proxy-catalog-destinations-only", 547 create: proxycfg.TestConfigSnapshot, 548 setup: func(snap *proxycfg.ConfigSnapshot) { 549 snap.Proxy.Mode = structs.ProxyModeTransparent 550 551 snap.ConnectProxy.MeshConfigSet = true 552 snap.ConnectProxy.MeshConfig = &structs.MeshConfigEntry{ 553 TransparentProxy: structs.TransparentProxyMeshConfig{ 554 MeshDestinationsOnly: true, 555 }, 556 } 557 558 // DiscoveryChain without an UpstreamConfig should yield a filter chain when in transparent proxy mode 559 snap.ConnectProxy.DiscoveryChain["google"] = discoverychain.TestCompileConfigEntries( 560 t, "google", "default", "dc1", 561 connect.TestClusterID+".consul", "dc1", nil) 562 snap.ConnectProxy.WatchedUpstreamEndpoints["google"] = map[string]structs.CheckServiceNodes{ 563 "google.default.dc1": { 564 structs.CheckServiceNode{ 565 Node: &structs.Node{ 566 Address: "8.8.8.8", 567 Datacenter: "dc1", 568 }, 569 Service: &structs.NodeService{ 570 Service: "google", 571 Address: "9.9.9.9", 572 Port: 9090, 573 TaggedAddresses: map[string]structs.ServiceAddress{ 574 "virtual": {Address: "10.0.0.1"}, 575 }, 576 }, 577 }, 578 }, 579 } 580 581 // DiscoveryChains without endpoints do not get a filter chain because there are no addresses to match on. 582 snap.ConnectProxy.DiscoveryChain["no-endpoints"] = discoverychain.TestCompileConfigEntries( 583 t, "no-endpoints", "default", "dc1", 584 connect.TestClusterID+".consul", "dc1", nil) 585 }, 586 }, 587 { 588 name: "transparent-proxy-dial-instances-directly", 589 create: proxycfg.TestConfigSnapshot, 590 setup: func(snap *proxycfg.ConfigSnapshot) { 591 snap.Proxy.Mode = structs.ProxyModeTransparent 592 593 snap.ConnectProxy.DiscoveryChain["mongo"] = discoverychain.TestCompileConfigEntries( 594 t, "mongo", "default", "dc1", 595 connect.TestClusterID+".consul", "dc1", nil) 596 597 snap.ConnectProxy.DiscoveryChain["kafka"] = discoverychain.TestCompileConfigEntries( 598 t, "kafka", "default", "dc1", 599 connect.TestClusterID+".consul", "dc1", nil) 600 601 kafka := structs.NewServiceName("kafka", structs.DefaultEnterpriseMeta()) 602 mongo := structs.NewServiceName("mongo", structs.DefaultEnterpriseMeta()) 603 604 // We add a filter chains for each passthrough service name. 605 // The filter chain will route to a cluster with the same SNI name. 606 snap.ConnectProxy.PassthroughUpstreams = map[string]proxycfg.ServicePassthroughAddrs{ 607 kafka.String(): { 608 SNI: "kafka.default.dc1.internal.e5b08d03-bfc3-c870-1833-baddb116e648.consul", 609 Addrs: map[string]struct{}{ 610 "9.9.9.9": {}, 611 }, 612 }, 613 mongo.String(): { 614 SNI: "mongo.default.dc1.internal.e5b08d03-bfc3-c870-1833-baddb116e648.consul", 615 Addrs: map[string]struct{}{ 616 "10.10.10.10": {}, 617 "10.10.10.12": {}, 618 }, 619 }, 620 } 621 622 // There should still be a filter chain for mongo's virtual address 623 snap.ConnectProxy.WatchedUpstreamEndpoints["mongo"] = map[string]structs.CheckServiceNodes{ 624 "mongo.default.dc1": { 625 structs.CheckServiceNode{ 626 Node: &structs.Node{ 627 Datacenter: "dc1", 628 }, 629 Service: &structs.NodeService{ 630 Service: "mongo", 631 Address: "7.7.7.7", 632 Port: 27017, 633 TaggedAddresses: map[string]structs.ServiceAddress{ 634 "virtual": {Address: "6.6.6.6"}, 635 }, 636 }, 637 }, 638 }, 639 } 640 }, 641 }, 642 } 643 644 latestEnvoyVersion := proxysupport.EnvoyVersions[0] 645 latestEnvoyVersion_v2 := proxysupport.EnvoyVersionsV2[0] 646 for _, envoyVersion := range proxysupport.EnvoyVersions { 647 sf, err := determineSupportedProxyFeaturesFromString(envoyVersion) 648 require.NoError(t, err) 649 t.Run("envoy-"+envoyVersion, func(t *testing.T) { 650 for _, tt := range tests { 651 t.Run(tt.name, func(t *testing.T) { 652 // Sanity check default with no overrides first 653 snap := tt.create(t) 654 655 // We need to replace the TLS certs with deterministic ones to make golden 656 // files workable. Note we don't update these otherwise they'd change 657 // golder files for every test case and so not be any use! 658 setupTLSRootsAndLeaf(t, snap) 659 660 if tt.setup != nil { 661 tt.setup(snap) 662 } 663 664 // Need server just for logger dependency 665 g := newResourceGenerator(testutil.Logger(t), nil, nil, false) 666 g.ProxyFeatures = sf 667 if tt.generatorSetup != nil { 668 tt.generatorSetup(g) 669 } 670 671 listeners, err := g.listenersFromSnapshot(snap) 672 require.NoError(t, err) 673 674 // The order of listeners returned via LDS isn't relevant, so it's safe 675 // to sort these for the purposes of test comparisons. 676 sort.Slice(listeners, func(i, j int) bool { 677 return listeners[i].(*envoy_listener_v3.Listener).Name < listeners[j].(*envoy_listener_v3.Listener).Name 678 }) 679 680 r, err := createResponse(ListenerType, "00000001", "00000001", listeners) 681 require.NoError(t, err) 682 683 t.Run("current", func(t *testing.T) { 684 gotJSON := protoToJSON(t, r) 685 686 gName := tt.name 687 if tt.overrideGoldenName != "" { 688 gName = tt.overrideGoldenName 689 } 690 691 require.JSONEq(t, goldenEnvoy(t, filepath.Join("listeners", gName), envoyVersion, latestEnvoyVersion, gotJSON), gotJSON) 692 }) 693 694 t.Run("v2-compat", func(t *testing.T) { 695 if !stringslice.Contains(proxysupport.EnvoyVersionsV2, envoyVersion) { 696 t.Skip() 697 } 698 respV2, err := convertDiscoveryResponseToV2(r) 699 require.NoError(t, err) 700 701 gotJSON := protoToJSON(t, respV2) 702 703 gName := tt.name 704 if tt.overrideGoldenName != "" { 705 gName = tt.overrideGoldenName 706 } 707 708 gName += ".v2compat" 709 710 require.JSONEq(t, goldenEnvoy(t, filepath.Join("listeners", gName), envoyVersion, latestEnvoyVersion_v2, gotJSON), gotJSON) 711 }) 712 }) 713 } 714 }) 715 } 716} 717 718type customListenerJSONOptions struct { 719 Name string 720 TLSContext string 721} 722 723const customListenerJSONTpl = `{ 724 "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", 725 "name": "{{ .Name }}", 726 "address": { 727 "socketAddress": { 728 "address": "11.11.11.11", 729 "portValue": 11111 730 } 731 }, 732 "filterChains": [ 733 { 734 {{ if .TLSContext -}} 735 "transport_socket": { 736 "name": "tls", 737 "typed_config": { 738 "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", 739 {{ .TLSContext }} 740 } 741 }, 742 {{- end }} 743 "filters": [ 744 { 745 "name": "envoy.filters.network.tcp_proxy", 746 "typedConfig": { 747 "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", 748 "cluster": "random-cluster", 749 "statPrefix": "foo-stats" 750 } 751 } 752 ] 753 } 754 ] 755}` 756 757type customHTTPListenerJSONOptions struct { 758 Name string 759 HTTPConnectionManagerName string 760} 761 762const customHTTPListenerJSONTpl = `{ 763 "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", 764 "name": "{{ .Name }}", 765 "address": { 766 "socketAddress": { 767 "address": "11.11.11.11", 768 "portValue": 11111 769 } 770 }, 771 "filterChains": [ 772 { 773 "filters": [ 774 { 775 "name": "{{ .HTTPConnectionManagerName }}", 776 "typedConfig": { 777 "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", 778 "http_filters": [ 779 { 780 "name": "envoy.filters.http.router" 781 } 782 ], 783 "route_config": { 784 "name": "public_listener", 785 "virtual_hosts": [ 786 { 787 "domains": [ 788 "*" 789 ], 790 "name": "public_listener", 791 "routes": [ 792 { 793 "match": { 794 "prefix": "/" 795 }, 796 "route": { 797 "cluster": "random-cluster" 798 } 799 } 800 ] 801 } 802 ] 803 } 804 } 805 } 806 ] 807 } 808 ] 809}` 810 811var ( 812 customListenerJSONTemplate = template.Must(template.New("").Parse(customListenerJSONTpl)) 813 customHTTPListenerJSONTemplate = template.Must(template.New("").Parse(customHTTPListenerJSONTpl)) 814) 815 816func customListenerJSON(t *testing.T, opts customListenerJSONOptions) string { 817 t.Helper() 818 var buf bytes.Buffer 819 require.NoError(t, customListenerJSONTemplate.Execute(&buf, opts)) 820 return buf.String() 821} 822 823func customHTTPListenerJSON(t *testing.T, opts customHTTPListenerJSONOptions) string { 824 t.Helper() 825 if opts.HTTPConnectionManagerName == "" { 826 opts.HTTPConnectionManagerName = httpConnectionManagerNewName 827 } 828 var buf bytes.Buffer 829 require.NoError(t, customHTTPListenerJSONTemplate.Execute(&buf, opts)) 830 return buf.String() 831} 832 833type httpCheckFetcherFunc func(serviceID structs.ServiceID) []structs.CheckType 834 835var _ HTTPCheckFetcher = (httpCheckFetcherFunc)(nil) 836 837func (f httpCheckFetcherFunc) ServiceHTTPBasedChecks(serviceID structs.ServiceID) []structs.CheckType { 838 return f(serviceID) 839} 840 841type configFetcherFunc func() string 842 843var _ ConfigFetcher = (configFetcherFunc)(nil) 844 845func (f configFetcherFunc) AdvertiseAddrLAN() string { 846 return f() 847} 848