1package discoverychain 2 3import ( 4 "testing" 5 "time" 6 7 "github.com/hashicorp/consul/agent/connect" 8 "github.com/hashicorp/consul/agent/structs" 9 "github.com/stretchr/testify/require" 10) 11 12type compileTestCase struct { 13 entries *structs.DiscoveryChainConfigEntries 14 setup func(req *CompileRequest) 15 expect *structs.CompiledDiscoveryChain 16 // expectIsDefault tests behavior of CompiledDiscoveryChain.IsDefault() 17 expectIsDefault bool 18 expectCustom bool 19 expectErr string 20 expectGraphErr bool 21} 22 23func TestCompile(t *testing.T) { 24 t.Parallel() 25 26 cases := map[string]compileTestCase{ 27 "router with defaults": testcase_JustRouterWithDefaults(), 28 "router with defaults and resolver": testcase_RouterWithDefaults_NoSplit_WithResolver(), 29 "router with defaults and noop split": testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver(), 30 "router with defaults and noop split and resolver": testcase_RouterWithDefaults_WithNoopSplit_WithResolver(), 31 "router with no destination": testcase_JustRouterWithNoDestination(), 32 "route bypasses splitter": testcase_RouteBypassesSplit(), 33 "noop split": testcase_NoopSplit_DefaultResolver(), 34 "noop split with protocol from proxy defaults": testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults(), 35 "noop split with resolver": testcase_NoopSplit_WithResolver(), 36 "subset split": testcase_SubsetSplit(), 37 "service split": testcase_ServiceSplit(), 38 "split bypasses next splitter": testcase_SplitBypassesSplit(), 39 "service redirect": testcase_ServiceRedirect(), 40 "service and subset redirect": testcase_ServiceAndSubsetRedirect(), 41 "datacenter redirect": testcase_DatacenterRedirect(), 42 "datacenter redirect with mesh gateways": testcase_DatacenterRedirect_WithMeshGateways(), 43 "service failover": testcase_ServiceFailover(), 44 "service failover through redirect": testcase_ServiceFailoverThroughRedirect(), 45 "circular resolver failover": testcase_Resolver_CircularFailover(), 46 "service and subset failover": testcase_ServiceAndSubsetFailover(), 47 "datacenter failover": testcase_DatacenterFailover(), 48 "datacenter failover with mesh gateways": testcase_DatacenterFailover_WithMeshGateways(), 49 "noop split to resolver with default subset": testcase_NoopSplit_WithDefaultSubset(), 50 "resolver with default subset": testcase_Resolve_WithDefaultSubset(), 51 "default resolver with external sni": testcase_DefaultResolver_ExternalSNI(), 52 "resolver with no entries and inferring defaults": testcase_DefaultResolver(), 53 "default resolver with proxy defaults": testcase_DefaultResolver_WithProxyDefaults(), 54 "loadbalancer splitter and resolver": testcase_LBSplitterAndResolver(), 55 "loadbalancer resolver": testcase_LBResolver(), 56 "service redirect to service with default resolver is not a default chain": testcase_RedirectToDefaultResolverIsNotDefaultChain(), 57 58 "all the bells and whistles": testcase_AllBellsAndWhistles(), 59 "multi dc canary": testcase_MultiDatacenterCanary(), 60 61 // various errors 62 "splitter requires valid protocol": testcase_SplitterRequiresValidProtocol(), 63 "router requires valid protocol": testcase_RouterRequiresValidProtocol(), 64 "split to unsplittable protocol": testcase_SplitToUnsplittableProtocol(), 65 "route to unroutable protocol": testcase_RouteToUnroutableProtocol(), 66 "failover crosses protocols": testcase_FailoverCrossesProtocols(), 67 "redirect crosses protocols": testcase_RedirectCrossesProtocols(), 68 "redirect to missing subset": testcase_RedirectToMissingSubset(), 69 "resolver with failover and external sni": testcase_Resolver_ExternalSNI_FailoverNotAllowed(), 70 "resolver with subsets and external sni": testcase_Resolver_ExternalSNI_SubsetsNotAllowed(), 71 "resolver with redirect and external sni": testcase_Resolver_ExternalSNI_RedirectNotAllowed(), 72 73 // overrides 74 "resolver with protocol from override": testcase_ResolverProtocolOverride(), 75 "resolver with protocol from override ignored": testcase_ResolverProtocolOverrideIgnored(), 76 "router ignored due to protocol override": testcase_RouterIgnored_ResolverProtocolOverride(), 77 78 // circular references 79 "circular resolver redirect": testcase_Resolver_CircularRedirect(), 80 "circular split": testcase_CircularSplit(), 81 } 82 83 for name, tc := range cases { 84 tc := tc 85 t.Run(name, func(t *testing.T) { 86 t.Parallel() 87 88 // sanity check entries are normalized and valid 89 for _, entry := range tc.entries.Routers { 90 require.NoError(t, entry.Normalize()) 91 require.NoError(t, entry.Validate()) 92 } 93 for _, entry := range tc.entries.Splitters { 94 require.NoError(t, entry.Normalize()) 95 require.NoError(t, entry.Validate()) 96 } 97 for _, entry := range tc.entries.Resolvers { 98 require.NoError(t, entry.Normalize()) 99 require.NoError(t, entry.Validate()) 100 } 101 102 req := CompileRequest{ 103 ServiceName: "main", 104 EvaluateInNamespace: "default", 105 EvaluateInDatacenter: "dc1", 106 EvaluateInTrustDomain: "trustdomain.consul", 107 UseInDatacenter: "dc1", 108 Entries: tc.entries, 109 } 110 if tc.setup != nil { 111 tc.setup(&req) 112 } 113 114 res, err := Compile(req) 115 if tc.expectErr != "" { 116 require.Error(t, err) 117 require.Contains(t, err.Error(), tc.expectErr) 118 _, ok := err.(*structs.ConfigEntryGraphError) 119 if tc.expectGraphErr { 120 require.True(t, ok, "%T is not a *ConfigEntryGraphError", err) 121 } else { 122 require.False(t, ok, "did not expect a *ConfigEntryGraphError here: %v", err) 123 } 124 } else { 125 require.NoError(t, err) 126 127 // Avoid requiring unnecessary test boilerplate and inject these 128 // ourselves. 129 tc.expect.ServiceName = "main" 130 tc.expect.Namespace = "default" 131 tc.expect.Datacenter = "dc1" 132 133 if tc.expectCustom { 134 require.NotEmpty(t, res.CustomizationHash) 135 res.CustomizationHash = "" 136 } else { 137 require.Empty(t, res.CustomizationHash) 138 } 139 140 require.Equal(t, tc.expect, res) 141 require.Equal(t, tc.expectIsDefault, res.IsDefault()) 142 } 143 }) 144 } 145} 146 147func testcase_JustRouterWithDefaults() compileTestCase { 148 entries := newEntries() 149 setServiceProtocol(entries, "main", "http") 150 151 entries.AddRouters( 152 &structs.ServiceRouterConfigEntry{ 153 Kind: "service-router", 154 Name: "main", 155 }, 156 ) 157 158 expect := &structs.CompiledDiscoveryChain{ 159 Protocol: "http", 160 StartNode: "router:main.default", 161 Nodes: map[string]*structs.DiscoveryGraphNode{ 162 "router:main.default": { 163 Type: structs.DiscoveryGraphNodeTypeRouter, 164 Name: "main.default", 165 Routes: []*structs.DiscoveryRoute{ 166 { 167 Definition: newDefaultServiceRoute("main", "default"), 168 NextNode: "resolver:main.default.dc1", 169 }, 170 }, 171 }, 172 "resolver:main.default.dc1": { 173 Type: structs.DiscoveryGraphNodeTypeResolver, 174 Name: "main.default.dc1", 175 Resolver: &structs.DiscoveryResolver{ 176 Default: true, 177 ConnectTimeout: 5 * time.Second, 178 Target: "main.default.dc1", 179 }, 180 }, 181 }, 182 Targets: map[string]*structs.DiscoveryTarget{ 183 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 184 }, 185 } 186 187 return compileTestCase{entries: entries, expect: expect} 188} 189 190func testcase_JustRouterWithNoDestination() compileTestCase { 191 entries := newEntries() 192 setServiceProtocol(entries, "main", "http") 193 194 entries.AddRouters( 195 &structs.ServiceRouterConfigEntry{ 196 Kind: "service-router", 197 Name: "main", 198 Routes: []structs.ServiceRoute{ 199 { 200 Match: &structs.ServiceRouteMatch{ 201 HTTP: &structs.ServiceRouteHTTPMatch{ 202 PathPrefix: "/", 203 }, 204 }, 205 }, 206 }, 207 }, 208 ) 209 210 expect := &structs.CompiledDiscoveryChain{ 211 Protocol: "http", 212 StartNode: "router:main.default", 213 Nodes: map[string]*structs.DiscoveryGraphNode{ 214 "router:main.default": { 215 Type: structs.DiscoveryGraphNodeTypeRouter, 216 Name: "main.default", 217 Routes: []*structs.DiscoveryRoute{ 218 { 219 Definition: &structs.ServiceRoute{ 220 Match: &structs.ServiceRouteMatch{ 221 HTTP: &structs.ServiceRouteHTTPMatch{ 222 PathPrefix: "/", 223 }, 224 }, 225 }, 226 NextNode: "resolver:main.default.dc1", 227 }, 228 { 229 Definition: newDefaultServiceRoute("main", "default"), 230 NextNode: "resolver:main.default.dc1", 231 }, 232 }, 233 }, 234 "resolver:main.default.dc1": { 235 Type: structs.DiscoveryGraphNodeTypeResolver, 236 Name: "main.default.dc1", 237 Resolver: &structs.DiscoveryResolver{ 238 Default: true, 239 ConnectTimeout: 5 * time.Second, 240 Target: "main.default.dc1", 241 }, 242 }, 243 }, 244 Targets: map[string]*structs.DiscoveryTarget{ 245 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 246 }, 247 } 248 249 return compileTestCase{entries: entries, expect: expect} 250} 251 252func testcase_RouterWithDefaults_NoSplit_WithResolver() compileTestCase { 253 entries := newEntries() 254 setServiceProtocol(entries, "main", "http") 255 256 entries.AddRouters( 257 &structs.ServiceRouterConfigEntry{ 258 Kind: "service-router", 259 Name: "main", 260 }, 261 ) 262 entries.AddResolvers( 263 &structs.ServiceResolverConfigEntry{ 264 Kind: "service-resolver", 265 Name: "main", 266 ConnectTimeout: 33 * time.Second, 267 }, 268 ) 269 270 expect := &structs.CompiledDiscoveryChain{ 271 Protocol: "http", 272 StartNode: "router:main.default", 273 Nodes: map[string]*structs.DiscoveryGraphNode{ 274 "router:main.default": { 275 Type: structs.DiscoveryGraphNodeTypeRouter, 276 Name: "main.default", 277 Routes: []*structs.DiscoveryRoute{ 278 { 279 Definition: newDefaultServiceRoute("main", "default"), 280 NextNode: "resolver:main.default.dc1", 281 }, 282 }, 283 }, 284 "resolver:main.default.dc1": { 285 Type: structs.DiscoveryGraphNodeTypeResolver, 286 Name: "main.default.dc1", 287 Resolver: &structs.DiscoveryResolver{ 288 ConnectTimeout: 33 * time.Second, 289 Target: "main.default.dc1", 290 }, 291 }, 292 }, 293 Targets: map[string]*structs.DiscoveryTarget{ 294 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 295 }, 296 } 297 298 return compileTestCase{entries: entries, expect: expect} 299} 300 301func testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver() compileTestCase { 302 entries := newEntries() 303 setServiceProtocol(entries, "main", "http") 304 305 entries.AddRouters( 306 &structs.ServiceRouterConfigEntry{ 307 Kind: "service-router", 308 Name: "main", 309 }, 310 ) 311 entries.AddSplitters( 312 &structs.ServiceSplitterConfigEntry{ 313 Kind: "service-splitter", 314 Name: "main", 315 Splits: []structs.ServiceSplit{ 316 {Weight: 100}, 317 }, 318 }, 319 ) 320 321 expect := &structs.CompiledDiscoveryChain{ 322 Protocol: "http", 323 StartNode: "router:main.default", 324 Nodes: map[string]*structs.DiscoveryGraphNode{ 325 "router:main.default": { 326 Type: structs.DiscoveryGraphNodeTypeRouter, 327 Name: "main.default", 328 Routes: []*structs.DiscoveryRoute{ 329 { 330 Definition: newDefaultServiceRoute("main", "default"), 331 NextNode: "splitter:main.default", 332 }, 333 }, 334 }, 335 "splitter:main.default": { 336 Type: structs.DiscoveryGraphNodeTypeSplitter, 337 Name: "main.default", 338 Splits: []*structs.DiscoverySplit{ 339 { 340 Weight: 100, 341 NextNode: "resolver:main.default.dc1", 342 }, 343 }, 344 }, 345 "resolver:main.default.dc1": { 346 Type: structs.DiscoveryGraphNodeTypeResolver, 347 Name: "main.default.dc1", 348 Resolver: &structs.DiscoveryResolver{ 349 Default: true, 350 ConnectTimeout: 5 * time.Second, 351 Target: "main.default.dc1", 352 }, 353 }, 354 }, 355 Targets: map[string]*structs.DiscoveryTarget{ 356 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 357 }, 358 } 359 360 return compileTestCase{entries: entries, expect: expect} 361} 362 363func testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults() compileTestCase { 364 entries := newEntries() 365 setGlobalProxyProtocol(entries, "http") 366 367 entries.AddRouters( 368 &structs.ServiceRouterConfigEntry{ 369 Kind: "service-router", 370 Name: "main", 371 }, 372 ) 373 entries.AddSplitters( 374 &structs.ServiceSplitterConfigEntry{ 375 Kind: "service-splitter", 376 Name: "main", 377 Splits: []structs.ServiceSplit{ 378 {Weight: 100}, 379 }, 380 }, 381 ) 382 383 expect := &structs.CompiledDiscoveryChain{ 384 Protocol: "http", 385 StartNode: "router:main.default", 386 Nodes: map[string]*structs.DiscoveryGraphNode{ 387 "router:main.default": { 388 Type: structs.DiscoveryGraphNodeTypeRouter, 389 Name: "main.default", 390 Routes: []*structs.DiscoveryRoute{ 391 { 392 Definition: newDefaultServiceRoute("main", "default"), 393 NextNode: "splitter:main.default", 394 }, 395 }, 396 }, 397 "splitter:main.default": { 398 Type: structs.DiscoveryGraphNodeTypeSplitter, 399 Name: "main.default", 400 Splits: []*structs.DiscoverySplit{ 401 { 402 Weight: 100, 403 NextNode: "resolver:main.default.dc1", 404 }, 405 }, 406 }, 407 "resolver:main.default.dc1": { 408 Type: structs.DiscoveryGraphNodeTypeResolver, 409 Name: "main.default.dc1", 410 Resolver: &structs.DiscoveryResolver{ 411 Default: true, 412 ConnectTimeout: 5 * time.Second, 413 Target: "main.default.dc1", 414 }, 415 }, 416 }, 417 Targets: map[string]*structs.DiscoveryTarget{ 418 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 419 }, 420 } 421 422 return compileTestCase{entries: entries, expect: expect} 423} 424 425func testcase_RouterWithDefaults_WithNoopSplit_WithResolver() compileTestCase { 426 entries := newEntries() 427 setServiceProtocol(entries, "main", "http") 428 429 entries.AddRouters( 430 &structs.ServiceRouterConfigEntry{ 431 Kind: "service-router", 432 Name: "main", 433 }, 434 ) 435 entries.AddSplitters( 436 &structs.ServiceSplitterConfigEntry{ 437 Kind: "service-splitter", 438 Name: "main", 439 Splits: []structs.ServiceSplit{ 440 {Weight: 100}, 441 }, 442 }, 443 ) 444 entries.AddResolvers( 445 &structs.ServiceResolverConfigEntry{ 446 Kind: "service-resolver", 447 Name: "main", 448 ConnectTimeout: 33 * time.Second, 449 }, 450 ) 451 452 expect := &structs.CompiledDiscoveryChain{ 453 Protocol: "http", 454 StartNode: "router:main.default", 455 Nodes: map[string]*structs.DiscoveryGraphNode{ 456 "router:main.default": { 457 Type: structs.DiscoveryGraphNodeTypeRouter, 458 Name: "main.default", 459 Routes: []*structs.DiscoveryRoute{ 460 { 461 Definition: newDefaultServiceRoute("main", "default"), 462 NextNode: "splitter:main.default", 463 }, 464 }, 465 }, 466 "splitter:main.default": { 467 Type: structs.DiscoveryGraphNodeTypeSplitter, 468 Name: "main.default", 469 Splits: []*structs.DiscoverySplit{ 470 { 471 Weight: 100, 472 NextNode: "resolver:main.default.dc1", 473 }, 474 }, 475 }, 476 "resolver:main.default.dc1": { 477 Type: structs.DiscoveryGraphNodeTypeResolver, 478 Name: "main.default.dc1", 479 Resolver: &structs.DiscoveryResolver{ 480 ConnectTimeout: 33 * time.Second, 481 Target: "main.default.dc1", 482 }, 483 }, 484 }, 485 Targets: map[string]*structs.DiscoveryTarget{ 486 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 487 }, 488 } 489 490 return compileTestCase{entries: entries, expect: expect} 491} 492 493func testcase_RouteBypassesSplit() compileTestCase { 494 entries := newEntries() 495 setServiceProtocol(entries, "main", "http") 496 setServiceProtocol(entries, "other", "http") 497 498 entries.AddRouters( 499 &structs.ServiceRouterConfigEntry{ 500 Kind: "service-router", 501 Name: "main", 502 Routes: []structs.ServiceRoute{ 503 // route direct subset reference (bypass split) 504 newSimpleRoute("other", func(r *structs.ServiceRoute) { 505 r.Destination.ServiceSubset = "bypass" 506 }), 507 }, 508 }, 509 ) 510 entries.AddSplitters( 511 &structs.ServiceSplitterConfigEntry{ 512 Kind: "service-splitter", 513 Name: "other", 514 Splits: []structs.ServiceSplit{ 515 {Weight: 100, Service: "ignored"}, 516 }, 517 }, 518 ) 519 entries.AddResolvers( 520 &structs.ServiceResolverConfigEntry{ 521 Kind: "service-resolver", 522 Name: "other", 523 Subsets: map[string]structs.ServiceResolverSubset{ 524 "bypass": { 525 Filter: "Service.Meta.version == bypass", 526 }, 527 }, 528 }, 529 ) 530 531 router := entries.GetRouter(structs.NewServiceID("main", nil)) 532 533 expect := &structs.CompiledDiscoveryChain{ 534 Protocol: "http", 535 StartNode: "router:main.default", 536 Nodes: map[string]*structs.DiscoveryGraphNode{ 537 "router:main.default": { 538 Type: structs.DiscoveryGraphNodeTypeRouter, 539 Name: "main.default", 540 Routes: []*structs.DiscoveryRoute{ 541 { 542 Definition: &router.Routes[0], 543 NextNode: "resolver:bypass.other.default.dc1", 544 }, 545 { 546 Definition: newDefaultServiceRoute("main", "default"), 547 NextNode: "resolver:main.default.dc1", 548 }, 549 }, 550 }, 551 "resolver:main.default.dc1": { 552 Type: structs.DiscoveryGraphNodeTypeResolver, 553 Name: "main.default.dc1", 554 Resolver: &structs.DiscoveryResolver{ 555 Default: true, 556 ConnectTimeout: 5 * time.Second, 557 Target: "main.default.dc1", 558 }, 559 }, 560 "resolver:bypass.other.default.dc1": { 561 Type: structs.DiscoveryGraphNodeTypeResolver, 562 Name: "bypass.other.default.dc1", 563 Resolver: &structs.DiscoveryResolver{ 564 ConnectTimeout: 5 * time.Second, 565 Target: "bypass.other.default.dc1", 566 }, 567 }, 568 }, 569 Targets: map[string]*structs.DiscoveryTarget{ 570 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 571 "bypass.other.default.dc1": newTarget("other", "bypass", "default", "dc1", func(t *structs.DiscoveryTarget) { 572 t.Subset = structs.ServiceResolverSubset{ 573 Filter: "Service.Meta.version == bypass", 574 } 575 }), 576 }, 577 } 578 579 return compileTestCase{entries: entries, expect: expect} 580} 581 582func testcase_NoopSplit_DefaultResolver() compileTestCase { 583 entries := newEntries() 584 setServiceProtocol(entries, "main", "http") 585 586 entries.AddSplitters( 587 &structs.ServiceSplitterConfigEntry{ 588 Kind: "service-splitter", 589 Name: "main", 590 Splits: []structs.ServiceSplit{ 591 {Weight: 100}, 592 }, 593 }, 594 ) 595 596 expect := &structs.CompiledDiscoveryChain{ 597 Protocol: "http", 598 StartNode: "splitter:main.default", 599 Nodes: map[string]*structs.DiscoveryGraphNode{ 600 "splitter:main.default": { 601 Type: structs.DiscoveryGraphNodeTypeSplitter, 602 Name: "main.default", 603 Splits: []*structs.DiscoverySplit{ 604 { 605 Weight: 100, 606 NextNode: "resolver:main.default.dc1", 607 }, 608 }, 609 }, 610 "resolver:main.default.dc1": { 611 Type: structs.DiscoveryGraphNodeTypeResolver, 612 Name: "main.default.dc1", 613 Resolver: &structs.DiscoveryResolver{ 614 Default: true, 615 ConnectTimeout: 5 * time.Second, 616 Target: "main.default.dc1", 617 }, 618 }, 619 }, 620 Targets: map[string]*structs.DiscoveryTarget{ 621 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 622 }, 623 } 624 625 return compileTestCase{entries: entries, expect: expect} 626} 627 628func testcase_NoopSplit_WithResolver() compileTestCase { 629 entries := newEntries() 630 setServiceProtocol(entries, "main", "http") 631 632 entries.AddSplitters( 633 &structs.ServiceSplitterConfigEntry{ 634 Kind: "service-splitter", 635 Name: "main", 636 Splits: []structs.ServiceSplit{ 637 {Weight: 100}, 638 }, 639 }, 640 ) 641 entries.AddResolvers( 642 &structs.ServiceResolverConfigEntry{ 643 Kind: "service-resolver", 644 Name: "main", 645 ConnectTimeout: 33 * time.Second, 646 }, 647 ) 648 649 expect := &structs.CompiledDiscoveryChain{ 650 Protocol: "http", 651 StartNode: "splitter:main.default", 652 Nodes: map[string]*structs.DiscoveryGraphNode{ 653 "splitter:main.default": { 654 Type: structs.DiscoveryGraphNodeTypeSplitter, 655 Name: "main.default", 656 Splits: []*structs.DiscoverySplit{ 657 { 658 Weight: 100, 659 NextNode: "resolver:main.default.dc1", 660 }, 661 }, 662 }, 663 "resolver:main.default.dc1": { 664 Type: structs.DiscoveryGraphNodeTypeResolver, 665 Name: "main.default.dc1", 666 Resolver: &structs.DiscoveryResolver{ 667 ConnectTimeout: 33 * time.Second, 668 Target: "main.default.dc1", 669 }, 670 }, 671 }, 672 Targets: map[string]*structs.DiscoveryTarget{ 673 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 674 }, 675 } 676 677 return compileTestCase{entries: entries, expect: expect} 678} 679 680func testcase_SubsetSplit() compileTestCase { 681 entries := newEntries() 682 setServiceProtocol(entries, "main", "http") 683 684 entries.AddSplitters( 685 &structs.ServiceSplitterConfigEntry{ 686 Kind: "service-splitter", 687 Name: "main", 688 Splits: []structs.ServiceSplit{ 689 {Weight: 60, ServiceSubset: "v2"}, 690 {Weight: 40, ServiceSubset: "v1"}, 691 }, 692 }, 693 ) 694 entries.AddResolvers( 695 &structs.ServiceResolverConfigEntry{ 696 Kind: "service-resolver", 697 Name: "main", 698 Subsets: map[string]structs.ServiceResolverSubset{ 699 "v1": { 700 Filter: "Service.Meta.version == 1", 701 }, 702 "v2": { 703 Filter: "Service.Meta.version == 2", 704 }, 705 }, 706 }, 707 ) 708 709 expect := &structs.CompiledDiscoveryChain{ 710 Protocol: "http", 711 StartNode: "splitter:main.default", 712 Nodes: map[string]*structs.DiscoveryGraphNode{ 713 "splitter:main.default": { 714 Type: structs.DiscoveryGraphNodeTypeSplitter, 715 Name: "main.default", 716 Splits: []*structs.DiscoverySplit{ 717 { 718 Weight: 60, 719 NextNode: "resolver:v2.main.default.dc1", 720 }, 721 { 722 Weight: 40, 723 NextNode: "resolver:v1.main.default.dc1", 724 }, 725 }, 726 }, 727 "resolver:v2.main.default.dc1": { 728 Type: structs.DiscoveryGraphNodeTypeResolver, 729 Name: "v2.main.default.dc1", 730 Resolver: &structs.DiscoveryResolver{ 731 ConnectTimeout: 5 * time.Second, 732 Target: "v2.main.default.dc1", 733 }, 734 }, 735 "resolver:v1.main.default.dc1": { 736 Type: structs.DiscoveryGraphNodeTypeResolver, 737 Name: "v1.main.default.dc1", 738 Resolver: &structs.DiscoveryResolver{ 739 ConnectTimeout: 5 * time.Second, 740 Target: "v1.main.default.dc1", 741 }, 742 }, 743 }, 744 Targets: map[string]*structs.DiscoveryTarget{ 745 "v2.main.default.dc1": newTarget("main", "v2", "default", "dc1", func(t *structs.DiscoveryTarget) { 746 t.Subset = structs.ServiceResolverSubset{ 747 Filter: "Service.Meta.version == 2", 748 } 749 }), 750 "v1.main.default.dc1": newTarget("main", "v1", "default", "dc1", func(t *structs.DiscoveryTarget) { 751 t.Subset = structs.ServiceResolverSubset{ 752 Filter: "Service.Meta.version == 1", 753 } 754 }), 755 }, 756 } 757 758 return compileTestCase{entries: entries, expect: expect} 759} 760 761func testcase_ServiceSplit() compileTestCase { 762 entries := newEntries() 763 setServiceProtocol(entries, "main", "http") 764 setServiceProtocol(entries, "foo", "http") 765 setServiceProtocol(entries, "bar", "http") 766 767 entries.AddSplitters( 768 &structs.ServiceSplitterConfigEntry{ 769 Kind: "service-splitter", 770 Name: "main", 771 Splits: []structs.ServiceSplit{ 772 {Weight: 60, Service: "foo"}, 773 {Weight: 40, Service: "bar"}, 774 }, 775 }, 776 ) 777 778 expect := &structs.CompiledDiscoveryChain{ 779 Protocol: "http", 780 StartNode: "splitter:main.default", 781 Nodes: map[string]*structs.DiscoveryGraphNode{ 782 "splitter:main.default": { 783 Type: structs.DiscoveryGraphNodeTypeSplitter, 784 Name: "main.default", 785 Splits: []*structs.DiscoverySplit{ 786 { 787 Weight: 60, 788 NextNode: "resolver:foo.default.dc1", 789 }, 790 { 791 Weight: 40, 792 NextNode: "resolver:bar.default.dc1", 793 }, 794 }, 795 }, 796 "resolver:foo.default.dc1": { 797 Type: structs.DiscoveryGraphNodeTypeResolver, 798 Name: "foo.default.dc1", 799 Resolver: &structs.DiscoveryResolver{ 800 Default: true, 801 ConnectTimeout: 5 * time.Second, 802 Target: "foo.default.dc1", 803 }, 804 }, 805 "resolver:bar.default.dc1": { 806 Type: structs.DiscoveryGraphNodeTypeResolver, 807 Name: "bar.default.dc1", 808 Resolver: &structs.DiscoveryResolver{ 809 Default: true, 810 ConnectTimeout: 5 * time.Second, 811 Target: "bar.default.dc1", 812 }, 813 }, 814 }, 815 Targets: map[string]*structs.DiscoveryTarget{ 816 "foo.default.dc1": newTarget("foo", "", "default", "dc1", nil), 817 "bar.default.dc1": newTarget("bar", "", "default", "dc1", nil), 818 }, 819 } 820 821 return compileTestCase{entries: entries, expect: expect} 822} 823 824func testcase_SplitBypassesSplit() compileTestCase { 825 entries := newEntries() 826 setServiceProtocol(entries, "main", "http") 827 setServiceProtocol(entries, "next", "http") 828 829 entries.AddSplitters( 830 &structs.ServiceSplitterConfigEntry{ 831 Kind: "service-splitter", 832 Name: "main", 833 Splits: []structs.ServiceSplit{ 834 { 835 Weight: 100, 836 Service: "next", 837 ServiceSubset: "bypassed", 838 }, 839 }, 840 }, 841 &structs.ServiceSplitterConfigEntry{ 842 Kind: "service-splitter", 843 Name: "next", 844 Splits: []structs.ServiceSplit{ 845 { 846 Weight: 100, 847 ServiceSubset: "not-bypassed", 848 }, 849 }, 850 }, 851 ) 852 entries.AddResolvers( 853 &structs.ServiceResolverConfigEntry{ 854 Kind: "service-resolver", 855 Name: "next", 856 Subsets: map[string]structs.ServiceResolverSubset{ 857 "bypassed": { 858 Filter: "Service.Meta.version == bypass", 859 }, 860 "not-bypassed": { 861 Filter: "Service.Meta.version != bypass", 862 }, 863 }, 864 }, 865 ) 866 867 expect := &structs.CompiledDiscoveryChain{ 868 Protocol: "http", 869 StartNode: "splitter:main.default", 870 Nodes: map[string]*structs.DiscoveryGraphNode{ 871 "splitter:main.default": { 872 Type: structs.DiscoveryGraphNodeTypeSplitter, 873 Name: "main.default", 874 Splits: []*structs.DiscoverySplit{ 875 { 876 Weight: 100, 877 NextNode: "resolver:bypassed.next.default.dc1", 878 }, 879 }, 880 }, 881 "resolver:bypassed.next.default.dc1": { 882 Type: structs.DiscoveryGraphNodeTypeResolver, 883 Name: "bypassed.next.default.dc1", 884 Resolver: &structs.DiscoveryResolver{ 885 ConnectTimeout: 5 * time.Second, 886 Target: "bypassed.next.default.dc1", 887 }, 888 }, 889 }, 890 Targets: map[string]*structs.DiscoveryTarget{ 891 "bypassed.next.default.dc1": newTarget("next", "bypassed", "default", "dc1", func(t *structs.DiscoveryTarget) { 892 t.Subset = structs.ServiceResolverSubset{ 893 Filter: "Service.Meta.version == bypass", 894 } 895 }), 896 }, 897 } 898 899 return compileTestCase{entries: entries, expect: expect} 900} 901 902func testcase_ServiceRedirect() compileTestCase { 903 entries := newEntries() 904 entries.AddResolvers( 905 &structs.ServiceResolverConfigEntry{ 906 Kind: "service-resolver", 907 Name: "main", 908 Redirect: &structs.ServiceResolverRedirect{ 909 Service: "other", 910 }, 911 }, 912 ) 913 914 expect := &structs.CompiledDiscoveryChain{ 915 Protocol: "tcp", 916 StartNode: "resolver:other.default.dc1", 917 Nodes: map[string]*structs.DiscoveryGraphNode{ 918 "resolver:other.default.dc1": { 919 Type: structs.DiscoveryGraphNodeTypeResolver, 920 Name: "other.default.dc1", 921 Resolver: &structs.DiscoveryResolver{ 922 Default: true, 923 ConnectTimeout: 5 * time.Second, 924 Target: "other.default.dc1", 925 }, 926 }, 927 }, 928 Targets: map[string]*structs.DiscoveryTarget{ 929 "other.default.dc1": newTarget("other", "", "default", "dc1", nil), 930 }, 931 } 932 933 return compileTestCase{entries: entries, expect: expect} 934} 935 936func testcase_ServiceAndSubsetRedirect() compileTestCase { 937 entries := newEntries() 938 entries.AddResolvers( 939 &structs.ServiceResolverConfigEntry{ 940 Kind: "service-resolver", 941 Name: "main", 942 Redirect: &structs.ServiceResolverRedirect{ 943 Service: "other", 944 ServiceSubset: "v2", 945 }, 946 }, 947 &structs.ServiceResolverConfigEntry{ 948 Kind: "service-resolver", 949 Name: "other", 950 Subsets: map[string]structs.ServiceResolverSubset{ 951 "v1": { 952 Filter: "Service.Meta.version == 1", 953 }, 954 "v2": { 955 Filter: "Service.Meta.version == 2", 956 }, 957 }, 958 }, 959 ) 960 961 expect := &structs.CompiledDiscoveryChain{ 962 Protocol: "tcp", 963 StartNode: "resolver:v2.other.default.dc1", 964 Nodes: map[string]*structs.DiscoveryGraphNode{ 965 "resolver:v2.other.default.dc1": { 966 Type: structs.DiscoveryGraphNodeTypeResolver, 967 Name: "v2.other.default.dc1", 968 Resolver: &structs.DiscoveryResolver{ 969 ConnectTimeout: 5 * time.Second, 970 Target: "v2.other.default.dc1", 971 }, 972 }, 973 }, 974 Targets: map[string]*structs.DiscoveryTarget{ 975 "v2.other.default.dc1": newTarget("other", "v2", "default", "dc1", func(t *structs.DiscoveryTarget) { 976 t.Subset = structs.ServiceResolverSubset{ 977 Filter: "Service.Meta.version == 2", 978 } 979 }), 980 }, 981 } 982 return compileTestCase{entries: entries, expect: expect} 983} 984 985func testcase_DatacenterRedirect() compileTestCase { 986 entries := newEntries() 987 entries.AddResolvers( 988 &structs.ServiceResolverConfigEntry{ 989 Kind: "service-resolver", 990 Name: "main", 991 Redirect: &structs.ServiceResolverRedirect{ 992 Datacenter: "dc9", 993 }, 994 }, 995 ) 996 997 expect := &structs.CompiledDiscoveryChain{ 998 Protocol: "tcp", 999 StartNode: "resolver:main.default.dc9", 1000 Nodes: map[string]*structs.DiscoveryGraphNode{ 1001 "resolver:main.default.dc9": { 1002 Type: structs.DiscoveryGraphNodeTypeResolver, 1003 Name: "main.default.dc9", 1004 Resolver: &structs.DiscoveryResolver{ 1005 ConnectTimeout: 5 * time.Second, 1006 Target: "main.default.dc9", 1007 }, 1008 }, 1009 }, 1010 Targets: map[string]*structs.DiscoveryTarget{ 1011 "main.default.dc9": newTarget("main", "", "default", "dc9", nil), 1012 }, 1013 } 1014 return compileTestCase{entries: entries, expect: expect} 1015} 1016 1017func testcase_DatacenterRedirect_WithMeshGateways() compileTestCase { 1018 entries := newEntries() 1019 entries.GlobalProxy = &structs.ProxyConfigEntry{ 1020 Kind: structs.ProxyDefaults, 1021 Name: structs.ProxyConfigGlobal, 1022 MeshGateway: structs.MeshGatewayConfig{ 1023 Mode: structs.MeshGatewayModeRemote, 1024 }, 1025 } 1026 entries.AddResolvers( 1027 &structs.ServiceResolverConfigEntry{ 1028 Kind: "service-resolver", 1029 Name: "main", 1030 Redirect: &structs.ServiceResolverRedirect{ 1031 Datacenter: "dc9", 1032 }, 1033 }, 1034 ) 1035 1036 expect := &structs.CompiledDiscoveryChain{ 1037 Protocol: "tcp", 1038 StartNode: "resolver:main.default.dc9", 1039 Nodes: map[string]*structs.DiscoveryGraphNode{ 1040 "resolver:main.default.dc9": { 1041 Type: structs.DiscoveryGraphNodeTypeResolver, 1042 Name: "main.default.dc9", 1043 Resolver: &structs.DiscoveryResolver{ 1044 ConnectTimeout: 5 * time.Second, 1045 Target: "main.default.dc9", 1046 }, 1047 }, 1048 }, 1049 Targets: map[string]*structs.DiscoveryTarget{ 1050 "main.default.dc9": newTarget("main", "", "default", "dc9", func(t *structs.DiscoveryTarget) { 1051 t.MeshGateway = structs.MeshGatewayConfig{ 1052 Mode: structs.MeshGatewayModeRemote, 1053 } 1054 }), 1055 }, 1056 } 1057 return compileTestCase{entries: entries, expect: expect} 1058} 1059 1060func testcase_ServiceFailover() compileTestCase { 1061 entries := newEntries() 1062 entries.AddResolvers( 1063 &structs.ServiceResolverConfigEntry{ 1064 Kind: "service-resolver", 1065 Name: "main", 1066 Failover: map[string]structs.ServiceResolverFailover{ 1067 "*": {Service: "backup"}, 1068 }, 1069 }, 1070 ) 1071 1072 expect := &structs.CompiledDiscoveryChain{ 1073 Protocol: "tcp", 1074 StartNode: "resolver:main.default.dc1", 1075 Nodes: map[string]*structs.DiscoveryGraphNode{ 1076 "resolver:main.default.dc1": { 1077 Type: structs.DiscoveryGraphNodeTypeResolver, 1078 Name: "main.default.dc1", 1079 Resolver: &structs.DiscoveryResolver{ 1080 ConnectTimeout: 5 * time.Second, 1081 Target: "main.default.dc1", 1082 Failover: &structs.DiscoveryFailover{ 1083 Targets: []string{"backup.default.dc1"}, 1084 }, 1085 }, 1086 }, 1087 }, 1088 Targets: map[string]*structs.DiscoveryTarget{ 1089 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 1090 "backup.default.dc1": newTarget("backup", "", "default", "dc1", nil), 1091 }, 1092 } 1093 return compileTestCase{entries: entries, expect: expect} 1094} 1095 1096func testcase_ServiceFailoverThroughRedirect() compileTestCase { 1097 entries := newEntries() 1098 entries.AddResolvers( 1099 &structs.ServiceResolverConfigEntry{ 1100 Kind: "service-resolver", 1101 Name: "backup", 1102 Redirect: &structs.ServiceResolverRedirect{ 1103 Service: "actual", 1104 }, 1105 }, 1106 &structs.ServiceResolverConfigEntry{ 1107 Kind: "service-resolver", 1108 Name: "main", 1109 Failover: map[string]structs.ServiceResolverFailover{ 1110 "*": {Service: "backup"}, 1111 }, 1112 }, 1113 ) 1114 1115 expect := &structs.CompiledDiscoveryChain{ 1116 Protocol: "tcp", 1117 StartNode: "resolver:main.default.dc1", 1118 Nodes: map[string]*structs.DiscoveryGraphNode{ 1119 "resolver:main.default.dc1": { 1120 Type: structs.DiscoveryGraphNodeTypeResolver, 1121 Name: "main.default.dc1", 1122 Resolver: &structs.DiscoveryResolver{ 1123 ConnectTimeout: 5 * time.Second, 1124 Target: "main.default.dc1", 1125 Failover: &structs.DiscoveryFailover{ 1126 Targets: []string{"actual.default.dc1"}, 1127 }, 1128 }, 1129 }, 1130 }, 1131 Targets: map[string]*structs.DiscoveryTarget{ 1132 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 1133 "actual.default.dc1": newTarget("actual", "", "default", "dc1", nil), 1134 }, 1135 } 1136 return compileTestCase{entries: entries, expect: expect} 1137} 1138 1139func testcase_Resolver_CircularFailover() compileTestCase { 1140 entries := newEntries() 1141 entries.AddResolvers( 1142 &structs.ServiceResolverConfigEntry{ 1143 Kind: "service-resolver", 1144 Name: "backup", 1145 Failover: map[string]structs.ServiceResolverFailover{ 1146 "*": {Service: "main"}, 1147 }, 1148 }, 1149 &structs.ServiceResolverConfigEntry{ 1150 Kind: "service-resolver", 1151 Name: "main", 1152 Failover: map[string]structs.ServiceResolverFailover{ 1153 "*": {Service: "backup"}, 1154 }, 1155 }, 1156 ) 1157 1158 expect := &structs.CompiledDiscoveryChain{ 1159 Protocol: "tcp", 1160 StartNode: "resolver:main.default.dc1", 1161 Nodes: map[string]*structs.DiscoveryGraphNode{ 1162 "resolver:main.default.dc1": { 1163 Type: structs.DiscoveryGraphNodeTypeResolver, 1164 Name: "main.default.dc1", 1165 Resolver: &structs.DiscoveryResolver{ 1166 ConnectTimeout: 5 * time.Second, 1167 Target: "main.default.dc1", 1168 Failover: &structs.DiscoveryFailover{ 1169 Targets: []string{"backup.default.dc1"}, 1170 }, 1171 }, 1172 }, 1173 }, 1174 Targets: map[string]*structs.DiscoveryTarget{ 1175 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 1176 "backup.default.dc1": newTarget("backup", "", "default", "dc1", nil), 1177 }, 1178 } 1179 return compileTestCase{entries: entries, expect: expect} 1180} 1181 1182func testcase_ServiceAndSubsetFailover() compileTestCase { 1183 entries := newEntries() 1184 entries.AddResolvers( 1185 &structs.ServiceResolverConfigEntry{ 1186 Kind: "service-resolver", 1187 Name: "main", 1188 Subsets: map[string]structs.ServiceResolverSubset{ 1189 "backup": { 1190 Filter: "Service.Meta.version == backup", 1191 }, 1192 }, 1193 Failover: map[string]structs.ServiceResolverFailover{ 1194 "*": {ServiceSubset: "backup"}, 1195 }, 1196 }, 1197 ) 1198 1199 expect := &structs.CompiledDiscoveryChain{ 1200 Protocol: "tcp", 1201 StartNode: "resolver:main.default.dc1", 1202 Nodes: map[string]*structs.DiscoveryGraphNode{ 1203 "resolver:main.default.dc1": { 1204 Type: structs.DiscoveryGraphNodeTypeResolver, 1205 Name: "main.default.dc1", 1206 Resolver: &structs.DiscoveryResolver{ 1207 ConnectTimeout: 5 * time.Second, 1208 Target: "main.default.dc1", 1209 Failover: &structs.DiscoveryFailover{ 1210 Targets: []string{"backup.main.default.dc1"}, 1211 }, 1212 }, 1213 }, 1214 }, 1215 Targets: map[string]*structs.DiscoveryTarget{ 1216 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 1217 "backup.main.default.dc1": newTarget("main", "backup", "default", "dc1", func(t *structs.DiscoveryTarget) { 1218 t.Subset = structs.ServiceResolverSubset{ 1219 Filter: "Service.Meta.version == backup", 1220 } 1221 }), 1222 }, 1223 } 1224 return compileTestCase{entries: entries, expect: expect} 1225} 1226 1227func testcase_DatacenterFailover() compileTestCase { 1228 entries := newEntries() 1229 entries.AddResolvers( 1230 &structs.ServiceResolverConfigEntry{ 1231 Kind: "service-resolver", 1232 Name: "main", 1233 Failover: map[string]structs.ServiceResolverFailover{ 1234 "*": {Datacenters: []string{"dc2", "dc4"}}, 1235 }, 1236 }, 1237 ) 1238 1239 expect := &structs.CompiledDiscoveryChain{ 1240 Protocol: "tcp", 1241 StartNode: "resolver:main.default.dc1", 1242 Nodes: map[string]*structs.DiscoveryGraphNode{ 1243 "resolver:main.default.dc1": { 1244 Type: structs.DiscoveryGraphNodeTypeResolver, 1245 Name: "main.default.dc1", 1246 Resolver: &structs.DiscoveryResolver{ 1247 ConnectTimeout: 5 * time.Second, 1248 Target: "main.default.dc1", 1249 Failover: &structs.DiscoveryFailover{ 1250 Targets: []string{"main.default.dc2", "main.default.dc4"}, 1251 }, 1252 }, 1253 }, 1254 }, 1255 Targets: map[string]*structs.DiscoveryTarget{ 1256 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 1257 "main.default.dc2": newTarget("main", "", "default", "dc2", nil), 1258 "main.default.dc4": newTarget("main", "", "default", "dc4", nil), 1259 }, 1260 } 1261 return compileTestCase{entries: entries, expect: expect} 1262} 1263 1264func testcase_DatacenterFailover_WithMeshGateways() compileTestCase { 1265 entries := newEntries() 1266 entries.GlobalProxy = &structs.ProxyConfigEntry{ 1267 Kind: structs.ProxyDefaults, 1268 Name: structs.ProxyConfigGlobal, 1269 MeshGateway: structs.MeshGatewayConfig{ 1270 Mode: structs.MeshGatewayModeRemote, 1271 }, 1272 } 1273 entries.AddResolvers( 1274 &structs.ServiceResolverConfigEntry{ 1275 Kind: "service-resolver", 1276 Name: "main", 1277 Failover: map[string]structs.ServiceResolverFailover{ 1278 "*": {Datacenters: []string{"dc2", "dc4"}}, 1279 }, 1280 }, 1281 ) 1282 1283 expect := &structs.CompiledDiscoveryChain{ 1284 Protocol: "tcp", 1285 StartNode: "resolver:main.default.dc1", 1286 Nodes: map[string]*structs.DiscoveryGraphNode{ 1287 "resolver:main.default.dc1": { 1288 Type: structs.DiscoveryGraphNodeTypeResolver, 1289 Name: "main.default.dc1", 1290 Resolver: &structs.DiscoveryResolver{ 1291 ConnectTimeout: 5 * time.Second, 1292 Target: "main.default.dc1", 1293 Failover: &structs.DiscoveryFailover{ 1294 Targets: []string{ 1295 "main.default.dc2", 1296 "main.default.dc4", 1297 }, 1298 }, 1299 }, 1300 }, 1301 }, 1302 Targets: map[string]*structs.DiscoveryTarget{ 1303 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 1304 "main.default.dc2": newTarget("main", "", "default", "dc2", func(t *structs.DiscoveryTarget) { 1305 t.MeshGateway = structs.MeshGatewayConfig{ 1306 Mode: structs.MeshGatewayModeRemote, 1307 } 1308 }), 1309 "main.default.dc4": newTarget("main", "", "default", "dc4", func(t *structs.DiscoveryTarget) { 1310 t.MeshGateway = structs.MeshGatewayConfig{ 1311 Mode: structs.MeshGatewayModeRemote, 1312 } 1313 }), 1314 }, 1315 } 1316 return compileTestCase{entries: entries, expect: expect} 1317} 1318 1319func testcase_NoopSplit_WithDefaultSubset() compileTestCase { 1320 entries := newEntries() 1321 setServiceProtocol(entries, "main", "http") 1322 1323 entries.AddSplitters( 1324 &structs.ServiceSplitterConfigEntry{ 1325 Kind: "service-splitter", 1326 Name: "main", 1327 Splits: []structs.ServiceSplit{ 1328 {Weight: 100}, 1329 }, 1330 }, 1331 ) 1332 entries.AddResolvers( 1333 &structs.ServiceResolverConfigEntry{ 1334 Kind: "service-resolver", 1335 Name: "main", 1336 DefaultSubset: "v2", 1337 Subsets: map[string]structs.ServiceResolverSubset{ 1338 "v1": {Filter: "Service.Meta.version == 1"}, 1339 "v2": {Filter: "Service.Meta.version == 2"}, 1340 }, 1341 }, 1342 ) 1343 1344 expect := &structs.CompiledDiscoveryChain{ 1345 Protocol: "http", 1346 StartNode: "splitter:main.default", 1347 Nodes: map[string]*structs.DiscoveryGraphNode{ 1348 "splitter:main.default": { 1349 Type: structs.DiscoveryGraphNodeTypeSplitter, 1350 Name: "main.default", 1351 Splits: []*structs.DiscoverySplit{ 1352 { 1353 Weight: 100, 1354 NextNode: "resolver:v2.main.default.dc1", 1355 }, 1356 }, 1357 }, 1358 "resolver:v2.main.default.dc1": { 1359 Type: structs.DiscoveryGraphNodeTypeResolver, 1360 Name: "v2.main.default.dc1", 1361 Resolver: &structs.DiscoveryResolver{ 1362 ConnectTimeout: 5 * time.Second, 1363 Target: "v2.main.default.dc1", 1364 }, 1365 }, 1366 }, 1367 Targets: map[string]*structs.DiscoveryTarget{ 1368 "v2.main.default.dc1": newTarget("main", "v2", "default", "dc1", func(t *structs.DiscoveryTarget) { 1369 t.Subset = structs.ServiceResolverSubset{ 1370 Filter: "Service.Meta.version == 2", 1371 } 1372 }), 1373 }, 1374 } 1375 return compileTestCase{entries: entries, expect: expect} 1376} 1377 1378func testcase_DefaultResolver() compileTestCase { 1379 entries := newEntries() 1380 1381 expect := &structs.CompiledDiscoveryChain{ 1382 Protocol: "tcp", 1383 StartNode: "resolver:main.default.dc1", 1384 Nodes: map[string]*structs.DiscoveryGraphNode{ 1385 "resolver:main.default.dc1": { 1386 Type: structs.DiscoveryGraphNodeTypeResolver, 1387 Name: "main.default.dc1", 1388 Resolver: &structs.DiscoveryResolver{ 1389 Default: true, 1390 ConnectTimeout: 5 * time.Second, 1391 Target: "main.default.dc1", 1392 }, 1393 }, 1394 }, 1395 Targets: map[string]*structs.DiscoveryTarget{ 1396 // TODO-TARGET 1397 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 1398 }, 1399 } 1400 return compileTestCase{entries: entries, expect: expect, expectIsDefault: true} 1401} 1402 1403func testcase_DefaultResolver_WithProxyDefaults() compileTestCase { 1404 entries := newEntries() 1405 entries.GlobalProxy = &structs.ProxyConfigEntry{ 1406 Kind: structs.ProxyDefaults, 1407 Name: structs.ProxyConfigGlobal, 1408 Config: map[string]interface{}{ 1409 "protocol": "grpc", 1410 }, 1411 MeshGateway: structs.MeshGatewayConfig{ 1412 Mode: structs.MeshGatewayModeRemote, 1413 }, 1414 } 1415 1416 expect := &structs.CompiledDiscoveryChain{ 1417 Protocol: "grpc", 1418 StartNode: "resolver:main.default.dc1", 1419 Nodes: map[string]*structs.DiscoveryGraphNode{ 1420 "resolver:main.default.dc1": { 1421 Type: structs.DiscoveryGraphNodeTypeResolver, 1422 Name: "main.default.dc1", 1423 Resolver: &structs.DiscoveryResolver{ 1424 Default: true, 1425 ConnectTimeout: 5 * time.Second, 1426 Target: "main.default.dc1", 1427 }, 1428 }, 1429 }, 1430 Targets: map[string]*structs.DiscoveryTarget{ 1431 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 1432 }, 1433 } 1434 return compileTestCase{entries: entries, expect: expect, expectIsDefault: true} 1435} 1436 1437func testcase_RedirectToDefaultResolverIsNotDefaultChain() compileTestCase { 1438 entries := newEntries() 1439 entries.AddResolvers( 1440 &structs.ServiceResolverConfigEntry{ 1441 Kind: structs.ServiceResolver, 1442 Name: "main", 1443 Redirect: &structs.ServiceResolverRedirect{ 1444 Service: "other", 1445 }, 1446 }, 1447 ) 1448 1449 expect := &structs.CompiledDiscoveryChain{ 1450 Protocol: "tcp", 1451 StartNode: "resolver:other.default.dc1", 1452 Nodes: map[string]*structs.DiscoveryGraphNode{ 1453 "resolver:other.default.dc1": { 1454 Type: structs.DiscoveryGraphNodeTypeResolver, 1455 Name: "other.default.dc1", 1456 Resolver: &structs.DiscoveryResolver{ 1457 Default: true, 1458 ConnectTimeout: 5 * time.Second, 1459 Target: "other.default.dc1", 1460 }, 1461 }, 1462 }, 1463 Targets: map[string]*structs.DiscoveryTarget{ 1464 "other.default.dc1": newTarget("other", "", "default", "dc1", nil), 1465 }, 1466 } 1467 1468 return compileTestCase{entries: entries, expect: expect, expectIsDefault: false /*being explicit here because this is the whole point of this test*/} 1469} 1470 1471func testcase_Resolve_WithDefaultSubset() compileTestCase { 1472 entries := newEntries() 1473 entries.AddResolvers( 1474 &structs.ServiceResolverConfigEntry{ 1475 Kind: "service-resolver", 1476 Name: "main", 1477 DefaultSubset: "v2", 1478 Subsets: map[string]structs.ServiceResolverSubset{ 1479 "v1": {Filter: "Service.Meta.version == 1"}, 1480 "v2": {Filter: "Service.Meta.version == 2"}, 1481 }, 1482 }, 1483 ) 1484 1485 expect := &structs.CompiledDiscoveryChain{ 1486 Protocol: "tcp", 1487 StartNode: "resolver:v2.main.default.dc1", 1488 Nodes: map[string]*structs.DiscoveryGraphNode{ 1489 "resolver:v2.main.default.dc1": { 1490 Type: structs.DiscoveryGraphNodeTypeResolver, 1491 Name: "v2.main.default.dc1", 1492 Resolver: &structs.DiscoveryResolver{ 1493 ConnectTimeout: 5 * time.Second, 1494 Target: "v2.main.default.dc1", 1495 }, 1496 }, 1497 }, 1498 Targets: map[string]*structs.DiscoveryTarget{ 1499 "v2.main.default.dc1": newTarget("main", "v2", "default", "dc1", func(t *structs.DiscoveryTarget) { 1500 t.Subset = structs.ServiceResolverSubset{ 1501 Filter: "Service.Meta.version == 2", 1502 } 1503 }), 1504 }, 1505 } 1506 return compileTestCase{entries: entries, expect: expect} 1507} 1508 1509func testcase_DefaultResolver_ExternalSNI() compileTestCase { 1510 entries := newEntries() 1511 entries.AddServices(&structs.ServiceConfigEntry{ 1512 Kind: structs.ServiceDefaults, 1513 Name: "main", 1514 ExternalSNI: "main.some.other.service.mesh", 1515 }) 1516 1517 expect := &structs.CompiledDiscoveryChain{ 1518 Protocol: "tcp", 1519 StartNode: "resolver:main.default.dc1", 1520 Nodes: map[string]*structs.DiscoveryGraphNode{ 1521 "resolver:main.default.dc1": { 1522 Type: structs.DiscoveryGraphNodeTypeResolver, 1523 Name: "main.default.dc1", 1524 Resolver: &structs.DiscoveryResolver{ 1525 Default: true, 1526 ConnectTimeout: 5 * time.Second, 1527 Target: "main.default.dc1", 1528 }, 1529 }, 1530 }, 1531 Targets: map[string]*structs.DiscoveryTarget{ 1532 "main.default.dc1": newTarget("main", "", "default", "dc1", func(t *structs.DiscoveryTarget) { 1533 t.SNI = "main.some.other.service.mesh" 1534 t.External = true 1535 }), 1536 }, 1537 } 1538 return compileTestCase{entries: entries, expect: expect, expectIsDefault: true} 1539} 1540 1541func testcase_Resolver_ExternalSNI_FailoverNotAllowed() compileTestCase { 1542 entries := newEntries() 1543 entries.AddServices(&structs.ServiceConfigEntry{ 1544 Kind: structs.ServiceDefaults, 1545 Name: "main", 1546 ExternalSNI: "main.some.other.service.mesh", 1547 }) 1548 entries.AddResolvers(&structs.ServiceResolverConfigEntry{ 1549 Kind: "service-resolver", 1550 Name: "main", 1551 ConnectTimeout: 33 * time.Second, 1552 Failover: map[string]structs.ServiceResolverFailover{ 1553 "*": {Service: "backup"}, 1554 }, 1555 }) 1556 1557 return compileTestCase{ 1558 entries: entries, 1559 expectErr: `service "main" has an external SNI set; cannot define failover for external services`, 1560 expectGraphErr: true, 1561 } 1562} 1563 1564func testcase_Resolver_ExternalSNI_SubsetsNotAllowed() compileTestCase { 1565 entries := newEntries() 1566 entries.AddServices(&structs.ServiceConfigEntry{ 1567 Kind: structs.ServiceDefaults, 1568 Name: "main", 1569 ExternalSNI: "main.some.other.service.mesh", 1570 }) 1571 entries.AddResolvers(&structs.ServiceResolverConfigEntry{ 1572 Kind: "service-resolver", 1573 Name: "main", 1574 ConnectTimeout: 33 * time.Second, 1575 Subsets: map[string]structs.ServiceResolverSubset{ 1576 "canary": { 1577 Filter: "Service.Meta.version == canary", 1578 }, 1579 }, 1580 }) 1581 1582 return compileTestCase{ 1583 entries: entries, 1584 expectErr: `service "main" has an external SNI set; cannot define subsets for external services`, 1585 expectGraphErr: true, 1586 } 1587} 1588 1589func testcase_Resolver_ExternalSNI_RedirectNotAllowed() compileTestCase { 1590 entries := newEntries() 1591 entries.AddServices(&structs.ServiceConfigEntry{ 1592 Kind: structs.ServiceDefaults, 1593 Name: "main", 1594 ExternalSNI: "main.some.other.service.mesh", 1595 }) 1596 entries.AddResolvers(&structs.ServiceResolverConfigEntry{ 1597 Kind: "service-resolver", 1598 Name: "main", 1599 ConnectTimeout: 33 * time.Second, 1600 Redirect: &structs.ServiceResolverRedirect{ 1601 Datacenter: "dc2", 1602 }, 1603 }) 1604 1605 return compileTestCase{ 1606 entries: entries, 1607 expectErr: `service "main" has an external SNI set; cannot define redirects for external services`, 1608 expectGraphErr: true, 1609 } 1610} 1611 1612func testcase_MultiDatacenterCanary() compileTestCase { 1613 entries := newEntries() 1614 setServiceProtocol(entries, "main", "http") 1615 setServiceProtocol(entries, "main-dc2", "http") 1616 setServiceProtocol(entries, "main-dc3", "http") 1617 1618 entries.AddSplitters( 1619 &structs.ServiceSplitterConfigEntry{ 1620 Kind: "service-splitter", 1621 Name: "main", 1622 Splits: []structs.ServiceSplit{ 1623 {Weight: 60, Service: "main-dc2"}, 1624 {Weight: 40, Service: "main-dc3"}, 1625 }, 1626 }, 1627 ) 1628 entries.AddResolvers( 1629 &structs.ServiceResolverConfigEntry{ 1630 Kind: "service-resolver", 1631 Name: "main-dc2", 1632 Redirect: &structs.ServiceResolverRedirect{ 1633 Service: "main", 1634 Datacenter: "dc2", 1635 }, 1636 }, 1637 &structs.ServiceResolverConfigEntry{ 1638 Kind: "service-resolver", 1639 Name: "main-dc3", 1640 Redirect: &structs.ServiceResolverRedirect{ 1641 Service: "main", 1642 Datacenter: "dc3", 1643 }, 1644 }, 1645 &structs.ServiceResolverConfigEntry{ 1646 Kind: "service-resolver", 1647 Name: "main", 1648 ConnectTimeout: 33 * time.Second, 1649 }, 1650 ) 1651 1652 expect := &structs.CompiledDiscoveryChain{ 1653 Protocol: "http", 1654 StartNode: "splitter:main.default", 1655 Nodes: map[string]*structs.DiscoveryGraphNode{ 1656 "splitter:main.default": { 1657 Type: structs.DiscoveryGraphNodeTypeSplitter, 1658 Name: "main.default", 1659 Splits: []*structs.DiscoverySplit{ 1660 { 1661 Weight: 60, 1662 NextNode: "resolver:main.default.dc2", 1663 }, 1664 { 1665 Weight: 40, 1666 NextNode: "resolver:main.default.dc3", 1667 }, 1668 }, 1669 }, 1670 "resolver:main.default.dc2": { 1671 Type: structs.DiscoveryGraphNodeTypeResolver, 1672 Name: "main.default.dc2", 1673 Resolver: &structs.DiscoveryResolver{ 1674 ConnectTimeout: 33 * time.Second, 1675 Target: "main.default.dc2", 1676 }, 1677 }, 1678 "resolver:main.default.dc3": { 1679 Type: structs.DiscoveryGraphNodeTypeResolver, 1680 Name: "main.default.dc3", 1681 Resolver: &structs.DiscoveryResolver{ 1682 ConnectTimeout: 33 * time.Second, 1683 Target: "main.default.dc3", 1684 }, 1685 }, 1686 }, 1687 Targets: map[string]*structs.DiscoveryTarget{ 1688 "main.default.dc2": newTarget("main", "", "default", "dc2", nil), 1689 "main.default.dc3": newTarget("main", "", "default", "dc3", nil), 1690 }, 1691 } 1692 return compileTestCase{entries: entries, expect: expect} 1693} 1694 1695func testcase_AllBellsAndWhistles() compileTestCase { 1696 entries := newEntries() 1697 setServiceProtocol(entries, "main", "http") 1698 setServiceProtocol(entries, "svc-redirect", "http") 1699 setServiceProtocol(entries, "svc-redirect-again", "http") 1700 setServiceProtocol(entries, "svc-split", "http") 1701 setServiceProtocol(entries, "svc-split-again", "http") 1702 setServiceProtocol(entries, "svc-split-one-more-time", "http") 1703 setServiceProtocol(entries, "redirected", "http") 1704 1705 entries.AddRouters( 1706 &structs.ServiceRouterConfigEntry{ 1707 Kind: "service-router", 1708 Name: "main", 1709 Routes: []structs.ServiceRoute{ 1710 newSimpleRoute("svc-redirect"), // double redirected to a default subset 1711 newSimpleRoute("svc-split"), // one split is split further 1712 }, 1713 }, 1714 ) 1715 entries.AddSplitters( 1716 &structs.ServiceSplitterConfigEntry{ 1717 Kind: "service-splitter", 1718 Name: "svc-split", 1719 Splits: []structs.ServiceSplit{ 1720 {Weight: 60, Service: "svc-redirect"}, // double redirected to a default subset 1721 {Weight: 40, Service: "svc-split-again"}, // split again 1722 }, 1723 }, 1724 &structs.ServiceSplitterConfigEntry{ 1725 Kind: "service-splitter", 1726 Name: "svc-split-again", 1727 Splits: []structs.ServiceSplit{ 1728 {Weight: 75, Service: "main", ServiceSubset: "v1"}, 1729 {Weight: 25, Service: "svc-split-one-more-time"}, 1730 }, 1731 }, 1732 &structs.ServiceSplitterConfigEntry{ 1733 Kind: "service-splitter", 1734 Name: "svc-split-one-more-time", 1735 Splits: []structs.ServiceSplit{ 1736 {Weight: 80, Service: "main", ServiceSubset: "v2"}, 1737 {Weight: 20, Service: "main", ServiceSubset: "v3"}, 1738 }, 1739 }, 1740 ) 1741 1742 entries.AddResolvers( 1743 &structs.ServiceResolverConfigEntry{ 1744 Kind: "service-resolver", 1745 Name: "svc-redirect", 1746 Redirect: &structs.ServiceResolverRedirect{ 1747 Service: "svc-redirect-again", 1748 }, 1749 }, 1750 &structs.ServiceResolverConfigEntry{ 1751 Kind: "service-resolver", 1752 Name: "svc-redirect-again", 1753 Redirect: &structs.ServiceResolverRedirect{ 1754 Service: "redirected", 1755 }, 1756 }, 1757 &structs.ServiceResolverConfigEntry{ 1758 Kind: "service-resolver", 1759 Name: "redirected", 1760 DefaultSubset: "prod", 1761 Subsets: map[string]structs.ServiceResolverSubset{ 1762 "prod": {Filter: "ServiceMeta.env == prod"}, 1763 "qa": {Filter: "ServiceMeta.env == qa"}, 1764 }, 1765 LoadBalancer: &structs.LoadBalancer{ 1766 Policy: "ring_hash", 1767 RingHashConfig: &structs.RingHashConfig{ 1768 MaximumRingSize: 100, 1769 }, 1770 HashPolicies: []structs.HashPolicy{ 1771 { 1772 SourceIP: true, 1773 }, 1774 }, 1775 }, 1776 }, 1777 &structs.ServiceResolverConfigEntry{ 1778 Kind: "service-resolver", 1779 Name: "main", 1780 DefaultSubset: "default-subset", 1781 Subsets: map[string]structs.ServiceResolverSubset{ 1782 "v1": {Filter: "Service.Meta.version == 1"}, 1783 "v2": {Filter: "Service.Meta.version == 2"}, 1784 "v3": {Filter: "Service.Meta.version == 3"}, 1785 "default-subset": {OnlyPassing: true}, 1786 }, 1787 }, 1788 ) 1789 1790 var ( 1791 router = entries.GetRouter(structs.NewServiceID("main", nil)) 1792 ) 1793 1794 expect := &structs.CompiledDiscoveryChain{ 1795 Protocol: "http", 1796 StartNode: "router:main.default", 1797 Nodes: map[string]*structs.DiscoveryGraphNode{ 1798 "router:main.default": { 1799 Type: structs.DiscoveryGraphNodeTypeRouter, 1800 Name: "main.default", 1801 Routes: []*structs.DiscoveryRoute{ 1802 { 1803 Definition: &router.Routes[0], 1804 NextNode: "resolver:prod.redirected.default.dc1", 1805 }, 1806 { 1807 Definition: &router.Routes[1], 1808 NextNode: "splitter:svc-split.default", 1809 }, 1810 { 1811 Definition: newDefaultServiceRoute("main", "default"), 1812 NextNode: "resolver:default-subset.main.default.dc1", 1813 }, 1814 }, 1815 }, 1816 "splitter:svc-split.default": { 1817 Type: structs.DiscoveryGraphNodeTypeSplitter, 1818 Name: "svc-split.default", 1819 Splits: []*structs.DiscoverySplit{ 1820 { 1821 Weight: 60, 1822 NextNode: "resolver:prod.redirected.default.dc1", 1823 }, 1824 { 1825 Weight: 30, 1826 NextNode: "resolver:v1.main.default.dc1", 1827 }, 1828 { 1829 Weight: 8, 1830 NextNode: "resolver:v2.main.default.dc1", 1831 }, 1832 { 1833 Weight: 2, 1834 NextNode: "resolver:v3.main.default.dc1", 1835 }, 1836 }, 1837 LoadBalancer: &structs.LoadBalancer{ 1838 Policy: "ring_hash", 1839 RingHashConfig: &structs.RingHashConfig{ 1840 MaximumRingSize: 100, 1841 }, 1842 HashPolicies: []structs.HashPolicy{ 1843 { 1844 SourceIP: true, 1845 }, 1846 }, 1847 }, 1848 }, 1849 "resolver:prod.redirected.default.dc1": { 1850 Type: structs.DiscoveryGraphNodeTypeResolver, 1851 Name: "prod.redirected.default.dc1", 1852 Resolver: &structs.DiscoveryResolver{ 1853 ConnectTimeout: 5 * time.Second, 1854 Target: "prod.redirected.default.dc1", 1855 }, 1856 LoadBalancer: &structs.LoadBalancer{ 1857 Policy: "ring_hash", 1858 RingHashConfig: &structs.RingHashConfig{ 1859 MaximumRingSize: 100, 1860 }, 1861 HashPolicies: []structs.HashPolicy{ 1862 { 1863 SourceIP: true, 1864 }, 1865 }, 1866 }, 1867 }, 1868 "resolver:v1.main.default.dc1": { 1869 Type: structs.DiscoveryGraphNodeTypeResolver, 1870 Name: "v1.main.default.dc1", 1871 Resolver: &structs.DiscoveryResolver{ 1872 ConnectTimeout: 5 * time.Second, 1873 Target: "v1.main.default.dc1", 1874 }, 1875 }, 1876 "resolver:v2.main.default.dc1": { 1877 Type: structs.DiscoveryGraphNodeTypeResolver, 1878 Name: "v2.main.default.dc1", 1879 Resolver: &structs.DiscoveryResolver{ 1880 ConnectTimeout: 5 * time.Second, 1881 Target: "v2.main.default.dc1", 1882 }, 1883 }, 1884 "resolver:v3.main.default.dc1": { 1885 Type: structs.DiscoveryGraphNodeTypeResolver, 1886 Name: "v3.main.default.dc1", 1887 Resolver: &structs.DiscoveryResolver{ 1888 ConnectTimeout: 5 * time.Second, 1889 Target: "v3.main.default.dc1", 1890 }, 1891 }, 1892 "resolver:default-subset.main.default.dc1": { 1893 Type: structs.DiscoveryGraphNodeTypeResolver, 1894 Name: "default-subset.main.default.dc1", 1895 Resolver: &structs.DiscoveryResolver{ 1896 ConnectTimeout: 5 * time.Second, 1897 Target: "default-subset.main.default.dc1", 1898 }, 1899 }, 1900 }, 1901 Targets: map[string]*structs.DiscoveryTarget{ 1902 "prod.redirected.default.dc1": newTarget("redirected", "prod", "default", "dc1", func(t *structs.DiscoveryTarget) { 1903 t.Subset = structs.ServiceResolverSubset{ 1904 Filter: "ServiceMeta.env == prod", 1905 } 1906 }), 1907 "v1.main.default.dc1": newTarget("main", "v1", "default", "dc1", func(t *structs.DiscoveryTarget) { 1908 t.Subset = structs.ServiceResolverSubset{ 1909 Filter: "Service.Meta.version == 1", 1910 } 1911 }), 1912 "v2.main.default.dc1": newTarget("main", "v2", "default", "dc1", func(t *structs.DiscoveryTarget) { 1913 t.Subset = structs.ServiceResolverSubset{ 1914 Filter: "Service.Meta.version == 2", 1915 } 1916 }), 1917 "v3.main.default.dc1": newTarget("main", "v3", "default", "dc1", func(t *structs.DiscoveryTarget) { 1918 t.Subset = structs.ServiceResolverSubset{ 1919 Filter: "Service.Meta.version == 3", 1920 } 1921 }), 1922 "default-subset.main.default.dc1": newTarget("main", "default-subset", "default", "dc1", func(t *structs.DiscoveryTarget) { 1923 t.Subset = structs.ServiceResolverSubset{OnlyPassing: true} 1924 }), 1925 }, 1926 } 1927 return compileTestCase{entries: entries, expect: expect} 1928} 1929 1930func testcase_SplitterRequiresValidProtocol() compileTestCase { 1931 entries := newEntries() 1932 setServiceProtocol(entries, "main", "tcp") 1933 1934 entries.AddSplitters( 1935 &structs.ServiceSplitterConfigEntry{ 1936 Kind: structs.ServiceSplitter, 1937 Name: "main", 1938 Splits: []structs.ServiceSplit{ 1939 {Weight: 90, Namespace: "v1"}, 1940 {Weight: 10, Namespace: "v2"}, 1941 }, 1942 }, 1943 ) 1944 1945 return compileTestCase{ 1946 entries: entries, 1947 expectErr: "does not permit advanced routing or splitting behavior", 1948 expectGraphErr: true, 1949 } 1950} 1951 1952func testcase_RouterRequiresValidProtocol() compileTestCase { 1953 entries := newEntries() 1954 setServiceProtocol(entries, "main", "tcp") 1955 1956 entries.AddRouters( 1957 &structs.ServiceRouterConfigEntry{ 1958 Kind: structs.ServiceRouter, 1959 Name: "main", 1960 Routes: []structs.ServiceRoute{ 1961 { 1962 Match: &structs.ServiceRouteMatch{ 1963 HTTP: &structs.ServiceRouteHTTPMatch{ 1964 PathExact: "/other", 1965 }, 1966 }, 1967 Destination: &structs.ServiceRouteDestination{ 1968 Namespace: "other", 1969 }, 1970 }, 1971 }, 1972 }, 1973 ) 1974 return compileTestCase{ 1975 entries: entries, 1976 expectErr: "does not permit advanced routing or splitting behavior", 1977 expectGraphErr: true, 1978 } 1979} 1980 1981func testcase_SplitToUnsplittableProtocol() compileTestCase { 1982 entries := newEntries() 1983 setServiceProtocol(entries, "main", "tcp") 1984 setServiceProtocol(entries, "other", "tcp") 1985 1986 entries.AddSplitters( 1987 &structs.ServiceSplitterConfigEntry{ 1988 Kind: structs.ServiceSplitter, 1989 Name: "main", 1990 Splits: []structs.ServiceSplit{ 1991 {Weight: 90}, 1992 {Weight: 10, Service: "other"}, 1993 }, 1994 }, 1995 ) 1996 return compileTestCase{ 1997 entries: entries, 1998 expectErr: "does not permit advanced routing or splitting behavior", 1999 expectGraphErr: true, 2000 } 2001} 2002 2003func testcase_RouteToUnroutableProtocol() compileTestCase { 2004 entries := newEntries() 2005 setServiceProtocol(entries, "main", "tcp") 2006 setServiceProtocol(entries, "other", "tcp") 2007 2008 entries.AddRouters( 2009 &structs.ServiceRouterConfigEntry{ 2010 Kind: structs.ServiceRouter, 2011 Name: "main", 2012 Routes: []structs.ServiceRoute{ 2013 { 2014 Match: &structs.ServiceRouteMatch{ 2015 HTTP: &structs.ServiceRouteHTTPMatch{ 2016 PathExact: "/other", 2017 }, 2018 }, 2019 Destination: &structs.ServiceRouteDestination{ 2020 Service: "other", 2021 }, 2022 }, 2023 }, 2024 }, 2025 ) 2026 2027 return compileTestCase{ 2028 entries: entries, 2029 expectErr: "does not permit advanced routing or splitting behavior", 2030 expectGraphErr: true, 2031 } 2032} 2033 2034func testcase_FailoverCrossesProtocols() compileTestCase { 2035 entries := newEntries() 2036 setServiceProtocol(entries, "main", "grpc") 2037 setServiceProtocol(entries, "other", "tcp") 2038 2039 entries.AddResolvers( 2040 &structs.ServiceResolverConfigEntry{ 2041 Kind: structs.ServiceResolver, 2042 Name: "main", 2043 Failover: map[string]structs.ServiceResolverFailover{ 2044 "*": { 2045 Service: "other", 2046 }, 2047 }, 2048 }, 2049 ) 2050 2051 return compileTestCase{ 2052 entries: entries, 2053 expectErr: "uses inconsistent protocols", 2054 expectGraphErr: true, 2055 } 2056} 2057 2058func testcase_RedirectCrossesProtocols() compileTestCase { 2059 entries := newEntries() 2060 setServiceProtocol(entries, "main", "grpc") 2061 setServiceProtocol(entries, "other", "tcp") 2062 2063 entries.AddResolvers( 2064 &structs.ServiceResolverConfigEntry{ 2065 Kind: structs.ServiceResolver, 2066 Name: "main", 2067 Redirect: &structs.ServiceResolverRedirect{ 2068 Service: "other", 2069 }, 2070 }, 2071 ) 2072 return compileTestCase{ 2073 entries: entries, 2074 expectErr: "uses inconsistent protocols", 2075 expectGraphErr: true, 2076 } 2077} 2078 2079func testcase_RedirectToMissingSubset() compileTestCase { 2080 entries := newEntries() 2081 2082 entries.AddResolvers( 2083 &structs.ServiceResolverConfigEntry{ 2084 Kind: structs.ServiceResolver, 2085 Name: "other", 2086 ConnectTimeout: 33 * time.Second, 2087 }, 2088 &structs.ServiceResolverConfigEntry{ 2089 Kind: structs.ServiceResolver, 2090 Name: "main", 2091 Redirect: &structs.ServiceResolverRedirect{ 2092 Service: "other", 2093 ServiceSubset: "v1", 2094 }, 2095 }, 2096 ) 2097 2098 return compileTestCase{ 2099 entries: entries, 2100 expectErr: `does not have a subset named "v1"`, 2101 expectGraphErr: true, 2102 } 2103} 2104 2105func testcase_ResolverProtocolOverride() compileTestCase { 2106 entries := newEntries() 2107 setServiceProtocol(entries, "main", "grpc") 2108 2109 expect := &structs.CompiledDiscoveryChain{ 2110 Protocol: "http2", 2111 StartNode: "resolver:main.default.dc1", 2112 Nodes: map[string]*structs.DiscoveryGraphNode{ 2113 "resolver:main.default.dc1": { 2114 Type: structs.DiscoveryGraphNodeTypeResolver, 2115 Name: "main.default.dc1", 2116 Resolver: &structs.DiscoveryResolver{ 2117 Default: true, 2118 ConnectTimeout: 5 * time.Second, 2119 Target: "main.default.dc1", 2120 }, 2121 }, 2122 }, 2123 Targets: map[string]*structs.DiscoveryTarget{ 2124 // TODO-TARGET 2125 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 2126 }, 2127 } 2128 return compileTestCase{entries: entries, expect: expect, expectIsDefault: true, 2129 expectCustom: true, 2130 setup: func(req *CompileRequest) { 2131 req.OverrideProtocol = "http2" 2132 }, 2133 } 2134} 2135 2136func testcase_ResolverProtocolOverrideIgnored() compileTestCase { 2137 // This shows that if you try to override the protocol to its current value 2138 // the override is completely ignored. 2139 entries := newEntries() 2140 setServiceProtocol(entries, "main", "http2") 2141 2142 expect := &structs.CompiledDiscoveryChain{ 2143 Protocol: "http2", 2144 StartNode: "resolver:main.default.dc1", 2145 Nodes: map[string]*structs.DiscoveryGraphNode{ 2146 "resolver:main.default.dc1": { 2147 Type: structs.DiscoveryGraphNodeTypeResolver, 2148 Name: "main.default.dc1", 2149 Resolver: &structs.DiscoveryResolver{ 2150 Default: true, 2151 ConnectTimeout: 5 * time.Second, 2152 Target: "main.default.dc1", 2153 }, 2154 }, 2155 }, 2156 Targets: map[string]*structs.DiscoveryTarget{ 2157 // TODO-TARGET 2158 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 2159 }, 2160 } 2161 return compileTestCase{entries: entries, expect: expect, expectIsDefault: true, 2162 setup: func(req *CompileRequest) { 2163 req.OverrideProtocol = "http2" 2164 }, 2165 } 2166} 2167 2168func testcase_RouterIgnored_ResolverProtocolOverride() compileTestCase { 2169 entries := newEntries() 2170 setServiceProtocol(entries, "main", "grpc") 2171 2172 entries.AddRouters( 2173 &structs.ServiceRouterConfigEntry{ 2174 Kind: "service-router", 2175 Name: "main", 2176 }, 2177 ) 2178 2179 expect := &structs.CompiledDiscoveryChain{ 2180 Protocol: "tcp", 2181 StartNode: "resolver:main.default.dc1", 2182 Nodes: map[string]*structs.DiscoveryGraphNode{ 2183 "resolver:main.default.dc1": { 2184 Type: structs.DiscoveryGraphNodeTypeResolver, 2185 Name: "main.default.dc1", 2186 Resolver: &structs.DiscoveryResolver{ 2187 Default: true, 2188 ConnectTimeout: 5 * time.Second, 2189 Target: "main.default.dc1", 2190 }, 2191 }, 2192 }, 2193 Targets: map[string]*structs.DiscoveryTarget{ 2194 // TODO-TARGET 2195 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 2196 }, 2197 } 2198 return compileTestCase{entries: entries, expect: expect, expectIsDefault: true, 2199 expectCustom: true, 2200 setup: func(req *CompileRequest) { 2201 req.OverrideProtocol = "tcp" 2202 }, 2203 } 2204} 2205 2206func testcase_Resolver_CircularRedirect() compileTestCase { 2207 entries := newEntries() 2208 entries.AddResolvers( 2209 &structs.ServiceResolverConfigEntry{ 2210 Kind: "service-resolver", 2211 Name: "main", 2212 Redirect: &structs.ServiceResolverRedirect{ 2213 Service: "other", 2214 }, 2215 }, 2216 &structs.ServiceResolverConfigEntry{ 2217 Kind: "service-resolver", 2218 Name: "other", 2219 Redirect: &structs.ServiceResolverRedirect{ 2220 Service: "main", 2221 }, 2222 }, 2223 ) 2224 2225 return compileTestCase{entries: entries, 2226 expectErr: "detected circular resolver redirect", 2227 expectGraphErr: true, 2228 } 2229} 2230 2231func testcase_CircularSplit() compileTestCase { 2232 entries := newEntries() 2233 setGlobalProxyProtocol(entries, "http") 2234 entries.AddSplitters( 2235 &structs.ServiceSplitterConfigEntry{ 2236 Kind: "service-splitter", 2237 Name: "main", 2238 Splits: []structs.ServiceSplit{ 2239 {Weight: 100, Service: "other"}, 2240 }, 2241 }, 2242 &structs.ServiceSplitterConfigEntry{ 2243 Kind: "service-splitter", 2244 Name: "other", 2245 Splits: []structs.ServiceSplit{ 2246 {Weight: 100, Service: "main"}, 2247 }, 2248 }, 2249 ) 2250 2251 return compileTestCase{entries: entries, 2252 expectErr: "detected circular reference", 2253 expectGraphErr: true, 2254 } 2255} 2256 2257func testcase_LBSplitterAndResolver() compileTestCase { 2258 entries := newEntries() 2259 setServiceProtocol(entries, "foo", "http") 2260 setServiceProtocol(entries, "bar", "http") 2261 setServiceProtocol(entries, "baz", "http") 2262 2263 entries.AddSplitters( 2264 &structs.ServiceSplitterConfigEntry{ 2265 Kind: "service-splitter", 2266 Name: "main", 2267 Splits: []structs.ServiceSplit{ 2268 {Weight: 60, Service: "foo"}, 2269 {Weight: 20, Service: "bar"}, 2270 {Weight: 20, Service: "baz"}, 2271 }, 2272 }, 2273 ) 2274 2275 entries.AddResolvers( 2276 &structs.ServiceResolverConfigEntry{ 2277 Kind: "service-resolver", 2278 Name: "foo", 2279 LoadBalancer: &structs.LoadBalancer{ 2280 Policy: "least_request", 2281 LeastRequestConfig: &structs.LeastRequestConfig{ 2282 ChoiceCount: 3, 2283 }, 2284 }, 2285 }, 2286 &structs.ServiceResolverConfigEntry{ 2287 Kind: "service-resolver", 2288 Name: "bar", 2289 LoadBalancer: &structs.LoadBalancer{ 2290 Policy: "ring_hash", 2291 RingHashConfig: &structs.RingHashConfig{ 2292 MaximumRingSize: 101, 2293 }, 2294 HashPolicies: []structs.HashPolicy{ 2295 { 2296 SourceIP: true, 2297 }, 2298 }, 2299 }, 2300 }, 2301 &structs.ServiceResolverConfigEntry{ 2302 Kind: "service-resolver", 2303 Name: "baz", 2304 LoadBalancer: &structs.LoadBalancer{ 2305 Policy: "maglev", 2306 HashPolicies: []structs.HashPolicy{ 2307 { 2308 Field: "cookie", 2309 FieldValue: "chocolate-chip", 2310 CookieConfig: &structs.CookieConfig{ 2311 TTL: 2 * time.Minute, 2312 Path: "/bowl", 2313 }, 2314 Terminal: true, 2315 }, 2316 }, 2317 }, 2318 }, 2319 ) 2320 2321 expect := &structs.CompiledDiscoveryChain{ 2322 Protocol: "http", 2323 StartNode: "splitter:main.default", 2324 Nodes: map[string]*structs.DiscoveryGraphNode{ 2325 "splitter:main.default": { 2326 Type: structs.DiscoveryGraphNodeTypeSplitter, 2327 Name: "main.default", 2328 Splits: []*structs.DiscoverySplit{ 2329 { 2330 Weight: 60, 2331 NextNode: "resolver:foo.default.dc1", 2332 }, 2333 { 2334 Weight: 20, 2335 NextNode: "resolver:bar.default.dc1", 2336 }, 2337 { 2338 Weight: 20, 2339 NextNode: "resolver:baz.default.dc1", 2340 }, 2341 }, 2342 // The LB config from bar is attached because splitters only care about hash-based policies, 2343 // and it's the config from bar not baz because we pick the first one we encounter in the Splits. 2344 LoadBalancer: &structs.LoadBalancer{ 2345 Policy: "ring_hash", 2346 RingHashConfig: &structs.RingHashConfig{ 2347 MaximumRingSize: 101, 2348 }, 2349 HashPolicies: []structs.HashPolicy{ 2350 { 2351 SourceIP: true, 2352 }, 2353 }, 2354 }, 2355 }, 2356 // Each service's LB config is passed down from the service-resolver to the resolver node 2357 "resolver:foo.default.dc1": { 2358 Type: structs.DiscoveryGraphNodeTypeResolver, 2359 Name: "foo.default.dc1", 2360 Resolver: &structs.DiscoveryResolver{ 2361 Default: false, 2362 ConnectTimeout: 5 * time.Second, 2363 Target: "foo.default.dc1", 2364 }, 2365 LoadBalancer: &structs.LoadBalancer{ 2366 Policy: "least_request", 2367 LeastRequestConfig: &structs.LeastRequestConfig{ 2368 ChoiceCount: 3, 2369 }, 2370 }, 2371 }, 2372 "resolver:bar.default.dc1": { 2373 Type: structs.DiscoveryGraphNodeTypeResolver, 2374 Name: "bar.default.dc1", 2375 Resolver: &structs.DiscoveryResolver{ 2376 Default: false, 2377 ConnectTimeout: 5 * time.Second, 2378 Target: "bar.default.dc1", 2379 }, 2380 LoadBalancer: &structs.LoadBalancer{ 2381 Policy: "ring_hash", 2382 RingHashConfig: &structs.RingHashConfig{ 2383 MaximumRingSize: 101, 2384 }, 2385 HashPolicies: []structs.HashPolicy{ 2386 { 2387 SourceIP: true, 2388 }, 2389 }, 2390 }, 2391 }, 2392 "resolver:baz.default.dc1": { 2393 Type: structs.DiscoveryGraphNodeTypeResolver, 2394 Name: "baz.default.dc1", 2395 Resolver: &structs.DiscoveryResolver{ 2396 Default: false, 2397 ConnectTimeout: 5 * time.Second, 2398 Target: "baz.default.dc1", 2399 }, 2400 LoadBalancer: &structs.LoadBalancer{ 2401 Policy: "maglev", 2402 HashPolicies: []structs.HashPolicy{ 2403 { 2404 Field: "cookie", 2405 FieldValue: "chocolate-chip", 2406 CookieConfig: &structs.CookieConfig{ 2407 TTL: 2 * time.Minute, 2408 Path: "/bowl", 2409 }, 2410 Terminal: true, 2411 }, 2412 }, 2413 }, 2414 }, 2415 }, 2416 Targets: map[string]*structs.DiscoveryTarget{ 2417 "foo.default.dc1": newTarget("foo", "", "default", "dc1", nil), 2418 "bar.default.dc1": newTarget("bar", "", "default", "dc1", nil), 2419 "baz.default.dc1": newTarget("baz", "", "default", "dc1", nil), 2420 }, 2421 } 2422 2423 return compileTestCase{entries: entries, expect: expect} 2424} 2425 2426// ensure chain with LB cfg in resolver isn't a default chain (!IsDefault) 2427func testcase_LBResolver() compileTestCase { 2428 entries := newEntries() 2429 setServiceProtocol(entries, "main", "http") 2430 2431 entries.AddResolvers( 2432 &structs.ServiceResolverConfigEntry{ 2433 Kind: "service-resolver", 2434 Name: "main", 2435 LoadBalancer: &structs.LoadBalancer{ 2436 Policy: "ring_hash", 2437 RingHashConfig: &structs.RingHashConfig{ 2438 MaximumRingSize: 101, 2439 }, 2440 HashPolicies: []structs.HashPolicy{ 2441 { 2442 SourceIP: true, 2443 }, 2444 }, 2445 }, 2446 }, 2447 ) 2448 2449 expect := &structs.CompiledDiscoveryChain{ 2450 Protocol: "http", 2451 StartNode: "resolver:main.default.dc1", 2452 Nodes: map[string]*structs.DiscoveryGraphNode{ 2453 "resolver:main.default.dc1": { 2454 Type: structs.DiscoveryGraphNodeTypeResolver, 2455 Name: "main.default.dc1", 2456 Resolver: &structs.DiscoveryResolver{ 2457 Default: false, 2458 ConnectTimeout: 5 * time.Second, 2459 Target: "main.default.dc1", 2460 }, 2461 LoadBalancer: &structs.LoadBalancer{ 2462 Policy: "ring_hash", 2463 RingHashConfig: &structs.RingHashConfig{ 2464 MaximumRingSize: 101, 2465 }, 2466 HashPolicies: []structs.HashPolicy{ 2467 { 2468 SourceIP: true, 2469 }, 2470 }, 2471 }, 2472 }, 2473 }, 2474 Targets: map[string]*structs.DiscoveryTarget{ 2475 "main.default.dc1": newTarget("main", "", "default", "dc1", nil), 2476 }, 2477 } 2478 2479 return compileTestCase{entries: entries, expect: expect} 2480} 2481 2482func newSimpleRoute(name string, muts ...func(*structs.ServiceRoute)) structs.ServiceRoute { 2483 r := structs.ServiceRoute{ 2484 Match: &structs.ServiceRouteMatch{ 2485 HTTP: &structs.ServiceRouteHTTPMatch{PathPrefix: "/" + name}, 2486 }, 2487 Destination: &structs.ServiceRouteDestination{Service: name}, 2488 } 2489 2490 for _, mut := range muts { 2491 mut(&r) 2492 } 2493 2494 return r 2495} 2496 2497func setGlobalProxyProtocol(entries *structs.DiscoveryChainConfigEntries, protocol string) { 2498 entries.GlobalProxy = &structs.ProxyConfigEntry{ 2499 Kind: structs.ProxyDefaults, 2500 Name: structs.ProxyConfigGlobal, 2501 Config: map[string]interface{}{ 2502 "protocol": protocol, 2503 }, 2504 } 2505} 2506 2507func setServiceProtocol(entries *structs.DiscoveryChainConfigEntries, name, protocol string) { 2508 entries.AddServices(&structs.ServiceConfigEntry{ 2509 Kind: structs.ServiceDefaults, 2510 Name: name, 2511 Protocol: protocol, 2512 }) 2513} 2514 2515func newEntries() *structs.DiscoveryChainConfigEntries { 2516 return &structs.DiscoveryChainConfigEntries{ 2517 Routers: make(map[structs.ServiceID]*structs.ServiceRouterConfigEntry), 2518 Splitters: make(map[structs.ServiceID]*structs.ServiceSplitterConfigEntry), 2519 Resolvers: make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry), 2520 } 2521} 2522 2523func newTarget(service, serviceSubset, namespace, datacenter string, modFn func(t *structs.DiscoveryTarget)) *structs.DiscoveryTarget { 2524 t := structs.NewDiscoveryTarget(service, serviceSubset, namespace, datacenter) 2525 t.SNI = connect.TargetSNI(t, "trustdomain.consul") 2526 t.Name = t.SNI 2527 if modFn != nil { 2528 modFn(t) 2529 } 2530 return t 2531} 2532