1package discoverychain 2 3import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/mitchellh/hashstructure" 9 "github.com/mitchellh/mapstructure" 10 11 "github.com/hashicorp/consul/agent/connect" 12 "github.com/hashicorp/consul/agent/structs" 13) 14 15type CompileRequest struct { 16 ServiceName string 17 EvaluateInNamespace string 18 EvaluateInDatacenter string 19 EvaluateInTrustDomain string 20 UseInDatacenter string // where the results will be used from 21 22 // OverrideMeshGateway allows for the setting to be overridden for any 23 // resolver in the compiled chain. 24 OverrideMeshGateway structs.MeshGatewayConfig 25 26 // OverrideProtocol allows for the final protocol for the chain to be 27 // altered. 28 // 29 // - If the chain ordinarily would be TCP and an L7 protocol is passed here 30 // the chain will not include Routers or Splitters. 31 // 32 // - If the chain ordinarily would be L7 and TCP is passed here the chain 33 // will not include Routers or Splitters. 34 OverrideProtocol string 35 36 // OverrideConnectTimeout allows for the ConnectTimeout setting to be 37 // overridden for any resolver in the compiled chain. 38 OverrideConnectTimeout time.Duration 39 40 Entries *structs.DiscoveryChainConfigEntries 41} 42 43// Compile assembles a discovery chain in the form of a graph of nodes using 44// raw config entries and local context. 45// 46// "Node" referenced in this file refers to a node in a graph and not to the 47// Consul construct called a "Node". 48// 49// Omitting router and splitter entries for services not using an L7 protocol 50// (like HTTP) happens during initial fetching, but for sanity purposes a quick 51// reinforcement of that happens here, too. 52// 53// May return a *structs.ConfigEntryGraphError, but that is only expected when 54// being used to validate modifications to the config entry graph. It should 55// not be expected when compiling existing entries at runtime that are already 56// valid. 57func Compile(req CompileRequest) (*structs.CompiledDiscoveryChain, error) { 58 var ( 59 serviceName = req.ServiceName 60 evaluateInNamespace = req.EvaluateInNamespace 61 evaluateInDatacenter = req.EvaluateInDatacenter 62 evaluateInTrustDomain = req.EvaluateInTrustDomain 63 useInDatacenter = req.UseInDatacenter 64 entries = req.Entries 65 ) 66 if serviceName == "" { 67 return nil, fmt.Errorf("serviceName is required") 68 } 69 if evaluateInNamespace == "" { 70 return nil, fmt.Errorf("evaluateInNamespace is required") 71 } 72 if evaluateInDatacenter == "" { 73 return nil, fmt.Errorf("evaluateInDatacenter is required") 74 } 75 if evaluateInTrustDomain == "" { 76 return nil, fmt.Errorf("evaluateInTrustDomain is required") 77 } 78 if useInDatacenter == "" { 79 return nil, fmt.Errorf("useInDatacenter is required") 80 } 81 if entries == nil { 82 return nil, fmt.Errorf("entries is required") 83 } 84 85 c := &compiler{ 86 serviceName: serviceName, 87 evaluateInNamespace: evaluateInNamespace, 88 evaluateInDatacenter: evaluateInDatacenter, 89 evaluateInTrustDomain: evaluateInTrustDomain, 90 useInDatacenter: useInDatacenter, 91 overrideMeshGateway: req.OverrideMeshGateway, 92 overrideProtocol: req.OverrideProtocol, 93 overrideConnectTimeout: req.OverrideConnectTimeout, 94 entries: entries, 95 96 resolvers: make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry), 97 splitterNodes: make(map[string]*structs.DiscoveryGraphNode), 98 resolveNodes: make(map[string]*structs.DiscoveryGraphNode), 99 100 nodes: make(map[string]*structs.DiscoveryGraphNode), 101 loadedTargets: make(map[string]*structs.DiscoveryTarget), 102 retainedTargets: make(map[string]struct{}), 103 } 104 105 if req.OverrideProtocol != "" { 106 c.disableAdvancedRoutingFeatures = !enableAdvancedRoutingForProtocol(req.OverrideProtocol) 107 } 108 109 // Clone this resolver map to avoid mutating the input map during compilation. 110 if len(entries.Resolvers) > 0 { 111 for k, v := range entries.Resolvers { 112 c.resolvers[k] = v 113 } 114 } 115 116 return c.compile() 117} 118 119// compiler is a single-use struct for handling intermediate state necessary 120// for assembling a discovery chain from raw config entries. 121type compiler struct { 122 serviceName string 123 evaluateInNamespace string 124 evaluateInDatacenter string 125 evaluateInTrustDomain string 126 useInDatacenter string 127 overrideMeshGateway structs.MeshGatewayConfig 128 overrideProtocol string 129 overrideConnectTimeout time.Duration 130 131 // config entries that are being compiled (will be mutated during compilation) 132 // 133 // This is an INPUT field. 134 entries *structs.DiscoveryChainConfigEntries 135 136 // resolvers is initially seeded by copying the provided entries.Resolvers 137 // map and default resolvers are added as they are needed. 138 resolvers map[structs.ServiceID]*structs.ServiceResolverConfigEntry 139 140 // cached nodes 141 splitterNodes map[string]*structs.DiscoveryGraphNode 142 resolveNodes map[string]*structs.DiscoveryGraphNode 143 144 // usesAdvancedRoutingFeatures is set to true if config entries for routing 145 // or splitting appear in the compiled chain 146 usesAdvancedRoutingFeatures bool 147 148 // disableAdvancedRoutingFeatures is set to true if overrideProtocol is set to tcp 149 disableAdvancedRoutingFeatures bool 150 151 // customizedBy indicates which override values customized how the 152 // compilation behaved. 153 // 154 // This is an OUTPUT field. 155 customizedBy customizationMarkers 156 157 // protocol is the common protocol used for all referenced services. These 158 // cannot be mixed. 159 // 160 // This is an OUTPUT field. 161 protocol string 162 163 // startNode is computed inside of assembleChain() 164 // 165 // This is an OUTPUT field. 166 startNode string 167 168 // nodes is computed inside of compile() 169 // 170 // This is an OUTPUT field. 171 nodes map[string]*structs.DiscoveryGraphNode 172 173 // This is an OUTPUT field. 174 loadedTargets map[string]*structs.DiscoveryTarget 175 retainedTargets map[string]struct{} 176} 177 178type customizationMarkers struct { 179 MeshGateway bool 180 Protocol bool 181 ConnectTimeout bool 182} 183 184// serviceIDString deviates from the standard formatting you would get with 185// the String() method on the type itself. It is this way to be more 186// consistent with other string ids within the discovery chain. 187func serviceIDString(sid structs.ServiceID) string { 188 return fmt.Sprintf("%s.%s", sid.ID, sid.NamespaceOrDefault()) 189} 190 191func (m *customizationMarkers) IsZero() bool { 192 return !m.MeshGateway && !m.Protocol && !m.ConnectTimeout 193} 194 195// recordNode stores the node internally in the compiled chain. 196func (c *compiler) recordNode(node *structs.DiscoveryGraphNode) { 197 // Some types have their own type-specific lookups, so record those, too. 198 switch node.Type { 199 case structs.DiscoveryGraphNodeTypeRouter: 200 // no special storage 201 case structs.DiscoveryGraphNodeTypeSplitter: 202 c.splitterNodes[node.Name] = node 203 case structs.DiscoveryGraphNodeTypeResolver: 204 c.resolveNodes[node.Resolver.Target] = node 205 default: 206 panic("unknown node type '" + node.Type + "'") 207 } 208 209 c.nodes[node.MapKey()] = node 210} 211 212func (c *compiler) recordServiceProtocol(sid structs.ServiceID) error { 213 if serviceDefault := c.entries.GetService(sid); serviceDefault != nil { 214 return c.recordProtocol(sid, serviceDefault.Protocol) 215 } 216 if c.entries.GlobalProxy != nil { 217 var cfg proxyConfig 218 // Ignore errors and fallback on defaults if it does happen. 219 _ = mapstructure.WeakDecode(c.entries.GlobalProxy.Config, &cfg) 220 if cfg.Protocol != "" { 221 return c.recordProtocol(sid, cfg.Protocol) 222 } 223 } 224 return c.recordProtocol(sid, "") 225} 226 227// proxyConfig is a snippet from agent/xds/config.go:ProxyConfig 228type proxyConfig struct { 229 Protocol string `mapstructure:"protocol"` 230} 231 232func (c *compiler) recordProtocol(fromService structs.ServiceID, protocol string) error { 233 if protocol == "" { 234 protocol = "tcp" 235 } else { 236 protocol = strings.ToLower(protocol) 237 } 238 239 if c.protocol == "" { 240 c.protocol = protocol 241 } else if c.protocol != protocol { 242 return &structs.ConfigEntryGraphError{ 243 Message: fmt.Sprintf( 244 "discovery chain %q uses inconsistent protocols; service %q has %q which is not %q", 245 c.serviceName, fromService.String(), protocol, c.protocol, 246 ), 247 } 248 } 249 250 return nil 251} 252 253func (c *compiler) compile() (*structs.CompiledDiscoveryChain, error) { 254 if err := c.assembleChain(); err != nil { 255 return nil, err 256 } 257 258 // We don't need these intermediates anymore. 259 c.splitterNodes = nil 260 c.resolveNodes = nil 261 262 if c.startNode == "" { 263 panic("impossible to return no results") 264 } 265 266 if err := c.detectCircularReferences(); err != nil { 267 return nil, err 268 } 269 270 c.flattenAdjacentSplitterNodes() 271 272 if err := c.removeUnusedNodes(); err != nil { 273 return nil, err 274 } 275 276 for targetID := range c.loadedTargets { 277 if _, ok := c.retainedTargets[targetID]; !ok { 278 delete(c.loadedTargets, targetID) 279 } 280 } 281 282 if !enableAdvancedRoutingForProtocol(c.protocol) && c.usesAdvancedRoutingFeatures { 283 return nil, &structs.ConfigEntryGraphError{ 284 Message: fmt.Sprintf( 285 "discovery chain %q uses a protocol %q that does not permit advanced routing or splitting behavior", 286 c.serviceName, c.protocol, 287 ), 288 } 289 } 290 291 if c.overrideProtocol != "" { 292 if c.overrideProtocol != c.protocol { 293 c.protocol = c.overrideProtocol 294 c.customizedBy.Protocol = true 295 } 296 } 297 298 var customizationHash string 299 if !c.customizedBy.IsZero() { 300 var customization struct { 301 OverrideMeshGateway structs.MeshGatewayConfig 302 OverrideProtocol string 303 OverrideConnectTimeout time.Duration 304 } 305 306 if c.customizedBy.MeshGateway { 307 customization.OverrideMeshGateway = c.overrideMeshGateway 308 } 309 if c.customizedBy.Protocol { 310 customization.OverrideProtocol = c.overrideProtocol 311 } 312 if c.customizedBy.ConnectTimeout { 313 customization.OverrideConnectTimeout = c.overrideConnectTimeout 314 } 315 v, err := hashstructure.Hash(customization, nil) 316 if err != nil { 317 return nil, fmt.Errorf("cannot create customization hash key: %v", err) 318 } 319 customizationHash = fmt.Sprintf("%x", v)[0:8] 320 } 321 322 return &structs.CompiledDiscoveryChain{ 323 ServiceName: c.serviceName, 324 Namespace: c.evaluateInNamespace, 325 Datacenter: c.evaluateInDatacenter, 326 CustomizationHash: customizationHash, 327 Protocol: c.protocol, 328 StartNode: c.startNode, 329 Nodes: c.nodes, 330 Targets: c.loadedTargets, 331 }, nil 332} 333 334func (c *compiler) detectCircularReferences() error { 335 var ( 336 todo stringStack 337 visited = make(map[string]struct{}) 338 visitChain stringStack 339 ) 340 341 todo.Push(c.startNode) 342 for { 343 current, ok := todo.Pop() 344 if !ok { 345 break 346 } 347 if current == "_popvisit" { 348 if v, ok := visitChain.Pop(); ok { 349 delete(visited, v) 350 } 351 continue 352 } 353 visitChain.Push(current) 354 355 if _, ok := visited[current]; ok { 356 return &structs.ConfigEntryGraphError{ 357 Message: fmt.Sprintf( 358 "detected circular reference: [%s]", 359 strings.Join(visitChain.Items(), " -> "), 360 ), 361 } 362 } 363 visited[current] = struct{}{} 364 365 todo.Push("_popvisit") 366 367 node := c.nodes[current] 368 369 switch node.Type { 370 case structs.DiscoveryGraphNodeTypeRouter: 371 for _, route := range node.Routes { 372 todo.Push(route.NextNode) 373 } 374 case structs.DiscoveryGraphNodeTypeSplitter: 375 for _, split := range node.Splits { 376 todo.Push(split.NextNode) 377 } 378 case structs.DiscoveryGraphNodeTypeResolver: 379 // Circular redirects are detected elsewhere and failover isn't 380 // recursive so there's nothing more to do here. 381 default: 382 return fmt.Errorf("unexpected graph node type: %s", node.Type) 383 } 384 } 385 386 return nil 387} 388 389func (c *compiler) flattenAdjacentSplitterNodes() { 390 for { 391 anyChanged := false 392 for _, node := range c.nodes { 393 if node.Type != structs.DiscoveryGraphNodeTypeSplitter { 394 continue 395 } 396 397 fixedSplits := make([]*structs.DiscoverySplit, 0, len(node.Splits)) 398 changed := false 399 for _, split := range node.Splits { 400 nextNode := c.nodes[split.NextNode] 401 if nextNode.Type != structs.DiscoveryGraphNodeTypeSplitter { 402 fixedSplits = append(fixedSplits, split) 403 continue 404 } 405 406 changed = true 407 408 for _, innerSplit := range nextNode.Splits { 409 effectiveWeight := split.Weight * innerSplit.Weight / 100 410 411 newDiscoverySplit := &structs.DiscoverySplit{ 412 Weight: structs.NormalizeServiceSplitWeight(effectiveWeight), 413 NextNode: innerSplit.NextNode, 414 } 415 416 fixedSplits = append(fixedSplits, newDiscoverySplit) 417 } 418 } 419 420 if changed { 421 node.Splits = fixedSplits 422 anyChanged = true 423 } 424 } 425 426 if !anyChanged { 427 return 428 } 429 } 430} 431 432// removeUnusedNodes walks the chain from the start and prunes any nodes that 433// are no longer referenced. This can happen as a result of operations like 434// flattenAdjacentSplitterNodes(). 435func (c *compiler) removeUnusedNodes() error { 436 var ( 437 visited = make(map[string]struct{}) 438 todo = make(map[string]struct{}) 439 ) 440 441 todo[c.startNode] = struct{}{} 442 443 getNext := func() string { 444 if len(todo) == 0 { 445 return "" 446 } 447 for k := range todo { 448 delete(todo, k) 449 return k 450 } 451 return "" 452 } 453 454 for { 455 next := getNext() 456 if next == "" { 457 break 458 } 459 if _, ok := visited[next]; ok { 460 continue 461 } 462 visited[next] = struct{}{} 463 464 node := c.nodes[next] 465 if node == nil { 466 return fmt.Errorf("compilation references non-retained node %q", next) 467 } 468 469 switch node.Type { 470 case structs.DiscoveryGraphNodeTypeRouter: 471 for _, route := range node.Routes { 472 todo[route.NextNode] = struct{}{} 473 } 474 case structs.DiscoveryGraphNodeTypeSplitter: 475 for _, split := range node.Splits { 476 todo[split.NextNode] = struct{}{} 477 } 478 case structs.DiscoveryGraphNodeTypeResolver: 479 // nothing special 480 default: 481 return fmt.Errorf("unknown node type %q", node.Type) 482 } 483 } 484 485 if len(visited) == len(c.nodes) { 486 return nil 487 } 488 489 for name := range c.nodes { 490 if _, ok := visited[name]; !ok { 491 delete(c.nodes, name) 492 } 493 } 494 495 return nil 496} 497 498// assembleChain will do the initial assembly of a chain of DiscoveryGraphNode 499// entries from the provided config entries. 500func (c *compiler) assembleChain() error { 501 if c.startNode != "" || len(c.nodes) > 0 { 502 return fmt.Errorf("assembleChain should only be called once") 503 } 504 505 sid := structs.NewServiceID(c.serviceName, c.GetEnterpriseMeta()) 506 507 // Check for short circuit path. 508 if len(c.resolvers) == 0 && c.entries.IsChainEmpty() { 509 // Materialize defaults and cache. 510 c.resolvers[sid] = newDefaultServiceResolver(sid) 511 } 512 513 // The only router we consult is the one for the service name at the top of 514 // the chain. 515 router := c.entries.GetRouter(sid) 516 if router != nil && c.disableAdvancedRoutingFeatures { 517 router = nil 518 c.customizedBy.Protocol = true 519 } 520 521 if router == nil { 522 // If no router is configured, move on down the line to the next hop of 523 // the chain. 524 node, err := c.getSplitterOrResolverNode(c.newTarget(c.serviceName, "", "", "")) 525 if err != nil { 526 return err 527 } 528 529 c.startNode = node.MapKey() 530 return nil 531 } 532 533 routerID := structs.NewServiceID(router.Name, router.GetEnterpriseMeta()) 534 535 routeNode := &structs.DiscoveryGraphNode{ 536 Type: structs.DiscoveryGraphNodeTypeRouter, 537 Name: serviceIDString(routerID), 538 Routes: make([]*structs.DiscoveryRoute, 0, len(router.Routes)+1), 539 } 540 c.usesAdvancedRoutingFeatures = true 541 if err := c.recordServiceProtocol(routerID); err != nil { 542 return err 543 } 544 545 for i := range router.Routes { 546 // We don't use range variables here because we'll take the address of 547 // this route and store that in a DiscoveryGraphNode and the range 548 // variables share memory addresses between iterations which is exactly 549 // wrong for us here. 550 route := router.Routes[i] 551 552 compiledRoute := &structs.DiscoveryRoute{Definition: &route} 553 routeNode.Routes = append(routeNode.Routes, compiledRoute) 554 555 dest := route.Destination 556 if dest == nil { 557 dest = &structs.ServiceRouteDestination{ 558 Service: c.serviceName, 559 Namespace: router.NamespaceOrDefault(), 560 } 561 } 562 svc := defaultIfEmpty(dest.Service, c.serviceName) 563 destNamespace := defaultIfEmpty(dest.Namespace, router.NamespaceOrDefault()) 564 565 // Check to see if the destination is eligible for splitting. 566 var ( 567 node *structs.DiscoveryGraphNode 568 err error 569 ) 570 if dest.ServiceSubset == "" { 571 node, err = c.getSplitterOrResolverNode( 572 c.newTarget(svc, "", destNamespace, ""), 573 ) 574 } else { 575 node, err = c.getResolverNode( 576 c.newTarget(svc, dest.ServiceSubset, destNamespace, ""), 577 false, 578 ) 579 } 580 if err != nil { 581 return err 582 } 583 compiledRoute.NextNode = node.MapKey() 584 } 585 586 // If we have a router, we'll add a catch-all route at the end to send 587 // unmatched traffic to the next hop in the chain. 588 defaultDestinationNode, err := c.getSplitterOrResolverNode(c.newTarget(router.Name, "", router.NamespaceOrDefault(), "")) 589 if err != nil { 590 return err 591 } 592 593 defaultRoute := &structs.DiscoveryRoute{ 594 Definition: newDefaultServiceRoute(router.Name, router.NamespaceOrDefault()), 595 NextNode: defaultDestinationNode.MapKey(), 596 } 597 routeNode.Routes = append(routeNode.Routes, defaultRoute) 598 599 c.startNode = routeNode.MapKey() 600 c.recordNode(routeNode) 601 602 return nil 603} 604 605func newDefaultServiceRoute(serviceName string, namespace string) *structs.ServiceRoute { 606 return &structs.ServiceRoute{ 607 Match: &structs.ServiceRouteMatch{ 608 HTTP: &structs.ServiceRouteHTTPMatch{ 609 PathPrefix: "/", 610 }, 611 }, 612 Destination: &structs.ServiceRouteDestination{ 613 Service: serviceName, 614 Namespace: namespace, 615 }, 616 } 617} 618 619func (c *compiler) newTarget(service, serviceSubset, namespace, datacenter string) *structs.DiscoveryTarget { 620 if service == "" { 621 panic("newTarget called with empty service which makes no sense") 622 } 623 624 t := structs.NewDiscoveryTarget( 625 service, 626 serviceSubset, 627 defaultIfEmpty(namespace, c.evaluateInNamespace), 628 defaultIfEmpty(datacenter, c.evaluateInDatacenter), 629 ) 630 631 // Set default connect SNI. This will be overridden later if the service 632 // has an explicit SNI value configured in service-defaults. 633 t.SNI = connect.TargetSNI(t, c.evaluateInTrustDomain) 634 635 // Use the same representation for the name. This will NOT be overridden 636 // later. 637 t.Name = t.SNI 638 639 prev, ok := c.loadedTargets[t.ID] 640 if ok { 641 return prev 642 } 643 c.loadedTargets[t.ID] = t 644 return t 645} 646 647func (c *compiler) rewriteTarget(t *structs.DiscoveryTarget, service, serviceSubset, namespace, datacenter string) *structs.DiscoveryTarget { 648 var ( 649 service2 = t.Service 650 serviceSubset2 = t.ServiceSubset 651 namespace2 = t.Namespace 652 datacenter2 = t.Datacenter 653 ) 654 655 if service != "" && service != service2 { 656 service2 = service 657 // Reset the chosen subset if we reference a service other than our own. 658 serviceSubset2 = "" 659 } 660 if serviceSubset != "" { 661 serviceSubset2 = serviceSubset 662 } 663 if namespace != "" { 664 namespace2 = namespace 665 } 666 if datacenter != "" { 667 datacenter2 = datacenter 668 } 669 670 return c.newTarget(service2, serviceSubset2, namespace2, datacenter2) 671} 672 673func (c *compiler) getSplitterOrResolverNode(target *structs.DiscoveryTarget) (*structs.DiscoveryGraphNode, error) { 674 nextNode, err := c.getSplitterNode(target.ServiceID()) 675 if err != nil { 676 return nil, err 677 } else if nextNode != nil { 678 return nextNode, nil 679 } 680 return c.getResolverNode(target, false) 681} 682 683func (c *compiler) getSplitterNode(sid structs.ServiceID) (*structs.DiscoveryGraphNode, error) { 684 name := serviceIDString(sid) 685 // Do we already have the node? 686 if prev, ok := c.splitterNodes[name]; ok { 687 return prev, nil 688 } 689 690 // Fetch the config entry. 691 splitter := c.entries.GetSplitter(sid) 692 if splitter != nil && c.disableAdvancedRoutingFeatures { 693 splitter = nil 694 c.customizedBy.Protocol = true 695 } 696 if splitter == nil { 697 return nil, nil 698 } 699 700 // Build node. 701 splitNode := &structs.DiscoveryGraphNode{ 702 Type: structs.DiscoveryGraphNodeTypeSplitter, 703 Name: name, 704 Splits: make([]*structs.DiscoverySplit, 0, len(splitter.Splits)), 705 } 706 707 // If we record this exists before recursing down it will short-circuit 708 // sanely if there is some sort of graph loop below. 709 c.recordNode(splitNode) 710 711 var hasLB bool 712 for _, split := range splitter.Splits { 713 compiledSplit := &structs.DiscoverySplit{ 714 Weight: split.Weight, 715 } 716 splitNode.Splits = append(splitNode.Splits, compiledSplit) 717 718 svc := defaultIfEmpty(split.Service, sid.ID) 719 splitID := structs.ServiceID{ 720 ID: svc, 721 EnterpriseMeta: *split.GetEnterpriseMeta(&sid.EnterpriseMeta), 722 } 723 724 // Check to see if the split is eligible for additional splitting. 725 if !splitID.Matches(sid) && split.ServiceSubset == "" { 726 nextNode, err := c.getSplitterNode(splitID) 727 if err != nil { 728 return nil, err 729 } else if nextNode != nil { 730 compiledSplit.NextNode = nextNode.MapKey() 731 continue 732 } 733 // fall through to group-resolver 734 } 735 736 node, err := c.getResolverNode( 737 c.newTarget(splitID.ID, split.ServiceSubset, splitID.NamespaceOrDefault(), ""), 738 false, 739 ) 740 if err != nil { 741 return nil, err 742 } 743 compiledSplit.NextNode = node.MapKey() 744 745 // There exists the possibility that a splitter may split between two distinct service names 746 // with distinct hash-based load balancer configs specified in their service resolvers. 747 // We cannot apply multiple hash policies to a splitter node's route action. 748 // Therefore, we attach the first hash-based load balancer config we encounter. 749 if !hasLB { 750 if lb := node.LoadBalancer; lb != nil && lb.IsHashBased() { 751 splitNode.LoadBalancer = node.LoadBalancer 752 hasLB = true 753 } 754 } 755 } 756 757 c.usesAdvancedRoutingFeatures = true 758 return splitNode, nil 759} 760 761// getResolverNode handles most of the code to handle redirection/rewriting 762// capabilities from a resolver config entry. It recurses into itself to 763// _generate_ targets used for failover out of convenience. 764func (c *compiler) getResolverNode(target *structs.DiscoveryTarget, recursedForFailover bool) (*structs.DiscoveryGraphNode, error) { 765 var ( 766 // State to help detect redirect cycles and print helpful error 767 // messages. 768 redirectHistory = make(map[string]struct{}) 769 redirectOrder []string 770 ) 771 772RESOLVE_AGAIN: 773 // Do we already have the node? 774 if prev, ok := c.resolveNodes[target.ID]; ok { 775 return prev, nil 776 } 777 778 targetID := target.ServiceID() 779 780 if err := c.recordServiceProtocol(targetID); err != nil { 781 return nil, err 782 } 783 784 // Fetch the config entry. 785 resolver, ok := c.resolvers[targetID] 786 if !ok { 787 // Materialize defaults and cache. 788 resolver = newDefaultServiceResolver(targetID) 789 c.resolvers[targetID] = resolver 790 } 791 792 if _, ok := redirectHistory[target.ID]; ok { 793 redirectOrder = append(redirectOrder, target.ID) 794 795 return nil, &structs.ConfigEntryGraphError{ 796 Message: fmt.Sprintf( 797 "detected circular resolver redirect: [%s]", 798 strings.Join(redirectOrder, " -> "), 799 ), 800 } 801 } 802 redirectHistory[target.ID] = struct{}{} 803 redirectOrder = append(redirectOrder, target.ID) 804 805 // Handle redirects right up front. 806 // 807 // TODO(rb): What about a redirected subset reference? (web/v2, but web redirects to alt/"") 808 if resolver.Redirect != nil { 809 redirect := resolver.Redirect 810 811 redirectedTarget := c.rewriteTarget( 812 target, 813 redirect.Service, 814 redirect.ServiceSubset, 815 redirect.Namespace, 816 redirect.Datacenter, 817 ) 818 if redirectedTarget.ID != target.ID { 819 target = redirectedTarget 820 goto RESOLVE_AGAIN 821 } 822 } 823 824 // Handle default subset. 825 if target.ServiceSubset == "" && resolver.DefaultSubset != "" { 826 target = c.rewriteTarget( 827 target, 828 "", 829 resolver.DefaultSubset, 830 "", 831 "", 832 ) 833 goto RESOLVE_AGAIN 834 } 835 836 if target.ServiceSubset != "" && !resolver.SubsetExists(target.ServiceSubset) { 837 return nil, &structs.ConfigEntryGraphError{ 838 Message: fmt.Sprintf( 839 "service %q does not have a subset named %q", 840 target.Service, 841 target.ServiceSubset, 842 ), 843 } 844 } 845 846 connectTimeout := resolver.ConnectTimeout 847 if connectTimeout < 1 { 848 connectTimeout = 5 * time.Second 849 } 850 851 if c.overrideConnectTimeout > 0 { 852 if connectTimeout != c.overrideConnectTimeout { 853 connectTimeout = c.overrideConnectTimeout 854 c.customizedBy.ConnectTimeout = true 855 } 856 } 857 858 // Build node. 859 node := &structs.DiscoveryGraphNode{ 860 Type: structs.DiscoveryGraphNodeTypeResolver, 861 Name: target.ID, 862 Resolver: &structs.DiscoveryResolver{ 863 Default: resolver.IsDefault(), 864 Target: target.ID, 865 ConnectTimeout: connectTimeout, 866 }, 867 LoadBalancer: resolver.LoadBalancer, 868 } 869 870 target.Subset = resolver.Subsets[target.ServiceSubset] 871 872 if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil && serviceDefault.ExternalSNI != "" { 873 // Override the default SNI value. 874 target.SNI = serviceDefault.ExternalSNI 875 target.External = true 876 } 877 878 // If using external SNI the service is fundamentally external. 879 if target.External { 880 if resolver.Redirect != nil { 881 return nil, &structs.ConfigEntryGraphError{ 882 Message: fmt.Sprintf( 883 "service %q has an external SNI set; cannot define redirects for external services", 884 target.Service, 885 ), 886 } 887 } 888 if len(resolver.Subsets) > 0 { 889 return nil, &structs.ConfigEntryGraphError{ 890 Message: fmt.Sprintf( 891 "service %q has an external SNI set; cannot define subsets for external services", 892 target.Service, 893 ), 894 } 895 } 896 if len(resolver.Failover) > 0 { 897 return nil, &structs.ConfigEntryGraphError{ 898 Message: fmt.Sprintf( 899 "service %q has an external SNI set; cannot define failover for external services", 900 target.Service, 901 ), 902 } 903 } 904 } 905 906 // TODO (mesh-gateway)- maybe allow using a gateway within a datacenter at some point 907 if target.Datacenter == c.useInDatacenter { 908 target.MeshGateway.Mode = structs.MeshGatewayModeDefault 909 910 } else if target.External { 911 // Bypass mesh gateways if it is an external service. 912 target.MeshGateway.Mode = structs.MeshGatewayModeDefault 913 914 } else { 915 // Default mesh gateway settings 916 if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil { 917 target.MeshGateway = serviceDefault.MeshGateway 918 } 919 920 if c.entries.GlobalProxy != nil && target.MeshGateway.Mode == structs.MeshGatewayModeDefault { 921 target.MeshGateway.Mode = c.entries.GlobalProxy.MeshGateway.Mode 922 } 923 924 if c.overrideMeshGateway.Mode != structs.MeshGatewayModeDefault { 925 if target.MeshGateway.Mode != c.overrideMeshGateway.Mode { 926 target.MeshGateway.Mode = c.overrideMeshGateway.Mode 927 c.customizedBy.MeshGateway = true 928 } 929 } 930 } 931 932 // Retain this target in the final results. 933 c.retainedTargets[target.ID] = struct{}{} 934 935 if recursedForFailover { 936 // If we recursed here from ourselves in a failover context, just emit 937 // this node without caching it or even processing failover again. 938 // This is a little weird but it keeps the redirect/default-subset 939 // logic in one place. 940 return node, nil 941 } 942 943 // If we record this exists before recursing down it will short-circuit 944 // sanely if there is some sort of graph loop below. 945 c.recordNode(node) 946 947 if len(resolver.Failover) > 0 { 948 f := resolver.Failover 949 950 // Determine which failover section applies. 951 failover, ok := f[target.ServiceSubset] 952 if !ok { 953 failover, ok = f["*"] 954 } 955 956 if ok { 957 // Determine which failover definitions apply. 958 var failoverTargets []*structs.DiscoveryTarget 959 if len(failover.Datacenters) > 0 { 960 for _, dc := range failover.Datacenters { 961 // Rewrite the target as per the failover policy. 962 failoverTarget := c.rewriteTarget( 963 target, 964 failover.Service, 965 failover.ServiceSubset, 966 failover.Namespace, 967 dc, 968 ) 969 if failoverTarget.ID != target.ID { // don't failover to yourself 970 failoverTargets = append(failoverTargets, failoverTarget) 971 } 972 } 973 } else { 974 // Rewrite the target as per the failover policy. 975 failoverTarget := c.rewriteTarget( 976 target, 977 failover.Service, 978 failover.ServiceSubset, 979 failover.Namespace, 980 "", 981 ) 982 if failoverTarget.ID != target.ID { // don't failover to yourself 983 failoverTargets = append(failoverTargets, failoverTarget) 984 } 985 } 986 987 // If we filtered everything out then no point in having a failover. 988 if len(failoverTargets) > 0 { 989 df := &structs.DiscoveryFailover{} 990 node.Resolver.Failover = df 991 992 // Take care of doing any redirects or configuration loading 993 // related to targets by cheating a bit and recursing into 994 // ourselves. 995 for _, target := range failoverTargets { 996 failoverResolveNode, err := c.getResolverNode(target, true) 997 if err != nil { 998 return nil, err 999 } 1000 failoverTarget := failoverResolveNode.Resolver.Target 1001 df.Targets = append(df.Targets, failoverTarget) 1002 } 1003 } 1004 } 1005 } 1006 1007 return node, nil 1008} 1009 1010func newDefaultServiceResolver(sid structs.ServiceID) *structs.ServiceResolverConfigEntry { 1011 return &structs.ServiceResolverConfigEntry{ 1012 Kind: structs.ServiceResolver, 1013 Name: sid.ID, 1014 EnterpriseMeta: sid.EnterpriseMeta, 1015 } 1016} 1017 1018func defaultIfEmpty(val, defaultVal string) string { 1019 if val != "" { 1020 return val 1021 } 1022 return defaultVal 1023} 1024 1025func enableAdvancedRoutingForProtocol(protocol string) bool { 1026 return structs.IsProtocolHTTPLike(protocol) 1027} 1028