Compile down LB policy to disco chain nodes
This commit is contained in:
parent
391d569a45
commit
afb14b6705
|
@ -707,6 +707,7 @@ func (c *compiler) getSplitterNode(sid structs.ServiceID) (*structs.DiscoveryGra
|
||||||
// sanely if there is some sort of graph loop below.
|
// sanely if there is some sort of graph loop below.
|
||||||
c.recordNode(splitNode)
|
c.recordNode(splitNode)
|
||||||
|
|
||||||
|
var hasLB bool
|
||||||
for _, split := range splitter.Splits {
|
for _, split := range splitter.Splits {
|
||||||
compiledSplit := &structs.DiscoverySplit{
|
compiledSplit := &structs.DiscoverySplit{
|
||||||
Weight: split.Weight,
|
Weight: split.Weight,
|
||||||
|
@ -739,6 +740,15 @@ func (c *compiler) getSplitterNode(sid structs.ServiceID) (*structs.DiscoveryGra
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
compiledSplit.NextNode = node.MapKey()
|
compiledSplit.NextNode = node.MapKey()
|
||||||
|
|
||||||
|
// There exists the possibility that a splitter may split between two distinct service names
|
||||||
|
// with distinct hash-based load balancer configs specified in their service resolvers.
|
||||||
|
// We cannot apply multiple hash policies to a splitter node's route action.
|
||||||
|
// Therefore, we attach the first hash-based load balancer config we encounter.
|
||||||
|
if !hasLB && node.LoadBalancer.IsHashBased() {
|
||||||
|
splitNode.LoadBalancer = node.LoadBalancer
|
||||||
|
hasLB = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.usesAdvancedRoutingFeatures = true
|
c.usesAdvancedRoutingFeatures = true
|
||||||
|
@ -851,6 +861,7 @@ RESOLVE_AGAIN:
|
||||||
Target: target.ID,
|
Target: target.ID,
|
||||||
ConnectTimeout: connectTimeout,
|
ConnectTimeout: connectTimeout,
|
||||||
},
|
},
|
||||||
|
LoadBalancer: resolver.LoadBalancer,
|
||||||
}
|
}
|
||||||
|
|
||||||
target.Subset = resolver.Subsets[target.ServiceSubset]
|
target.Subset = resolver.Subsets[target.ServiceSubset]
|
||||||
|
|
|
@ -51,6 +51,7 @@ func TestCompile(t *testing.T) {
|
||||||
"default resolver with external sni": testcase_DefaultResolver_ExternalSNI(),
|
"default resolver with external sni": testcase_DefaultResolver_ExternalSNI(),
|
||||||
"resolver with no entries and inferring defaults": testcase_DefaultResolver(),
|
"resolver with no entries and inferring defaults": testcase_DefaultResolver(),
|
||||||
"default resolver with proxy defaults": testcase_DefaultResolver_WithProxyDefaults(),
|
"default resolver with proxy defaults": testcase_DefaultResolver_WithProxyDefaults(),
|
||||||
|
"loadbalancer config": testcase_LBConfig(),
|
||||||
"service redirect to service with default resolver is not a default chain": testcase_RedirectToDefaultResolverIsNotDefaultChain(),
|
"service redirect to service with default resolver is not a default chain": testcase_RedirectToDefaultResolverIsNotDefaultChain(),
|
||||||
|
|
||||||
"all the bells and whistles": testcase_AllBellsAndWhistles(),
|
"all the bells and whistles": testcase_AllBellsAndWhistles(),
|
||||||
|
@ -1760,6 +1761,17 @@ func testcase_AllBellsAndWhistles() compileTestCase {
|
||||||
"prod": {Filter: "ServiceMeta.env == prod"},
|
"prod": {Filter: "ServiceMeta.env == prod"},
|
||||||
"qa": {Filter: "ServiceMeta.env == qa"},
|
"qa": {Filter: "ServiceMeta.env == qa"},
|
||||||
},
|
},
|
||||||
|
LoadBalancer: structs.LoadBalancer{
|
||||||
|
Policy: "ring_hash",
|
||||||
|
RingHashConfig: structs.RingHashConfig{
|
||||||
|
MaximumRingSize: 100,
|
||||||
|
},
|
||||||
|
HashPolicies: []structs.HashPolicy{
|
||||||
|
{
|
||||||
|
SourceAddress: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&structs.ServiceResolverConfigEntry{
|
&structs.ServiceResolverConfigEntry{
|
||||||
Kind: "service-resolver",
|
Kind: "service-resolver",
|
||||||
|
@ -1821,6 +1833,17 @@ func testcase_AllBellsAndWhistles() compileTestCase {
|
||||||
NextNode: "resolver:v3.main.default.dc1",
|
NextNode: "resolver:v3.main.default.dc1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
LoadBalancer: structs.LoadBalancer{
|
||||||
|
Policy: "ring_hash",
|
||||||
|
RingHashConfig: structs.RingHashConfig{
|
||||||
|
MaximumRingSize: 100,
|
||||||
|
},
|
||||||
|
HashPolicies: []structs.HashPolicy{
|
||||||
|
{
|
||||||
|
SourceAddress: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"resolver:prod.redirected.default.dc1": {
|
"resolver:prod.redirected.default.dc1": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeResolver,
|
Type: structs.DiscoveryGraphNodeTypeResolver,
|
||||||
|
@ -1829,6 +1852,17 @@ func testcase_AllBellsAndWhistles() compileTestCase {
|
||||||
ConnectTimeout: 5 * time.Second,
|
ConnectTimeout: 5 * time.Second,
|
||||||
Target: "prod.redirected.default.dc1",
|
Target: "prod.redirected.default.dc1",
|
||||||
},
|
},
|
||||||
|
LoadBalancer: structs.LoadBalancer{
|
||||||
|
Policy: "ring_hash",
|
||||||
|
RingHashConfig: structs.RingHashConfig{
|
||||||
|
MaximumRingSize: 100,
|
||||||
|
},
|
||||||
|
HashPolicies: []structs.HashPolicy{
|
||||||
|
{
|
||||||
|
SourceAddress: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"resolver:v1.main.default.dc1": {
|
"resolver:v1.main.default.dc1": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeResolver,
|
Type: structs.DiscoveryGraphNodeTypeResolver,
|
||||||
|
@ -2219,6 +2253,167 @@ func testcase_CircularSplit() compileTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testcase_LBConfig() compileTestCase {
|
||||||
|
entries := newEntries()
|
||||||
|
setServiceProtocol(entries, "foo", "http")
|
||||||
|
setServiceProtocol(entries, "bar", "http")
|
||||||
|
setServiceProtocol(entries, "baz", "http")
|
||||||
|
|
||||||
|
entries.AddSplitters(
|
||||||
|
&structs.ServiceSplitterConfigEntry{
|
||||||
|
Kind: "service-splitter",
|
||||||
|
Name: "main",
|
||||||
|
Splits: []structs.ServiceSplit{
|
||||||
|
{Weight: 60, Service: "foo"},
|
||||||
|
{Weight: 20, Service: "bar"},
|
||||||
|
{Weight: 20, Service: "baz"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
entries.AddResolvers(
|
||||||
|
&structs.ServiceResolverConfigEntry{
|
||||||
|
Kind: "service-resolver",
|
||||||
|
Name: "foo",
|
||||||
|
LoadBalancer: structs.LoadBalancer{
|
||||||
|
Policy: "least_request",
|
||||||
|
LeastRequestConfig: structs.LeastRequestConfig{
|
||||||
|
ChoiceCount: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.ServiceResolverConfigEntry{
|
||||||
|
Kind: "service-resolver",
|
||||||
|
Name: "bar",
|
||||||
|
LoadBalancer: structs.LoadBalancer{
|
||||||
|
Policy: "ring_hash",
|
||||||
|
RingHashConfig: structs.RingHashConfig{
|
||||||
|
MaximumRingSize: 101,
|
||||||
|
},
|
||||||
|
HashPolicies: []structs.HashPolicy{
|
||||||
|
{
|
||||||
|
SourceAddress: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.ServiceResolverConfigEntry{
|
||||||
|
Kind: "service-resolver",
|
||||||
|
Name: "baz",
|
||||||
|
LoadBalancer: structs.LoadBalancer{
|
||||||
|
Policy: "maglev",
|
||||||
|
HashPolicies: []structs.HashPolicy{
|
||||||
|
{
|
||||||
|
Field: "cookie",
|
||||||
|
FieldMatchValue: "chocolate-chip",
|
||||||
|
Terminal: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
|
Protocol: "http",
|
||||||
|
StartNode: "splitter:main.default",
|
||||||
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
|
"splitter:main.default": {
|
||||||
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
|
Name: "main.default",
|
||||||
|
Splits: []*structs.DiscoverySplit{
|
||||||
|
{
|
||||||
|
Weight: 60,
|
||||||
|
NextNode: "resolver:foo.default.dc1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Weight: 20,
|
||||||
|
NextNode: "resolver:bar.default.dc1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Weight: 20,
|
||||||
|
NextNode: "resolver:baz.default.dc1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// The LB config from bar is attached because splitters only care about hash-based policies,
|
||||||
|
// and it's the config from bar not baz because we pick the first one we encounter in the Splits.
|
||||||
|
LoadBalancer: structs.LoadBalancer{
|
||||||
|
Policy: "ring_hash",
|
||||||
|
RingHashConfig: structs.RingHashConfig{
|
||||||
|
MaximumRingSize: 101,
|
||||||
|
},
|
||||||
|
HashPolicies: []structs.HashPolicy{
|
||||||
|
{
|
||||||
|
SourceAddress: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Each service's LB config is passed down from the service-resolver to the resolver node
|
||||||
|
"resolver:foo.default.dc1": {
|
||||||
|
Type: structs.DiscoveryGraphNodeTypeResolver,
|
||||||
|
Name: "foo.default.dc1",
|
||||||
|
Resolver: &structs.DiscoveryResolver{
|
||||||
|
Default: true,
|
||||||
|
ConnectTimeout: 5 * time.Second,
|
||||||
|
Target: "foo.default.dc1",
|
||||||
|
},
|
||||||
|
LoadBalancer: structs.LoadBalancer{
|
||||||
|
Policy: "least_request",
|
||||||
|
LeastRequestConfig: structs.LeastRequestConfig{
|
||||||
|
ChoiceCount: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"resolver:bar.default.dc1": {
|
||||||
|
Type: structs.DiscoveryGraphNodeTypeResolver,
|
||||||
|
Name: "bar.default.dc1",
|
||||||
|
Resolver: &structs.DiscoveryResolver{
|
||||||
|
Default: true,
|
||||||
|
ConnectTimeout: 5 * time.Second,
|
||||||
|
Target: "bar.default.dc1",
|
||||||
|
},
|
||||||
|
LoadBalancer: structs.LoadBalancer{
|
||||||
|
Policy: "ring_hash",
|
||||||
|
RingHashConfig: structs.RingHashConfig{
|
||||||
|
MaximumRingSize: 101,
|
||||||
|
},
|
||||||
|
HashPolicies: []structs.HashPolicy{
|
||||||
|
{
|
||||||
|
SourceAddress: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"resolver:baz.default.dc1": {
|
||||||
|
Type: structs.DiscoveryGraphNodeTypeResolver,
|
||||||
|
Name: "baz.default.dc1",
|
||||||
|
Resolver: &structs.DiscoveryResolver{
|
||||||
|
Default: true,
|
||||||
|
ConnectTimeout: 5 * time.Second,
|
||||||
|
Target: "baz.default.dc1",
|
||||||
|
},
|
||||||
|
LoadBalancer: structs.LoadBalancer{
|
||||||
|
Policy: "maglev",
|
||||||
|
HashPolicies: []structs.HashPolicy{
|
||||||
|
{
|
||||||
|
Field: "cookie",
|
||||||
|
FieldMatchValue: "chocolate-chip",
|
||||||
|
Terminal: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Targets: map[string]*structs.DiscoveryTarget{
|
||||||
|
"foo.default.dc1": newTarget("foo", "", "default", "dc1", nil),
|
||||||
|
"bar.default.dc1": newTarget("bar", "", "default", "dc1", nil),
|
||||||
|
"baz.default.dc1": newTarget("baz", "", "default", "dc1", nil),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return compileTestCase{entries: entries, expect: expect}
|
||||||
|
}
|
||||||
|
|
||||||
func newSimpleRoute(name string, muts ...func(*structs.ServiceRoute)) structs.ServiceRoute {
|
func newSimpleRoute(name string, muts ...func(*structs.ServiceRoute)) structs.ServiceRoute {
|
||||||
r := structs.ServiceRoute{
|
r := structs.ServiceRoute{
|
||||||
Match: &structs.ServiceRouteMatch{
|
Match: &structs.ServiceRouteMatch{
|
||||||
|
|
|
@ -107,6 +107,9 @@ type DiscoveryGraphNode struct {
|
||||||
|
|
||||||
// fields for Type==resolver
|
// fields for Type==resolver
|
||||||
Resolver *DiscoveryResolver `json:",omitempty"`
|
Resolver *DiscoveryResolver `json:",omitempty"`
|
||||||
|
|
||||||
|
// shared by Type==resolver || Type==splitter
|
||||||
|
LoadBalancer LoadBalancer `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DiscoveryGraphNode) IsRouter() bool {
|
func (s *DiscoveryGraphNode) IsRouter() bool {
|
||||||
|
|
Loading…
Reference in a new issue