diff --git a/agent/consul/config_endpoint.go b/agent/consul/config_endpoint.go index 1ba7a62e9e..8571be6c94 100644 --- a/agent/consul/config_endpoint.go +++ b/agent/consul/config_endpoint.go @@ -361,7 +361,6 @@ func (c *ConfigEntry) ReadDiscoveryChain(args *structs.DiscoveryChainRequest, re OverrideMeshGateway: args.OverrideMeshGateway, OverrideProtocol: args.OverrideProtocol, OverrideConnectTimeout: args.OverrideConnectTimeout, - InferDefaults: true, Entries: entries, }) if err != nil { diff --git a/agent/consul/discoverychain/compile.go b/agent/consul/discoverychain/compile.go index 1aa5382491..c3acdccd40 100644 --- a/agent/consul/discoverychain/compile.go +++ b/agent/consul/discoverychain/compile.go @@ -33,8 +33,7 @@ type CompileRequest struct { // overridden for any resolver in the compiled chain. OverrideConnectTimeout time.Duration - InferDefaults bool // TODO(rb): remove this? - Entries *structs.DiscoveryChainConfigEntries + Entries *structs.DiscoveryChainConfigEntries } // Compile assembles a discovery chain in the form of a graph of nodes using @@ -56,7 +55,6 @@ func Compile(req CompileRequest) (*structs.CompiledDiscoveryChain, error) { serviceName = req.ServiceName currentNamespace = req.CurrentNamespace currentDatacenter = req.CurrentDatacenter - inferDefaults = req.InferDefaults entries = req.Entries ) if serviceName == "" { @@ -79,15 +77,15 @@ func Compile(req CompileRequest) (*structs.CompiledDiscoveryChain, error) { overrideMeshGateway: req.OverrideMeshGateway, overrideProtocol: req.OverrideProtocol, overrideConnectTimeout: req.OverrideConnectTimeout, - inferDefaults: inferDefaults, entries: entries, - splitterNodes: make(map[string]*structs.DiscoveryGraphNode), - groupResolverNodes: make(map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode), + resolvers: make(map[string]*structs.ServiceResolverConfigEntry), + splitterNodes: make(map[string]*structs.DiscoveryGraphNode), + resolveNodes: make(map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode), - resolvers: make(map[string]*structs.ServiceResolverConfigEntry), - retainResolvers: make(map[string]struct{}), - targets: make(map[structs.DiscoveryTarget]struct{}), + nodes: make(map[string]*structs.DiscoveryGraphNode), + + targets: make(map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig), } if req.OverrideProtocol != "" { @@ -113,16 +111,19 @@ type compiler struct { overrideMeshGateway structs.MeshGatewayConfig overrideProtocol string overrideConnectTimeout time.Duration - inferDefaults bool // config entries that are being compiled (will be mutated during compilation) // // This is an INPUT field. entries *structs.DiscoveryChainConfigEntries + // resolvers is initially seeded by copying the provided entries.Resolvers + // map and default resolvers are added as they are needed. + resolvers map[string]*structs.ServiceResolverConfigEntry + // cached nodes - splitterNodes map[string]*structs.DiscoveryGraphNode - groupResolverNodes map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode // this is also an OUTPUT field + splitterNodes map[string]*structs.DiscoveryGraphNode + resolveNodes map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode // usesAdvancedRoutingFeatures is set to true if config entries for routing // or splitting appear in the compiled chain @@ -137,31 +138,24 @@ type compiler struct { // This is an OUTPUT field. customizedBy customizationMarkers - // topNode is computed inside of assembleChain() - // - // This is an OUTPUT field. - topNode *structs.DiscoveryGraphNode - // protocol is the common protocol used for all referenced services. These // cannot be mixed. // // This is an OUTPUT field. protocol string - // resolvers is initially seeded by copying the provided entries.Resolvers - // map and default resolvers are added as they are needed. - // - // If redirects cause a resolver to not be needed it will be omitted from - // this map. + // startNode is computed inside of assembleChain() // // This is an OUTPUT field. - resolvers map[string]*structs.ServiceResolverConfigEntry - // retainResolvers flags the elements of the resolvers map that should be - // retained in the final results. - retainResolvers map[string]struct{} + startNode string + + // nodes is computed inside of compile() + // + // This is an OUTPUT field. + nodes map[string]*structs.DiscoveryGraphNode // This is an OUTPUT field. - targets map[structs.DiscoveryTarget]struct{} + targets map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig } type customizationMarkers struct { @@ -174,6 +168,23 @@ func (m *customizationMarkers) IsZero() bool { return !m.MeshGateway && !m.Protocol && !m.ConnectTimeout } +// recordNode stores the node internally in the compiled chain. +func (c *compiler) recordNode(node *structs.DiscoveryGraphNode) { + // Some types have their own type-specific lookups, so record those, too. + switch node.Type { + case structs.DiscoveryGraphNodeTypeRouter: + // no special storage + case structs.DiscoveryGraphNodeTypeSplitter: + c.splitterNodes[node.ServiceName()] = node + case structs.DiscoveryGraphNodeTypeResolver: + c.resolveNodes[node.Resolver.Target] = node + default: + panic("unknown node type '" + node.Type + "'") + } + + c.nodes[node.MapKey()] = node +} + func (c *compiler) recordServiceProtocol(serviceName string) error { if serviceDefault := c.entries.GetService(serviceName); serviceDefault != nil { return c.recordProtocol(serviceName, serviceDefault.Protocol) @@ -220,11 +231,12 @@ func (c *compiler) compile() (*structs.CompiledDiscoveryChain, error) { return nil, err } - if c.topNode == nil { - if c.inferDefaults { - panic("impossible to return no results with infer defaults set to true") - } - return nil, nil + // We don't need these intermediates anymore. + c.splitterNodes = nil + c.resolveNodes = nil + + if c.startNode == "" { + panic("impossible to return no results") } if err := c.detectCircularSplits(); err != nil { @@ -236,11 +248,8 @@ func (c *compiler) compile() (*structs.CompiledDiscoveryChain, error) { c.flattenAdjacentSplitterNodes() - // Remove any unused resolvers. - for name, _ := range c.resolvers { - if _, ok := c.retainResolvers[name]; !ok { - delete(c.resolvers, name) - } + if err := c.removeUnusedNodes(); err != nil { + return nil, err } if !enableAdvancedRoutingForProtocol(c.protocol) && c.usesAdvancedRoutingFeatures { @@ -252,12 +261,6 @@ func (c *compiler) compile() (*structs.CompiledDiscoveryChain, error) { } } - targets := make([]structs.DiscoveryTarget, 0, len(c.targets)) - for target, _ := range c.targets { - targets = append(targets, target) - } - structs.DiscoveryTargets(targets).Sort() - if c.overrideProtocol != "" { if c.overrideProtocol != c.protocol { c.protocol = c.overrideProtocol @@ -290,15 +293,14 @@ func (c *compiler) compile() (*structs.CompiledDiscoveryChain, error) { } return &structs.CompiledDiscoveryChain{ - ServiceName: c.serviceName, - Namespace: c.currentNamespace, - Datacenter: c.currentDatacenter, - CustomizationHash: customizationHash, - Protocol: c.protocol, - Node: c.topNode, - Resolvers: c.resolvers, - Targets: targets, - GroupResolverNodes: c.groupResolverNodes, // TODO(rb): prune unused + ServiceName: c.serviceName, + Namespace: c.currentNamespace, + Datacenter: c.currentDatacenter, + CustomizationHash: customizationHash, + Protocol: c.protocol, + StartNode: c.startNode, + Nodes: c.nodes, + Targets: c.targets, }, nil } @@ -315,23 +317,28 @@ func (c *compiler) detectCircularResolves() error { func (c *compiler) flattenAdjacentSplitterNodes() { for { anyChanged := false - for _, splitterNode := range c.splitterNodes { - fixedSplits := make([]*structs.DiscoverySplit, 0, len(splitterNode.Splits)) + for _, node := range c.nodes { + if node.Type != structs.DiscoveryGraphNodeTypeSplitter { + continue + } + + fixedSplits := make([]*structs.DiscoverySplit, 0, len(node.Splits)) changed := false - for _, split := range splitterNode.Splits { - if split.Node.Type != structs.DiscoveryGraphNodeTypeSplitter { + for _, split := range node.Splits { + nextNode := c.nodes[split.NextNode] + if nextNode.Type != structs.DiscoveryGraphNodeTypeSplitter { fixedSplits = append(fixedSplits, split) continue } changed = true - for _, innerSplit := range split.Node.Splits { + for _, innerSplit := range nextNode.Splits { effectiveWeight := split.Weight * innerSplit.Weight / 100 newDiscoverySplit := &structs.DiscoverySplit{ - Weight: structs.NormalizeServiceSplitWeight(effectiveWeight), - Node: innerSplit.Node, + Weight: structs.NormalizeServiceSplitWeight(effectiveWeight), + NextNode: innerSplit.NextNode, } fixedSplits = append(fixedSplits, newDiscoverySplit) @@ -339,7 +346,7 @@ func (c *compiler) flattenAdjacentSplitterNodes() { } if changed { - splitterNode.Splits = fixedSplits + node.Splits = fixedSplits anyChanged = true } } @@ -350,21 +357,81 @@ func (c *compiler) flattenAdjacentSplitterNodes() { } } +// removeUnusedNodes walks the chain from the start and prunes any nodes that +// are no longer referenced. This can happen as a result of operations like +// flattenAdjacentSplitterNodes(). +func (c *compiler) removeUnusedNodes() error { + var ( + visited = make(map[string]struct{}) + todo = make(map[string]struct{}) + ) + + todo[c.startNode] = struct{}{} + + getNext := func() string { + if len(todo) == 0 { + return "" + } + for k, _ := range todo { + delete(todo, k) + return k + } + return "" + } + + for { + next := getNext() + if next == "" { + break + } + if _, ok := visited[next]; ok { + continue + } + visited[next] = struct{}{} + + node := c.nodes[next] + if node == nil { + return fmt.Errorf("compilation references non-retained node %q", next) + } + + switch node.Type { + case structs.DiscoveryGraphNodeTypeRouter: + for _, route := range node.Routes { + todo[route.NextNode] = struct{}{} + } + case structs.DiscoveryGraphNodeTypeSplitter: + for _, split := range node.Splits { + todo[split.NextNode] = struct{}{} + } + case structs.DiscoveryGraphNodeTypeResolver: + // nothing special + default: + return fmt.Errorf("unknown node type %q", node.Type) + } + } + + if len(visited) == len(c.nodes) { + return nil + } + + for name, _ := range c.nodes { + if _, ok := visited[name]; !ok { + delete(c.nodes, name) + } + } + + return nil +} + // assembleChain will do the initial assembly of a chain of DiscoveryGraphNode -// entries from the provided config entries. No default resolvers are injected -// here so it is expected that if there are no discovery chain config entries -// set up for a given service that it will produce no topNode from this. +// entries from the provided config entries. func (c *compiler) assembleChain() error { - if c.topNode != nil { + if c.startNode != "" || len(c.nodes) > 0 { return fmt.Errorf("assembleChain should only be called once") } // Check for short circuit path. if len(c.resolvers) == 0 && c.entries.IsChainEmpty() { - if !c.inferDefaults { - return nil // nothing explicitly configured - } - // Materialize defaults and cache. c.resolvers[c.serviceName] = newDefaultServiceResolver(c.serviceName) } @@ -380,12 +447,12 @@ func (c *compiler) assembleChain() error { if router == nil { // If no router is configured, move on down the line to the next hop of // the chain. - node, err := c.getSplitterOrGroupResolverNode(c.newTarget(c.serviceName, "", "", "")) + node, err := c.getSplitterOrResolverNode(c.newTarget(c.serviceName, "", "", "")) if err != nil { return err } - c.topNode = node + c.startNode = node.MapKey() return nil } @@ -419,11 +486,11 @@ func (c *compiler) assembleChain() error { err error ) if dest.ServiceSubset == "" && dest.Namespace == "" { - node, err = c.getSplitterOrGroupResolverNode( + node, err = c.getSplitterOrResolverNode( c.newTarget(svc, dest.ServiceSubset, dest.Namespace, ""), ) } else { - node, err = c.getGroupResolverNode( + node, err = c.getResolverNode( c.newTarget(svc, dest.ServiceSubset, dest.Namespace, ""), false, ) @@ -431,23 +498,25 @@ func (c *compiler) assembleChain() error { if err != nil { return err } - compiledRoute.DestinationNode = node + compiledRoute.NextNode = node.MapKey() } // If we have a router, we'll add a catch-all route at the end to send // unmatched traffic to the next hop in the chain. - defaultDestinationNode, err := c.getSplitterOrGroupResolverNode(c.newTarget(c.serviceName, "", "", "")) + defaultDestinationNode, err := c.getSplitterOrResolverNode(c.newTarget(c.serviceName, "", "", "")) if err != nil { return err } defaultRoute := &structs.DiscoveryRoute{ - Definition: newDefaultServiceRoute(c.serviceName), - DestinationNode: defaultDestinationNode, + Definition: newDefaultServiceRoute(c.serviceName), + NextNode: defaultDestinationNode.MapKey(), } routeNode.Routes = append(routeNode.Routes, defaultRoute) - c.topNode = routeNode + c.startNode = routeNode.MapKey() + c.recordNode(routeNode) + return nil } @@ -476,18 +545,17 @@ func (c *compiler) newTarget(service, serviceSubset, namespace, datacenter strin } } -func (c *compiler) getSplitterOrGroupResolverNode(target structs.DiscoveryTarget) (*structs.DiscoveryGraphNode, error) { +func (c *compiler) getSplitterOrResolverNode(target structs.DiscoveryTarget) (*structs.DiscoveryGraphNode, error) { nextNode, err := c.getSplitterNode(target.Service) if err != nil { return nil, err } else if nextNode != nil { return nextNode, nil } - return c.getGroupResolverNode(target, false) + return c.getResolverNode(target, false) } func (c *compiler) getSplitterNode(name string) (*structs.DiscoveryGraphNode, error) { - // Do we already have the node? if prev, ok := c.splitterNodes[name]; ok { return prev, nil @@ -512,7 +580,7 @@ func (c *compiler) getSplitterNode(name string) (*structs.DiscoveryGraphNode, er // If we record this exists before recursing down it will short-circuit // sanely if there is some sort of graph loop below. - c.splitterNodes[name] = splitNode + c.recordNode(splitNode) for _, split := range splitter.Splits { compiledSplit := &structs.DiscoverySplit{ @@ -527,33 +595,33 @@ func (c *compiler) getSplitterNode(name string) (*structs.DiscoveryGraphNode, er if err != nil { return nil, err } else if nextNode != nil { - compiledSplit.Node = nextNode + compiledSplit.NextNode = nextNode.MapKey() continue } // fall through to group-resolver } - node, err := c.getGroupResolverNode( + node, err := c.getResolverNode( c.newTarget(svc, split.ServiceSubset, split.Namespace, ""), false, ) if err != nil { return nil, err } - compiledSplit.Node = node + compiledSplit.NextNode = node.MapKey() } c.usesAdvancedRoutingFeatures = true return splitNode, nil } -// getGroupResolverNode handles most of the code to handle -// redirection/rewriting capabilities from a resolver config entry. It recurses -// into itself to _generate_ targets used for failover out of convenience. -func (c *compiler) getGroupResolverNode(target structs.DiscoveryTarget, recursedForFailover bool) (*structs.DiscoveryGraphNode, error) { +// getResolverNode handles most of the code to handle redirection/rewriting +// capabilities from a resolver config entry. It recurses into itself to +// _generate_ targets used for failover out of convenience. +func (c *compiler) getResolverNode(target structs.DiscoveryTarget, recursedForFailover bool) (*structs.DiscoveryGraphNode, error) { RESOLVE_AGAIN: // Do we already have the node? - if prev, ok := c.groupResolverNodes[target]; ok { + if prev, ok := c.resolveNodes[target]; ok { return prev, nil } @@ -603,9 +671,6 @@ RESOLVE_AGAIN: } } - // Since we're actually building a node with it, we can keep it. - c.retainResolvers[target.Service] = struct{}{} - connectTimeout := resolver.ConnectTimeout if connectTimeout < 1 { connectTimeout = 5 * time.Second @@ -619,48 +684,51 @@ RESOLVE_AGAIN: } // Build node. - groupResolverNode := &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: resolver.Name, - GroupResolver: &structs.DiscoveryGroupResolver{ + node := &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: target.Identifier(), + Resolver: &structs.DiscoveryResolver{ Definition: resolver, Default: resolver.IsDefault(), Target: target, ConnectTimeout: connectTimeout, }, } - groupResolver := groupResolverNode.GroupResolver - // Default mesh gateway settings - if serviceDefault := c.entries.GetService(resolver.Name); serviceDefault != nil { - groupResolver.MeshGateway = serviceDefault.MeshGateway + targetConfig := structs.DiscoveryTargetConfig{ + Subset: resolver.Subsets[target.ServiceSubset], } - if c.entries.GlobalProxy != nil && groupResolver.MeshGateway.Mode == structs.MeshGatewayModeDefault { - groupResolver.MeshGateway.Mode = c.entries.GlobalProxy.MeshGateway.Mode + // Default mesh gateway settings + if serviceDefault := c.entries.GetService(target.Service); serviceDefault != nil { + targetConfig.MeshGateway = serviceDefault.MeshGateway + } + + if c.entries.GlobalProxy != nil && targetConfig.MeshGateway.Mode == structs.MeshGatewayModeDefault { + targetConfig.MeshGateway.Mode = c.entries.GlobalProxy.MeshGateway.Mode } if c.overrideMeshGateway.Mode != structs.MeshGatewayModeDefault { - if groupResolver.MeshGateway.Mode != c.overrideMeshGateway.Mode { - groupResolver.MeshGateway.Mode = c.overrideMeshGateway.Mode + if targetConfig.MeshGateway.Mode != c.overrideMeshGateway.Mode { + targetConfig.MeshGateway.Mode = c.overrideMeshGateway.Mode c.customizedBy.MeshGateway = true } } // Retain this target even if we may not retain the group resolver. - c.targets[target] = struct{}{} + c.targets[target] = targetConfig if recursedForFailover { // If we recursed here from ourselves in a failover context, just emit // this node without caching it or even processing failover again. // This is a little weird but it keeps the redirect/default-subset // logic in one place. - return groupResolverNode, nil + return node, nil } // If we record this exists before recursing down it will short-circuit // sanely if there is some sort of graph loop below. - c.groupResolverNodes[target] = groupResolverNode + c.recordNode(node) if len(resolver.Failover) > 0 { f := resolver.Failover @@ -705,23 +773,23 @@ RESOLVE_AGAIN: df := &structs.DiscoveryFailover{ Definition: &failover, } - groupResolver.Failover = df + node.Resolver.Failover = df // Convert the targets into targets by cheating a bit and // recursing into ourselves. for _, target := range failoverTargets { - failoverGroupResolverNode, err := c.getGroupResolverNode(target, true) + failoverResolveNode, err := c.getResolverNode(target, true) if err != nil { return nil, err } - failoverTarget := failoverGroupResolverNode.GroupResolver.Target + failoverTarget := failoverResolveNode.Resolver.Target df.Targets = append(df.Targets, failoverTarget) } } } } - return groupResolverNode, nil + return node, nil } func newDefaultServiceResolver(serviceName string) *structs.ServiceResolverConfigEntry { diff --git a/agent/consul/discoverychain/compile_test.go b/agent/consul/discoverychain/compile_test.go index 5e64a6e9e1..3a1b2cb924 100644 --- a/agent/consul/discoverychain/compile_test.go +++ b/agent/consul/discoverychain/compile_test.go @@ -8,25 +8,10 @@ import ( "github.com/stretchr/testify/require" ) -func TestCompile_NoEntries_NoInferDefaults(t *testing.T) { - entries := newEntries() - - res, err := Compile(CompileRequest{ - ServiceName: "main", - CurrentNamespace: "default", - CurrentDatacenter: "dc1", - InferDefaults: false, - Entries: entries, - }) - require.NoError(t, err) - require.Nil(t, res) -} - type compileTestCase struct { entries *structs.DiscoveryChainConfigEntries setup func(req *CompileRequest) - // expect: the GroupResolverNodes map should have nil values - expect *structs.CompiledDiscoveryChain + expect *structs.CompiledDiscoveryChain // expectIsDefault tests behavior of CompiledDiscoveryChain.IsDefault() expectIsDefault bool expectCustom bool @@ -56,6 +41,7 @@ func TestCompile(t *testing.T) { "service failover": testcase_ServiceFailover(), "service and subset failover": testcase_ServiceAndSubsetFailover(), "datacenter failover": testcase_DatacenterFailover(), + "service failover with mesh gateways": testcase_ServiceFailover_WithMeshGateways(), "noop split to resolver with default subset": testcase_NoopSplit_WithDefaultSubset(), "resolver with default subset": testcase_Resolve_WithDefaultSubset(), "resolver with no entries and inferring defaults": testcase_DefaultResolver(), @@ -104,7 +90,6 @@ func TestCompile(t *testing.T) { ServiceName: "main", CurrentNamespace: "default", CurrentDatacenter: "dc1", - InferDefaults: true, Entries: tc.entries, } if tc.setup != nil { @@ -130,16 +115,6 @@ func TestCompile(t *testing.T) { tc.expect.Namespace = "default" tc.expect.Datacenter = "dc1" - // These nodes are duplicated elsewhere in the results, so we only - // care that the keys are present. Walk the results and nil out the - // value payloads so that the require.Equal will still do the work - // for us. - if len(res.GroupResolverNodes) > 0 { - for target, _ := range res.GroupResolverNodes { - res.GroupResolverNodes[target] = nil - } - } - if tc.expectCustom { require.NotEmpty(t, res.CustomizationHash) res.CustomizationHash = "" @@ -168,34 +143,32 @@ func testcase_JustRouterWithDefaults() compileTestCase { resolver := newDefaultServiceResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeRouter, - Name: "main", - Routes: []*structs.DiscoveryRoute{ - { - Definition: newDefaultServiceRoute("main"), - DestinationNode: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc1"), - }, + Protocol: "http", + StartNode: "router:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "router:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeRouter, + Name: "main", + Routes: []*structs.DiscoveryRoute{ + { + Definition: newDefaultServiceRoute("main"), + NextNode: "resolver:main,,,dc1", }, }, }, + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } @@ -223,33 +196,31 @@ func testcase_RouterWithDefaults_NoSplit_WithResolver() compileTestCase { resolver := entries.GetResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeRouter, - Name: "main", - Routes: []*structs.DiscoveryRoute{ - { - Definition: newDefaultServiceRoute("main"), - DestinationNode: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 33 * time.Second, - Target: newTarget("main", "", "default", "dc1"), - }, + Protocol: "http", + StartNode: "router:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "router:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeRouter, + Name: "main", + Routes: []*structs.DiscoveryRoute{ + { + Definition: newDefaultServiceRoute("main"), + NextNode: "resolver:main,,,dc1", }, }, }, + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 33 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } @@ -279,43 +250,42 @@ func testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver() compileTestCase resolver := newDefaultServiceResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeRouter, - Name: "main", - Routes: []*structs.DiscoveryRoute{ - { - Definition: newDefaultServiceRoute("main"), - DestinationNode: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeSplitter, - Name: "main", - Splits: []*structs.DiscoverySplit{ - { - Weight: 100, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc1"), - }, - }, - }, - }, + Protocol: "http", + StartNode: "router:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "router:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeRouter, + Name: "main", + Routes: []*structs.DiscoveryRoute{ + { + Definition: newDefaultServiceRoute("main"), + NextNode: "splitter:main", }, }, }, + "splitter:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "main", + Splits: []*structs.DiscoverySplit{ + { + Weight: 100, + NextNode: "resolver:main,,,dc1", + }, + }, + }, + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } @@ -345,43 +315,42 @@ func testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults() compileTestC resolver := newDefaultServiceResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeRouter, - Name: "main", - Routes: []*structs.DiscoveryRoute{ - { - Definition: newDefaultServiceRoute("main"), - DestinationNode: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeSplitter, - Name: "main", - Splits: []*structs.DiscoverySplit{ - { - Weight: 100, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc1"), - }, - }, - }, - }, + Protocol: "http", + StartNode: "router:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "router:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeRouter, + Name: "main", + Routes: []*structs.DiscoveryRoute{ + { + Definition: newDefaultServiceRoute("main"), + NextNode: "splitter:main", }, }, }, + "splitter:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "main", + Splits: []*structs.DiscoverySplit{ + { + Weight: 100, + NextNode: "resolver:main,,,dc1", + }, + }, + }, + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } @@ -418,42 +387,41 @@ func testcase_RouterWithDefaults_WithNoopSplit_WithResolver() compileTestCase { resolver := entries.GetResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeRouter, - Name: "main", - Routes: []*structs.DiscoveryRoute{ - { - Definition: newDefaultServiceRoute("main"), - DestinationNode: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeSplitter, - Name: "main", - Splits: []*structs.DiscoverySplit{ - { - Weight: 100, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 33 * time.Second, - Target: newTarget("main", "", "default", "dc1"), - }, - }, - }, - }, + Protocol: "http", + StartNode: "router:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "router:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeRouter, + Name: "main", + Routes: []*structs.DiscoveryRoute{ + { + Definition: newDefaultServiceRoute("main"), + NextNode: "splitter:main", }, }, }, + "splitter:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "main", + Splits: []*structs.DiscoverySplit{ + { + Weight: 100, + NextNode: "resolver:main,,,dc1", + }, + }, + }, + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 33 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } @@ -504,49 +472,50 @@ func testcase_RouteBypassesSplit() compileTestCase { resolverMain := newDefaultServiceResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeRouter, - Name: "main", - Routes: []*structs.DiscoveryRoute{ - { - Definition: &router.Routes[0], - DestinationNode: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "other", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverOther, - ConnectTimeout: 5 * time.Second, - Target: newTarget("other", "bypass", "default", "dc1"), - }, + Protocol: "http", + StartNode: "router:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "router:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeRouter, + Name: "main", + Routes: []*structs.DiscoveryRoute{ + { + Definition: &router.Routes[0], + NextNode: "resolver:other,bypass,,dc1", }, - }, - { - Definition: newDefaultServiceRoute("main"), - DestinationNode: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverMain, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc1"), - }, + { + Definition: newDefaultServiceRoute("main"), + NextNode: "resolver:main,,,dc1", }, }, }, + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverMain, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, + }, + "resolver:other,bypass,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "other,bypass,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverOther, + ConnectTimeout: 5 * time.Second, + Target: newTarget("other", "bypass", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "other": resolverOther, - "main": resolverMain, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - newTarget("other", "bypass", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, - newTarget("other", "bypass", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, + newTarget("other", "bypass", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "Service.Meta.version == bypass", + }, + }, }, } @@ -570,34 +539,32 @@ func testcase_NoopSplit_DefaultResolver() compileTestCase { resolver := newDefaultServiceResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeSplitter, - Name: "main", - Splits: []*structs.DiscoverySplit{ - { - Weight: 100, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc1"), - }, + Protocol: "http", + StartNode: "splitter:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "splitter:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "main", + Splits: []*structs.DiscoverySplit{ + { + Weight: 100, + NextNode: "resolver:main,,,dc1", }, }, }, + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } @@ -628,33 +595,31 @@ func testcase_NoopSplit_WithResolver() compileTestCase { resolver := entries.GetResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeSplitter, - Name: "main", - Splits: []*structs.DiscoverySplit{ - { - Weight: 100, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 33 * time.Second, - Target: newTarget("main", "", "default", "dc1"), - }, + Protocol: "http", + StartNode: "splitter:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "splitter:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "main", + Splits: []*structs.DiscoverySplit{ + { + Weight: 100, + NextNode: "resolver:main,,,dc1", }, }, }, + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 33 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } @@ -693,47 +658,53 @@ func testcase_SubsetSplit() compileTestCase { resolver := entries.GetResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeSplitter, - Name: "main", - Splits: []*structs.DiscoverySplit{ - { - Weight: 60, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "v2", "default", "dc1"), - }, + Protocol: "http", + StartNode: "splitter:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "splitter:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "main", + Splits: []*structs.DiscoverySplit{ + { + Weight: 60, + NextNode: "resolver:main,v2,,dc1", }, - }, - { - Weight: 40, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "v1", "default", "dc1"), - }, + { + Weight: 40, + NextNode: "resolver:main,v1,,dc1", }, }, }, + "resolver:main,v2,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,v2,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "v2", "default", "dc1"), + }, + }, + "resolver:main,v1,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,v1,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "v1", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "v1", "default", "dc1"), - newTarget("main", "v2", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "v1", "default", "dc1"): nil, - newTarget("main", "v2", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "v1", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "Service.Meta.version == 1", + }, + }, + newTarget("main", "v2", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "Service.Meta.version == 2", + }, + }, }, } @@ -761,50 +732,47 @@ func testcase_ServiceSplit() compileTestCase { resolverBar := newDefaultServiceResolver("bar") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeSplitter, - Name: "main", - Splits: []*structs.DiscoverySplit{ - { - Weight: 60, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "foo", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverFoo, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("foo", "", "default", "dc1"), - }, + Protocol: "http", + StartNode: "splitter:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "splitter:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "main", + Splits: []*structs.DiscoverySplit{ + { + Weight: 60, + NextNode: "resolver:foo,,,dc1", }, - }, - { - Weight: 40, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "bar", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverBar, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("bar", "", "default", "dc1"), - }, + { + Weight: 40, + NextNode: "resolver:bar,,,dc1", }, }, }, + "resolver:foo,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "foo,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverFoo, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("foo", "", "default", "dc1"), + }, + }, + "resolver:bar,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "bar,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverBar, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("bar", "", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "foo": resolverFoo, - "bar": resolverBar, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("bar", "", "default", "dc1"), - newTarget("foo", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("bar", "", "default", "dc1"): nil, - newTarget("foo", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("bar", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, + newTarget("foo", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } @@ -857,33 +825,35 @@ func testcase_SplitBypassesSplit() compileTestCase { resolverNext := entries.GetResolver("next") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeSplitter, - Name: "main", - Splits: []*structs.DiscoverySplit{ - { - Weight: 100, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "next", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverNext, - ConnectTimeout: 5 * time.Second, - Target: newTarget("next", "bypassed", "default", "dc1"), - }, + Protocol: "http", + StartNode: "splitter:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "splitter:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "main", + Splits: []*structs.DiscoverySplit{ + { + Weight: 100, + NextNode: "resolver:next,bypassed,,dc1", }, }, }, + "resolver:next,bypassed,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "next,bypassed,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverNext, + ConnectTimeout: 5 * time.Second, + Target: newTarget("next", "bypassed", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "next": resolverNext, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("next", "bypassed", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("next", "bypassed", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("next", "bypassed", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "Service.Meta.version == bypass", + }, + }, }, } @@ -905,25 +875,22 @@ func testcase_ServiceRedirect() compileTestCase { resolverOther := newDefaultServiceResolver("other") expect := &structs.CompiledDiscoveryChain{ - Protocol: "tcp", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "other", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverOther, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("other", "", "default", "dc1"), + Protocol: "tcp", + StartNode: "resolver:other,,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:other,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "other,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverOther, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("other", "", "default", "dc1"), + }, }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "other": resolverOther, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("other", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("other", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("other", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } @@ -958,24 +925,25 @@ func testcase_ServiceAndSubsetRedirect() compileTestCase { resolver := entries.GetResolver("other") expect := &structs.CompiledDiscoveryChain{ - Protocol: "tcp", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "other", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 5 * time.Second, - Target: newTarget("other", "v2", "default", "dc1"), + Protocol: "tcp", + StartNode: "resolver:other,v2,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:other,v2,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "other,v2,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 5 * time.Second, + Target: newTarget("other", "v2", "default", "dc1"), + }, }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "other": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("other", "v2", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("other", "v2", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("other", "v2", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "Service.Meta.version == 2", + }, + }, }, } return compileTestCase{entries: entries, expect: expect} @@ -996,24 +964,21 @@ func testcase_DatacenterRedirect() compileTestCase { resolver := entries.GetResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "tcp", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc9"), + Protocol: "tcp", + StartNode: "resolver:main,,,dc9", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main,,,dc9": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc9", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc9"), + }, }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc9"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc9"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc9"): structs.DiscoveryTargetConfig{}, }, } return compileTestCase{entries: entries, expect: expect} @@ -1032,37 +997,32 @@ func testcase_ServiceFailover() compileTestCase { ) resolverMain := entries.GetResolver("main") - resolverBackup := newDefaultServiceResolver("backup") wildFail := resolverMain.Failover["*"] expect := &structs.CompiledDiscoveryChain{ - Protocol: "tcp", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverMain, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc1"), - Failover: &structs.DiscoveryFailover{ - Definition: &wildFail, - Targets: []structs.DiscoveryTarget{ - newTarget("backup", "", "default", "dc1"), + Protocol: "tcp", + StartNode: "resolver:main,,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverMain, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + Failover: &structs.DiscoveryFailover{ + Definition: &wildFail, + Targets: []structs.DiscoveryTarget{ + newTarget("backup", "", "default", "dc1"), + }, }, }, }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolverMain, - "backup": resolverBackup, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("backup", "", "default", "dc1"), - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("backup", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } return compileTestCase{entries: entries, expect: expect} @@ -1089,31 +1049,32 @@ func testcase_ServiceAndSubsetFailover() compileTestCase { wildFail := resolver.Failover["*"] expect := &structs.CompiledDiscoveryChain{ - Protocol: "tcp", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc1"), - Failover: &structs.DiscoveryFailover{ - Definition: &wildFail, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "backup", "default", "dc1"), + Protocol: "tcp", + StartNode: "resolver:main,,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + Failover: &structs.DiscoveryFailover{ + Definition: &wildFail, + Targets: []structs.DiscoveryTarget{ + newTarget("main", "backup", "default", "dc1"), + }, }, }, }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - newTarget("main", "backup", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, + newTarget("main", "backup", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "Service.Meta.version == backup", + }, + }, }, } return compileTestCase{entries: entries, expect: expect} @@ -1135,33 +1096,89 @@ func testcase_DatacenterFailover() compileTestCase { wildFail := resolver.Failover["*"] expect := &structs.CompiledDiscoveryChain{ - Protocol: "tcp", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc1"), - Failover: &structs.DiscoveryFailover{ - Definition: &wildFail, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc2"), - newTarget("main", "", "default", "dc4"), + Protocol: "tcp", + StartNode: "resolver:main,,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + Failover: &structs.DiscoveryFailover{ + Definition: &wildFail, + Targets: []structs.DiscoveryTarget{ + newTarget("main", "", "default", "dc2"), + newTarget("main", "", "default", "dc4"), + }, }, }, }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, + newTarget("main", "", "default", "dc2"): structs.DiscoveryTargetConfig{}, + newTarget("main", "", "default", "dc4"): structs.DiscoveryTargetConfig{}, }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - newTarget("main", "", "default", "dc2"), - newTarget("main", "", "default", "dc4"), + } + return compileTestCase{entries: entries, expect: expect} +} + +func testcase_ServiceFailover_WithMeshGateways() compileTestCase { + entries := newEntries() + entries.GlobalProxy = &structs.ProxyConfigEntry{ + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + MeshGateway: structs.MeshGatewayConfig{ + Mode: structs.MeshGatewayModeRemote, }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + } + entries.AddResolvers( + &structs.ServiceResolverConfigEntry{ + Kind: "service-resolver", + Name: "main", + Failover: map[string]structs.ServiceResolverFailover{ + "*": {Service: "backup"}, + }, + }, + ) + + resolverMain := entries.GetResolver("main") + + wildFail := resolverMain.Failover["*"] + + expect := &structs.CompiledDiscoveryChain{ + Protocol: "tcp", + StartNode: "resolver:main,,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverMain, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + Failover: &structs.DiscoveryFailover{ + Definition: &wildFail, + Targets: []structs.DiscoveryTarget{ + newTarget("backup", "", "default", "dc1"), + }, + }, + }, + }, + }, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("backup", "", "default", "dc1"): structs.DiscoveryTargetConfig{ + MeshGateway: structs.MeshGatewayConfig{ + Mode: structs.MeshGatewayModeRemote, + }, + }, + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{ + MeshGateway: structs.MeshGatewayConfig{ + Mode: structs.MeshGatewayModeRemote, + }, + }, }, } return compileTestCase{entries: entries, expect: expect} @@ -1195,33 +1212,35 @@ func testcase_NoopSplit_WithDefaultSubset() compileTestCase { resolver := entries.GetResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeSplitter, - Name: "main", - Splits: []*structs.DiscoverySplit{ - { - Weight: 100, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "v2", "default", "dc1"), - }, + Protocol: "http", + StartNode: "splitter:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "splitter:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "main", + Splits: []*structs.DiscoverySplit{ + { + Weight: 100, + NextNode: "resolver:main,v2,,dc1", }, }, }, + "resolver:main,v2,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,v2,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "v2", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "v2", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "v2", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "v2", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "Service.Meta.version == 2", + }, + }, }, } return compileTestCase{entries: entries, expect: expect} @@ -1233,25 +1252,22 @@ func testcase_DefaultResolver() compileTestCase { resolver := newDefaultServiceResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "tcp", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc1"), + Protocol: "tcp", + StartNode: "resolver:main,,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } return compileTestCase{entries: entries, expect: expect, expectIsDefault: true} @@ -1273,29 +1289,27 @@ func testcase_DefaultResolver_WithProxyDefaults() compileTestCase { resolver := newDefaultServiceResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "grpc", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - Default: true, - ConnectTimeout: 5 * time.Second, + Protocol: "grpc", + StartNode: "resolver:main,,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, + }, + }, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{ MeshGateway: structs.MeshGatewayConfig{ Mode: structs.MeshGatewayModeRemote, }, - Target: newTarget("main", "", "default", "dc1"), }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, - }, } return compileTestCase{entries: entries, expect: expect, expectIsDefault: true} } @@ -1315,25 +1329,22 @@ func testcase_RedirectToDefaultResolverIsNotDefaultChain() compileTestCase { resolver := newDefaultServiceResolver("other") expect := &structs.CompiledDiscoveryChain{ - Protocol: "tcp", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "other", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("other", "", "default", "dc1"), + Protocol: "tcp", + StartNode: "resolver:other,,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:other,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "other,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("other", "", "default", "dc1"), + }, }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "other": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("other", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("other", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("other", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } @@ -1357,24 +1368,25 @@ func testcase_Resolve_WithDefaultSubset() compileTestCase { resolver := entries.GetResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "tcp", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "v2", "default", "dc1"), + Protocol: "tcp", + StartNode: "resolver:main,v2,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main,v2,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,v2,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "v2", "default", "dc1"), + }, }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "v2", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "v2", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "v2", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "Service.Meta.version == 2", + }, + }, }, } return compileTestCase{entries: entries, expect: expect} @@ -1407,33 +1419,35 @@ func testcase_CircularSplit() compileTestCase { resolveMain := newDefaultServiceResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeSplitter, - Name: "main", - Splits: []*structs.DiscoverySplit{ - { - Weight: 60, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolveMain, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "v2", "default", "dc1"), - }, + Protocol: "http", + StartNode: "splitter:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "splitter:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "main", + Splits: []*structs.DiscoverySplit{ + { + Weight: 60, + NextNode: "resolver:main,v2,,dc1", }, }, }, + "resolver:main,v2,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,v2,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolveMain, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "v2", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolveMain, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "v2", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "v2", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "v2", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "TODO", + }, + }, }, } return compileTestCase{entries: entries, expect: expect} @@ -1482,47 +1496,45 @@ func testcase_MultiDatacenterCanary() compileTestCase { resolver := entries.GetResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeSplitter, - Name: "main", - Splits: []*structs.DiscoverySplit{ - { - Weight: 60, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 33 * time.Second, - Target: newTarget("main", "", "default", "dc2"), - }, + Protocol: "http", + StartNode: "splitter:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "splitter:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "main", + Splits: []*structs.DiscoverySplit{ + { + Weight: 60, + NextNode: "resolver:main,,,dc2", }, - }, - { - Weight: 40, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - ConnectTimeout: 33 * time.Second, - Target: newTarget("main", "", "default", "dc3"), - }, + { + Weight: 40, + NextNode: "resolver:main,,,dc3", }, }, }, + "resolver:main,,,dc2": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc2", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 33 * time.Second, + Target: newTarget("main", "", "default", "dc2"), + }, + }, + "resolver:main,,,dc3": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc3", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + ConnectTimeout: 33 * time.Second, + Target: newTarget("main", "", "default", "dc3"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc2"), - newTarget("main", "", "default", "dc3"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc2"): nil, - newTarget("main", "", "default", "dc3"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc2"): structs.DiscoveryTargetConfig{}, + newTarget("main", "", "default", "dc3"): structs.DiscoveryTargetConfig{}, }, } return compileTestCase{entries: entries, expect: expect} @@ -1619,111 +1631,119 @@ func testcase_AllBellsAndWhistles() compileTestCase { ) expect := &structs.CompiledDiscoveryChain{ - Protocol: "http", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeRouter, - Name: "main", - Routes: []*structs.DiscoveryRoute{ - { - Definition: &router.Routes[0], - DestinationNode: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "redirected", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverRedirected, - ConnectTimeout: 5 * time.Second, - Target: newTarget("redirected", "prod", "default", "dc1"), - }, + Protocol: "http", + StartNode: "router:main", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "router:main": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeRouter, + Name: "main", + Routes: []*structs.DiscoveryRoute{ + { + Definition: &router.Routes[0], + NextNode: "resolver:redirected,prod,,dc1", }, - }, - { - Definition: &router.Routes[1], - DestinationNode: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeSplitter, - Name: "svc-split", - Splits: []*structs.DiscoverySplit{ - { - Weight: 60, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "redirected", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverRedirected, - ConnectTimeout: 5 * time.Second, - Target: newTarget("redirected", "prod", "default", "dc1"), - }, - }, - }, - { - Weight: 30, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverMain, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "v1", "default", "dc1"), - }, - }, - }, - { - Weight: 8, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverMain, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "v2", "default", "dc1"), - }, - }, - }, - { - Weight: 2, - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverMain, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "v3", "default", "dc1"), - }, - }, - }, - }, + { + Definition: &router.Routes[1], + NextNode: "splitter:svc-split", }, - }, - { - Definition: newDefaultServiceRoute("main"), - DestinationNode: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolverMain, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "default-subset", "default", "dc1"), - }, + { + Definition: newDefaultServiceRoute("main"), + NextNode: "resolver:main,default-subset,,dc1", }, }, }, + "splitter:svc-split": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeSplitter, + Name: "svc-split", + Splits: []*structs.DiscoverySplit{ + { + Weight: 60, + NextNode: "resolver:redirected,prod,,dc1", + }, + { + Weight: 30, + NextNode: "resolver:main,v1,,dc1", + }, + { + Weight: 8, + NextNode: "resolver:main,v2,,dc1", + }, + { + Weight: 2, + NextNode: "resolver:main,v3,,dc1", + }, + }, + }, + "resolver:redirected,prod,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "redirected,prod,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverRedirected, + ConnectTimeout: 5 * time.Second, + Target: newTarget("redirected", "prod", "default", "dc1"), + }, + }, + "resolver:main,v1,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,v1,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverMain, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "v1", "default", "dc1"), + }, + }, + "resolver:main,v2,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,v2,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverMain, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "v2", "default", "dc1"), + }, + }, + "resolver:main,v3,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,v3,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverMain, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "v3", "default", "dc1"), + }, + }, + "resolver:main,default-subset,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,default-subset,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolverMain, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "default-subset", "default", "dc1"), + }, + }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolverMain, - "redirected": resolverRedirected, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "default-subset", "default", "dc1"), - newTarget("main", "v1", "default", "dc1"), - newTarget("main", "v2", "default", "dc1"), - newTarget("main", "v3", "default", "dc1"), - newTarget("redirected", "prod", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "default-subset", "default", "dc1"): nil, - newTarget("main", "v1", "default", "dc1"): nil, - newTarget("main", "v2", "default", "dc1"): nil, - newTarget("main", "v3", "default", "dc1"): nil, - newTarget("redirected", "prod", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "default-subset", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{OnlyPassing: true}, + }, + newTarget("main", "v1", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "Service.Meta.version == 1", + }, + }, + newTarget("main", "v2", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "Service.Meta.version == 2", + }, + }, + newTarget("main", "v3", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "Service.Meta.version == 3", + }, + }, + newTarget("redirected", "prod", "default", "dc1"): structs.DiscoveryTargetConfig{ + Subset: structs.ServiceResolverSubset{ + Filter: "ServiceMeta.env == prod", + }, + }, }, } return compileTestCase{entries: entries, expect: expect} @@ -1911,25 +1931,22 @@ func testcase_ResolverProtocolOverride() compileTestCase { resolver := newDefaultServiceResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http2", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc1"), + Protocol: "http2", + StartNode: "resolver:main,,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } return compileTestCase{entries: entries, expect: expect, expectIsDefault: true, @@ -1949,25 +1966,22 @@ func testcase_ResolverProtocolOverrideIgnored() compileTestCase { resolver := newDefaultServiceResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "http2", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc1"), + Protocol: "http2", + StartNode: "resolver:main,,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } return compileTestCase{entries: entries, expect: expect, expectIsDefault: true, @@ -1991,25 +2005,22 @@ func testcase_RouterIgnored_ResolverProtocolOverride() compileTestCase { resolver := newDefaultServiceResolver("main") expect := &structs.CompiledDiscoveryChain{ - Protocol: "tcp", - Node: &structs.DiscoveryGraphNode{ - Type: structs.DiscoveryGraphNodeTypeGroupResolver, - Name: "main", - GroupResolver: &structs.DiscoveryGroupResolver{ - Definition: resolver, - Default: true, - ConnectTimeout: 5 * time.Second, - Target: newTarget("main", "", "default", "dc1"), + Protocol: "tcp", + StartNode: "resolver:main,,,dc1", + Nodes: map[string]*structs.DiscoveryGraphNode{ + "resolver:main,,,dc1": &structs.DiscoveryGraphNode{ + Type: structs.DiscoveryGraphNodeTypeResolver, + Name: "main,,,dc1", + Resolver: &structs.DiscoveryResolver{ + Definition: resolver, + Default: true, + ConnectTimeout: 5 * time.Second, + Target: newTarget("main", "", "default", "dc1"), + }, }, }, - Resolvers: map[string]*structs.ServiceResolverConfigEntry{ - "main": resolver, - }, - Targets: []structs.DiscoveryTarget{ - newTarget("main", "", "default", "dc1"), - }, - GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{ - newTarget("main", "", "default", "dc1"): nil, + Targets: map[structs.DiscoveryTarget]structs.DiscoveryTargetConfig{ + newTarget("main", "", "default", "dc1"): structs.DiscoveryTargetConfig{}, }, } return compileTestCase{entries: entries, expect: expect, expectIsDefault: true, diff --git a/agent/consul/discoverychain/testing.go b/agent/consul/discoverychain/testing.go index 0c05c44da4..8dbed12540 100644 --- a/agent/consul/discoverychain/testing.go +++ b/agent/consul/discoverychain/testing.go @@ -22,7 +22,6 @@ func TestCompileConfigEntries( ServiceName: serviceName, CurrentNamespace: currentNamespace, CurrentDatacenter: currentDatacenter, - InferDefaults: true, Entries: set, } if setup != nil { diff --git a/agent/proxycfg/state.go b/agent/proxycfg/state.go index 36fe2f4a2b..fa5fd7f04f 100644 --- a/agent/proxycfg/state.go +++ b/agent/proxycfg/state.go @@ -559,37 +559,6 @@ func (s *state) resetWatchesFromChain( return fmt.Errorf("not possible to arrive here with no discovery chain") } - // Collect all sorts of catalog queries we'll have to run. - targets := make(map[structs.DiscoveryTarget]*structs.ServiceResolverConfigEntry) - addTarget := func(target structs.DiscoveryTarget) error { - resolver, ok := chain.Resolvers[target.Service] - if !ok { - return fmt.Errorf("missing resolver %q for target %s", target.Service, target) - } - - targets[target] = resolver - return nil - } - - // NOTE: We will NEVER see a missing chain, because we always request it with defaulting enabled. - meshGatewayModes := make(map[structs.DiscoveryTarget]structs.MeshGatewayMode) - for _, group := range chain.GroupResolverNodes { - groupResolver := group.GroupResolver - - meshGatewayModes[groupResolver.Target] = groupResolver.MeshGateway.Mode - - if err := addTarget(groupResolver.Target); err != nil { - return err - } - if groupResolver.Failover != nil { - for _, target := range groupResolver.Failover.Targets { - if err := addTarget(target); err != nil { - return err - } - } - } - } - // Initialize relevant sub maps. if _, ok := snap.ConnectProxy.WatchedUpstreams[id]; !ok { snap.ConnectProxy.WatchedUpstreams[id] = make(map[structs.DiscoveryTarget]context.CancelFunc) @@ -611,35 +580,17 @@ func (s *state) resetWatchesFromChain( cancelFn() } - for target, resolver := range targets { - if target.Service != resolver.Name { - panic(target.Service + " != " + resolver.Name) // TODO(rb): remove - } + for target, targetConfig := range chain.Targets { s.logger.Printf("[TRACE] proxycfg: upstream=%q:chain=%q: initializing watch of target %s", id, chain.ServiceName, target) - // TODO(rb): make sure the cross-dc request properly fills in the alternate datacenters - - var subset structs.ServiceResolverSubset - if target.ServiceSubset != "" { - var ok bool - subset, ok = resolver.Subsets[target.ServiceSubset] - if !ok { - // Not possible really. - return fmt.Errorf("target %s cannot be resolved; service %q does not have a subset named %q", target, target.Service, target.ServiceSubset) - } - } - - encodedTarget, err := target.MarshalText() - if err != nil { - return fmt.Errorf("target %s cannot be converted into a cache key string: %v", target, err) - } + encodedTarget := target.Identifier() ctx, cancel := context.WithCancel(s.ctx) // TODO (mesh-gateway)- maybe allow using a gateway within a datacenter at some point meshGateway := structs.MeshGatewayModeDefault if target.Datacenter != s.source.Datacenter { - meshGateway = meshGatewayModes[target] + meshGateway = targetConfig.MeshGateway.Mode } // if the default mode @@ -647,12 +598,12 @@ func (s *state) resetWatchesFromChain( meshGateway = structs.MeshGatewayModeNone } - err = s.watchConnectProxyService( + err := s.watchConnectProxyService( ctx, - "upstream-target:"+string(encodedTarget)+":"+id, + "upstream-target:"+encodedTarget+":"+id, target.Service, target.Datacenter, - subset.Filter, + targetConfig.Subset.Filter, meshGateway, ) if err != nil { diff --git a/agent/structs/discovery_chain.go b/agent/structs/discovery_chain.go index 5eb25cc709..ee3cb94552 100644 --- a/agent/structs/discovery_chain.go +++ b/agent/structs/discovery_chain.go @@ -5,7 +5,6 @@ import ( "encoding" "fmt" "net/url" - "sort" "strings" "time" ) @@ -30,24 +29,19 @@ type CompiledDiscoveryChain struct { // Protocol is the overall protocol shared by everything in the chain. Protocol string - // Node is the top node in the chain. - // - // If this is a router or splitter then in envoy this renders as an http - // route object. - // - // If this is a group resolver then in envoy this renders as a default - // wildcard http route object. - Node *DiscoveryGraphNode `json:",omitempty"` + // StartNode is the first key into the Nodes map that should be followed + // when walking the discovery chain. + StartNode string `json:",omitempty"` - // GroupResolverNodes respresents all unique service instance groups that - // need to be represented. For envoy these render as Clusters. + // Nodes contains all nodes available for traversal in the chain keyed by a + // unique name. You can walk this by starting with StartNode. // - // Omitted from JSON because these already show up under the Node field. - GroupResolverNodes map[DiscoveryTarget]*DiscoveryGraphNode `json:"-"` + // NOTE: The names should be treated as opaque values and are only + // guaranteed to be consistent within a single compilation. + Nodes map[string]*DiscoveryGraphNode `json:",omitempty"` - // TODO(rb): not sure if these two fields are actually necessary but I'll know when I get into xDS - Resolvers map[string]*ServiceResolverConfigEntry `json:",omitempty"` - Targets []DiscoveryTarget `json:",omitempty"` + // Targets is a list of all targets and configuration related just to targets. + Targets map[DiscoveryTarget]DiscoveryTargetConfig `json:",omitempty"` } // IsDefault returns true if the compiled chain represents no routing, no @@ -56,41 +50,31 @@ type CompiledDiscoveryChain struct { // applied is redirection to another resolver that is default, so we double // check the resolver matches the requested resolver. func (c *CompiledDiscoveryChain) IsDefault() bool { - if c.Node == nil { + if c.StartNode == "" || len(c.Nodes) == 0 { return true } + + node := c.Nodes[c.StartNode] + if node == nil { + panic("not possible: missing node named '" + c.StartNode + "' in chain '" + c.ServiceName + "'") + } + // TODO(rb): include CustomizationHash here? - return c.Node.Name == c.ServiceName && - c.Node.Type == DiscoveryGraphNodeTypeGroupResolver && - c.Node.GroupResolver.Default -} - -// SubsetDefinitionForTarget is a convenience function to fetch the subset -// definition for the service subset defined by the provided target. If the -// subset is not defined an empty definition is returned. -func (c *CompiledDiscoveryChain) SubsetDefinitionForTarget(t DiscoveryTarget) ServiceResolverSubset { - if t.ServiceSubset == "" { - return ServiceResolverSubset{} - } - - resolver, ok := c.Resolvers[t.Service] - if !ok { - return ServiceResolverSubset{} - } - - return resolver.Subsets[t.ServiceSubset] + return node.Type == DiscoveryGraphNodeTypeResolver && + node.Resolver.Default && + node.Resolver.Target.Service == c.ServiceName } const ( - DiscoveryGraphNodeTypeRouter = "router" - DiscoveryGraphNodeTypeSplitter = "splitter" - DiscoveryGraphNodeTypeGroupResolver = "group-resolver" + DiscoveryGraphNodeTypeRouter = "router" + DiscoveryGraphNodeTypeSplitter = "splitter" + DiscoveryGraphNodeTypeResolver = "resolver" ) -// DiscoveryGraphNode is a single node of the compiled discovery chain. +// DiscoveryGraphNode is a single node in the compiled discovery chain. type DiscoveryGraphNode struct { Type string - Name string // default chain/service name at this spot + Name string // this is NOT necessarily a service // fields for Type==router Routes []*DiscoveryRoute `json:",omitempty"` @@ -98,34 +82,48 @@ type DiscoveryGraphNode struct { // fields for Type==splitter Splits []*DiscoverySplit `json:",omitempty"` - // fields for Type==group-resolver - GroupResolver *DiscoveryGroupResolver `json:",omitempty"` + // fields for Type==resolver + Resolver *DiscoveryResolver `json:",omitempty"` } -// compiled form of ServiceResolverConfigEntry but customized per non-failover target -type DiscoveryGroupResolver struct { +func (s *DiscoveryGraphNode) ServiceName() string { + if s.Type == DiscoveryGraphNodeTypeResolver { + return s.Resolver.Target.Service + } + return s.Name +} + +func (s *DiscoveryGraphNode) MapKey() string { + return fmt.Sprintf("%s:%s", s.Type, s.Name) +} + +// compiled form of ServiceResolverConfigEntry +type DiscoveryResolver struct { Definition *ServiceResolverConfigEntry `json:",omitempty"` Default bool `json:",omitempty"` ConnectTimeout time.Duration `json:",omitempty"` - MeshGateway MeshGatewayConfig `json:",omitempty"` Target DiscoveryTarget `json:",omitempty"` Failover *DiscoveryFailover `json:",omitempty"` } +type DiscoveryTargetConfig struct { + MeshGateway MeshGatewayConfig `json:",omitempty"` + Subset ServiceResolverSubset `json:",omitempty"` +} + // compiled form of ServiceRoute type DiscoveryRoute struct { - Definition *ServiceRoute `json:",omitempty"` - DestinationNode *DiscoveryGraphNode `json:",omitempty"` + Definition *ServiceRoute `json:",omitempty"` + NextNode string `json:",omitempty"` } // compiled form of ServiceSplit type DiscoverySplit struct { - Weight float32 `json:",omitempty"` - Node *DiscoveryGraphNode `json:",omitempty"` + Weight float32 `json:",omitempty"` + NextNode string `json:",omitempty"` } // compiled form of ServiceResolverFailover -// TODO(rb): figure out how to get mesh gateways in here type DiscoveryFailover struct { Definition *ServiceResolverFailover `json:",omitempty"` Targets []DiscoveryTarget `json:",omitempty"` @@ -183,17 +181,21 @@ var _ encoding.TextUnmarshaler = (*DiscoveryTarget)(nil) // // This should NOT return any errors. func (t DiscoveryTarget) MarshalText() (text []byte, err error) { + return []byte(t.Identifier()), nil +} + +func (t DiscoveryTarget) Identifier() string { var buf bytes.Buffer buf.WriteString(url.QueryEscape(t.Service)) buf.WriteRune(',') - buf.WriteString(url.QueryEscape(t.ServiceSubset)) + buf.WriteString(url.QueryEscape(t.ServiceSubset)) // TODO(rb): move this first so the scoping flows from small->large? buf.WriteRune(',') if t.Namespace != "default" { buf.WriteString(url.QueryEscape(t.Namespace)) } buf.WriteRune(',') buf.WriteString(url.QueryEscape(t.Datacenter)) - return buf.Bytes(), nil + return buf.String() } // UnmarshalText implements encoding.TextUnmarshaler. @@ -256,29 +258,3 @@ func (t DiscoveryTarget) String() string { return b.String() } - -type DiscoveryTargets []DiscoveryTarget - -func (targets DiscoveryTargets) Sort() { - sort.Slice(targets, func(i, j int) bool { - if targets[i].Service < targets[j].Service { - return true - } else if targets[i].Service > targets[j].Service { - return false - } - - if targets[i].ServiceSubset < targets[j].ServiceSubset { - return true - } else if targets[i].ServiceSubset > targets[j].ServiceSubset { - return false - } - - if targets[i].Namespace < targets[j].Namespace { - return true - } else if targets[i].Namespace > targets[j].Namespace { - return false - } - - return targets[i].Datacenter < targets[j].Datacenter - }) -} diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index eeb7813503..8bf5623005 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -234,7 +234,6 @@ func (s *Server) makeUpstreamClustersForDiscoveryChain( chain *structs.CompiledDiscoveryChain, cfgSnap *proxycfg.ConfigSnapshot, ) ([]*envoy.Cluster, error) { - cfg, err := ParseUpstreamConfigNoDefaults(upstream.Config) if err != nil { // Don't hard fail on a config typo, just warn. The parse func returns @@ -250,8 +249,12 @@ func (s *Server) makeUpstreamClustersForDiscoveryChain( // TODO(rb): make escape hatches work with chains var out []*envoy.Cluster - for target, node := range chain.GroupResolverNodes { - groupResolver := node.GroupResolver + + for _, node := range chain.Nodes { + if node.Type != structs.DiscoveryGraphNodeTypeResolver { + continue + } + target := node.Resolver.Target sni := TargetSNI(target, cfgSnap) clusterName := CustomizeClusterName(sni, chain) @@ -260,14 +263,13 @@ func (s *Server) makeUpstreamClustersForDiscoveryChain( c := &envoy.Cluster{ Name: clusterName, AltStatName: clusterName, - ConnectTimeout: groupResolver.ConnectTimeout, + ConnectTimeout: node.Resolver.ConnectTimeout, ClusterDiscoveryType: &envoy.Cluster_Type{Type: envoy.Cluster_EDS}, CommonLbConfig: &envoy.Cluster_CommonLbConfig{ HealthyPanicThreshold: &envoytype.Percent{ Value: 0, // disable panic threshold }, }, - // TODO(rb): adjust load assignment EdsClusterConfig: &envoy.Cluster_EdsClusterConfig{ EdsConfig: &envoycore.ConfigSource{ ConfigSourceSpecifier: &envoycore.ConfigSource_Ads{ diff --git a/agent/xds/endpoints.go b/agent/xds/endpoints.go index 5b9785fa3f..ff0219d1d1 100644 --- a/agent/xds/endpoints.go +++ b/agent/xds/endpoints.go @@ -73,15 +73,21 @@ func (s *Server) endpointsFromSnapshotConnectProxy(cfgSnap *proxycfg.ConfigSnaps continue // skip the upstream (should not happen) } - for target, node := range chain.GroupResolverNodes { - groupResolver := node.GroupResolver - failover := groupResolver.Failover + // Find all resolver nodes. + for _, node := range chain.Nodes { + if node.Type != structs.DiscoveryGraphNodeTypeResolver { + continue + } + failover := node.Resolver.Failover + target := node.Resolver.Target endpoints, ok := chainEndpointMap[target] if !ok { continue // skip the cluster (should not happen) } + targetConfig := chain.Targets[target] + var ( endpointGroups []loadAssignmentEndpointGroup overprovisioningFactor int @@ -89,7 +95,7 @@ func (s *Server) endpointsFromSnapshotConnectProxy(cfgSnap *proxycfg.ConfigSnaps primaryGroup := loadAssignmentEndpointGroup{ Endpoints: endpoints, - OnlyPassing: chain.SubsetDefinitionForTarget(target).OnlyPassing, + OnlyPassing: targetConfig.Subset.OnlyPassing, } if failover != nil && len(failover.Targets) > 0 { @@ -112,9 +118,11 @@ func (s *Server) endpointsFromSnapshotConnectProxy(cfgSnap *proxycfg.ConfigSnaps continue // skip the failover target (should not happen) } + failTargetConfig := chain.Targets[failTarget] + endpointGroups = append(endpointGroups, loadAssignmentEndpointGroup{ Endpoints: failEndpoints, - OnlyPassing: chain.SubsetDefinitionForTarget(failTarget).OnlyPassing, + OnlyPassing: failTargetConfig.Subset.OnlyPassing, }) } } else { diff --git a/agent/xds/endpoints_test.go b/agent/xds/endpoints_test.go index 41a5456e91..c280760d04 100644 --- a/agent/xds/endpoints_test.go +++ b/agent/xds/endpoints_test.go @@ -266,11 +266,11 @@ func Test_endpointsFromSnapshot(t *testing.T) { Namespace: "default", Datacenter: "dc1", } - dbResolverNode := chain.GroupResolverNodes[dbTarget] + dbResolverNode := chain.Nodes["resolver:"+dbTarget.Identifier()] - groupResolverFailover := dbResolverNode.GroupResolver.Failover + failover := dbResolverNode.Resolver.Failover - groupResolverFailover.Definition.OverprovisioningFactor = 160 + failover.Definition.OverprovisioningFactor = 160 }, }, { diff --git a/agent/xds/routes.go b/agent/xds/routes.go index f8c089df81..243c065410 100644 --- a/agent/xds/routes.go +++ b/agent/xds/routes.go @@ -73,11 +73,16 @@ func makeUpstreamRouteForDiscoveryChain( var routes []envoyroute.Route - switch chain.Node.Type { - case structs.DiscoveryGraphNodeTypeRouter: - routes = make([]envoyroute.Route, 0, len(chain.Node.Routes)) + startNode := chain.Nodes[chain.StartNode] + if startNode == nil { + panic("missing first node in compiled discovery chain for: " + chain.ServiceName) + } - for _, discoveryRoute := range chain.Node.Routes { + switch startNode.Type { + case structs.DiscoveryGraphNodeTypeRouter: + routes = make([]envoyroute.Route, 0, len(startNode.Routes)) + + for _, discoveryRoute := range startNode.Routes { routeMatch := makeRouteMatchForDiscoveryRoute(discoveryRoute, chain.Protocol) var ( @@ -85,19 +90,19 @@ func makeUpstreamRouteForDiscoveryChain( err error ) - next := discoveryRoute.DestinationNode - if next.Type == structs.DiscoveryGraphNodeTypeSplitter { - routeAction, err = makeRouteActionForSplitter(next.Splits, chain, cfgSnap) + nextNode := chain.Nodes[discoveryRoute.NextNode] + switch nextNode.Type { + case structs.DiscoveryGraphNodeTypeSplitter: + routeAction, err = makeRouteActionForSplitter(nextNode.Splits, chain, cfgSnap) if err != nil { return nil, err } - } else if next.Type == structs.DiscoveryGraphNodeTypeGroupResolver { - groupResolver := next.GroupResolver - routeAction = makeRouteActionForSingleCluster(groupResolver.Target, chain, cfgSnap) + case structs.DiscoveryGraphNodeTypeResolver: + routeAction = makeRouteActionForSingleCluster(nextNode.Resolver.Target, chain, cfgSnap) - } else { - return nil, fmt.Errorf("unexpected graph node after route %q", next.Type) + default: + return nil, fmt.Errorf("unexpected graph node after route %q", nextNode.Type) } // TODO(rb): Better help handle the envoy case where you need (prefix=/foo/,rewrite=/) and (exact=/foo,rewrite=/) to do a full rewrite @@ -142,7 +147,7 @@ func makeUpstreamRouteForDiscoveryChain( } case structs.DiscoveryGraphNodeTypeSplitter: - routeAction, err := makeRouteActionForSplitter(chain.Node.Splits, chain, cfgSnap) + routeAction, err := makeRouteActionForSplitter(startNode.Splits, chain, cfgSnap) if err != nil { return nil, err } @@ -154,10 +159,8 @@ func makeUpstreamRouteForDiscoveryChain( routes = []envoyroute.Route{defaultRoute} - case structs.DiscoveryGraphNodeTypeGroupResolver: - groupResolver := chain.Node.GroupResolver - - routeAction := makeRouteActionForSingleCluster(groupResolver.Target, chain, cfgSnap) + case structs.DiscoveryGraphNodeTypeResolver: + routeAction := makeRouteActionForSingleCluster(startNode.Resolver.Target, chain, cfgSnap) defaultRoute := envoyroute.Route{ Match: makeDefaultRouteMatch(), @@ -167,7 +170,7 @@ func makeUpstreamRouteForDiscoveryChain( routes = []envoyroute.Route{defaultRoute} default: - panic("unknown top node in discovery chain of type: " + chain.Node.Type) + panic("unknown first node in discovery chain of type: " + startNode.Type) } return &envoy.RouteConfiguration{ @@ -320,11 +323,12 @@ func makeRouteActionForSingleCluster(target structs.DiscoveryTarget, chain *stru func makeRouteActionForSplitter(splits []*structs.DiscoverySplit, chain *structs.CompiledDiscoveryChain, cfgSnap *proxycfg.ConfigSnapshot) (*envoyroute.Route_Route, error) { clusters := make([]*envoyroute.WeightedCluster_ClusterWeight, 0, len(splits)) for _, split := range splits { - if split.Node.Type != structs.DiscoveryGraphNodeTypeGroupResolver { - return nil, fmt.Errorf("unexpected splitter destination node type: %s", split.Node.Type) + nextNode := chain.Nodes[split.NextNode] + + if nextNode.Type != structs.DiscoveryGraphNodeTypeResolver { + return nil, fmt.Errorf("unexpected splitter destination node type: %s", nextNode.Type) } - groupResolver := split.Node.GroupResolver - target := groupResolver.Target + target := nextNode.Resolver.Target sni := TargetSNI(target, cfgSnap) clusterName := CustomizeClusterName(sni, chain)