open-consul/agent/consul/discoverychain/compile_test.go

2072 lines
58 KiB
Go
Raw Normal View History

package discoverychain
import (
"testing"
"time"
"github.com/hashicorp/consul/agent/structs"
"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
connect: reconcile how upstream configuration works with discovery chains (#6225) * connect: reconcile how upstream configuration works with discovery chains The following upstream config fields for connect sidecars sanely integrate into discovery chain resolution: - Destination Namespace/Datacenter: Compilation occurs locally but using different default values for namespaces and datacenters. The xDS clusters that are created are named as they normally would be. - Mesh Gateway Mode (single upstream): If set this value overrides any value computed for any resolver for the entire discovery chain. The xDS clusters that are created may be named differently (see below). - Mesh Gateway Mode (whole sidecar): If set this value overrides any value computed for any resolver for the entire discovery chain. If this is specifically overridden for a single upstream this value is ignored in that case. The xDS clusters that are created may be named differently (see below). - Protocol (in opaque config): If set this value overrides the value computed when evaluating the entire discovery chain. If the normal chain would be TCP or if this override is set to TCP then the result is that we explicitly disable L7 Routing and Splitting. The xDS clusters that are created may be named differently (see below). - Connect Timeout (in opaque config): If set this value overrides the value for any resolver in the entire discovery chain. The xDS clusters that are created may be named differently (see below). If any of the above overrides affect the actual result of compiling the discovery chain (i.e. "tcp" becomes "grpc" instead of being a no-op override to "tcp") then the relevant parameters are hashed and provided to the xDS layer as a prefix for use in naming the Clusters. This is to ensure that if one Upstream discovery chain has no overrides and tangentially needs a cluster named "api.default.XXX", and another Upstream does have overrides for "api.default.XXX" that they won't cross-pollinate against the operator's wishes. Fixes #6159
2019-08-02 03:03:34 +00:00
setup func(req *CompileRequest)
// expect: the GroupResolverNodes map should have nil values
expect *structs.CompiledDiscoveryChain
// expectIsDefault tests behavior of CompiledDiscoveryChain.IsDefault()
expectIsDefault bool
connect: reconcile how upstream configuration works with discovery chains (#6225) * connect: reconcile how upstream configuration works with discovery chains The following upstream config fields for connect sidecars sanely integrate into discovery chain resolution: - Destination Namespace/Datacenter: Compilation occurs locally but using different default values for namespaces and datacenters. The xDS clusters that are created are named as they normally would be. - Mesh Gateway Mode (single upstream): If set this value overrides any value computed for any resolver for the entire discovery chain. The xDS clusters that are created may be named differently (see below). - Mesh Gateway Mode (whole sidecar): If set this value overrides any value computed for any resolver for the entire discovery chain. If this is specifically overridden for a single upstream this value is ignored in that case. The xDS clusters that are created may be named differently (see below). - Protocol (in opaque config): If set this value overrides the value computed when evaluating the entire discovery chain. If the normal chain would be TCP or if this override is set to TCP then the result is that we explicitly disable L7 Routing and Splitting. The xDS clusters that are created may be named differently (see below). - Connect Timeout (in opaque config): If set this value overrides the value for any resolver in the entire discovery chain. The xDS clusters that are created may be named differently (see below). If any of the above overrides affect the actual result of compiling the discovery chain (i.e. "tcp" becomes "grpc" instead of being a no-op override to "tcp") then the relevant parameters are hashed and provided to the xDS layer as a prefix for use in naming the Clusters. This is to ensure that if one Upstream discovery chain has no overrides and tangentially needs a cluster named "api.default.XXX", and another Upstream does have overrides for "api.default.XXX" that they won't cross-pollinate against the operator's wishes. Fixes #6159
2019-08-02 03:03:34 +00:00
expectCustom bool
expectErr string
expectGraphErr bool
}
func TestCompile(t *testing.T) {
t.Parallel()
// TODO(rb): test circular dependency?
cases := map[string]compileTestCase{
"router with defaults": testcase_JustRouterWithDefaults(),
"router with defaults and resolver": testcase_RouterWithDefaults_NoSplit_WithResolver(),
"router with defaults and noop split": testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver(),
"router with defaults and noop split and resolver": testcase_RouterWithDefaults_WithNoopSplit_WithResolver(),
"route bypasses splitter": testcase_RouteBypassesSplit(),
"noop split": testcase_NoopSplit_DefaultResolver(),
"noop split with protocol from proxy defaults": testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults(),
"noop split with resolver": testcase_NoopSplit_WithResolver(),
"subset split": testcase_SubsetSplit(),
"service split": testcase_ServiceSplit(),
"split bypasses next splitter": testcase_SplitBypassesSplit(),
"service redirect": testcase_ServiceRedirect(),
"service and subset redirect": testcase_ServiceAndSubsetRedirect(),
"datacenter redirect": testcase_DatacenterRedirect(),
"service failover": testcase_ServiceFailover(),
"service and subset failover": testcase_ServiceAndSubsetFailover(),
"datacenter failover": testcase_DatacenterFailover(),
"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(),
"default resolver with proxy defaults": testcase_DefaultResolver_WithProxyDefaults(),
"service redirect to service with default resolver is not a default chain": testcase_RedirectToDefaultResolverIsNotDefaultChain(),
// TODO(rb): handle this case better: "circular split": testcase_CircularSplit(),
"all the bells and whistles": testcase_AllBellsAndWhistles(),
"multi dc canary": testcase_MultiDatacenterCanary(),
// various errors
"splitter requires valid protocol": testcase_SplitterRequiresValidProtocol(),
"router requires valid protocol": testcase_RouterRequiresValidProtocol(),
"split to unsplittable protocol": testcase_SplitToUnsplittableProtocol(),
"route to unroutable protocol": testcase_RouteToUnroutableProtocol(),
"failover crosses protocols": testcase_FailoverCrossesProtocols(),
"redirect crosses protocols": testcase_RedirectCrossesProtocols(),
"redirect to missing subset": testcase_RedirectToMissingSubset(),
connect: reconcile how upstream configuration works with discovery chains (#6225) * connect: reconcile how upstream configuration works with discovery chains The following upstream config fields for connect sidecars sanely integrate into discovery chain resolution: - Destination Namespace/Datacenter: Compilation occurs locally but using different default values for namespaces and datacenters. The xDS clusters that are created are named as they normally would be. - Mesh Gateway Mode (single upstream): If set this value overrides any value computed for any resolver for the entire discovery chain. The xDS clusters that are created may be named differently (see below). - Mesh Gateway Mode (whole sidecar): If set this value overrides any value computed for any resolver for the entire discovery chain. If this is specifically overridden for a single upstream this value is ignored in that case. The xDS clusters that are created may be named differently (see below). - Protocol (in opaque config): If set this value overrides the value computed when evaluating the entire discovery chain. If the normal chain would be TCP or if this override is set to TCP then the result is that we explicitly disable L7 Routing and Splitting. The xDS clusters that are created may be named differently (see below). - Connect Timeout (in opaque config): If set this value overrides the value for any resolver in the entire discovery chain. The xDS clusters that are created may be named differently (see below). If any of the above overrides affect the actual result of compiling the discovery chain (i.e. "tcp" becomes "grpc" instead of being a no-op override to "tcp") then the relevant parameters are hashed and provided to the xDS layer as a prefix for use in naming the Clusters. This is to ensure that if one Upstream discovery chain has no overrides and tangentially needs a cluster named "api.default.XXX", and another Upstream does have overrides for "api.default.XXX" that they won't cross-pollinate against the operator's wishes. Fixes #6159
2019-08-02 03:03:34 +00:00
// overrides
"resolver with protocol from override": testcase_ResolverProtocolOverride(),
"resolver with protocol from override ignored": testcase_ResolverProtocolOverrideIgnored(),
"router ignored due to protocol override": testcase_RouterIgnored_ResolverProtocolOverride(),
}
for name, tc := range cases {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
// sanity check entries are normalized and valid
for _, entry := range tc.entries.Routers {
require.NoError(t, entry.Normalize())
require.NoError(t, entry.Validate())
}
for _, entry := range tc.entries.Splitters {
require.NoError(t, entry.Normalize())
require.NoError(t, entry.Validate())
}
for _, entry := range tc.entries.Resolvers {
require.NoError(t, entry.Normalize())
require.NoError(t, entry.Validate())
}
connect: reconcile how upstream configuration works with discovery chains (#6225) * connect: reconcile how upstream configuration works with discovery chains The following upstream config fields for connect sidecars sanely integrate into discovery chain resolution: - Destination Namespace/Datacenter: Compilation occurs locally but using different default values for namespaces and datacenters. The xDS clusters that are created are named as they normally would be. - Mesh Gateway Mode (single upstream): If set this value overrides any value computed for any resolver for the entire discovery chain. The xDS clusters that are created may be named differently (see below). - Mesh Gateway Mode (whole sidecar): If set this value overrides any value computed for any resolver for the entire discovery chain. If this is specifically overridden for a single upstream this value is ignored in that case. The xDS clusters that are created may be named differently (see below). - Protocol (in opaque config): If set this value overrides the value computed when evaluating the entire discovery chain. If the normal chain would be TCP or if this override is set to TCP then the result is that we explicitly disable L7 Routing and Splitting. The xDS clusters that are created may be named differently (see below). - Connect Timeout (in opaque config): If set this value overrides the value for any resolver in the entire discovery chain. The xDS clusters that are created may be named differently (see below). If any of the above overrides affect the actual result of compiling the discovery chain (i.e. "tcp" becomes "grpc" instead of being a no-op override to "tcp") then the relevant parameters are hashed and provided to the xDS layer as a prefix for use in naming the Clusters. This is to ensure that if one Upstream discovery chain has no overrides and tangentially needs a cluster named "api.default.XXX", and another Upstream does have overrides for "api.default.XXX" that they won't cross-pollinate against the operator's wishes. Fixes #6159
2019-08-02 03:03:34 +00:00
req := CompileRequest{
ServiceName: "main",
CurrentNamespace: "default",
CurrentDatacenter: "dc1",
InferDefaults: true,
Entries: tc.entries,
connect: reconcile how upstream configuration works with discovery chains (#6225) * connect: reconcile how upstream configuration works with discovery chains The following upstream config fields for connect sidecars sanely integrate into discovery chain resolution: - Destination Namespace/Datacenter: Compilation occurs locally but using different default values for namespaces and datacenters. The xDS clusters that are created are named as they normally would be. - Mesh Gateway Mode (single upstream): If set this value overrides any value computed for any resolver for the entire discovery chain. The xDS clusters that are created may be named differently (see below). - Mesh Gateway Mode (whole sidecar): If set this value overrides any value computed for any resolver for the entire discovery chain. If this is specifically overridden for a single upstream this value is ignored in that case. The xDS clusters that are created may be named differently (see below). - Protocol (in opaque config): If set this value overrides the value computed when evaluating the entire discovery chain. If the normal chain would be TCP or if this override is set to TCP then the result is that we explicitly disable L7 Routing and Splitting. The xDS clusters that are created may be named differently (see below). - Connect Timeout (in opaque config): If set this value overrides the value for any resolver in the entire discovery chain. The xDS clusters that are created may be named differently (see below). If any of the above overrides affect the actual result of compiling the discovery chain (i.e. "tcp" becomes "grpc" instead of being a no-op override to "tcp") then the relevant parameters are hashed and provided to the xDS layer as a prefix for use in naming the Clusters. This is to ensure that if one Upstream discovery chain has no overrides and tangentially needs a cluster named "api.default.XXX", and another Upstream does have overrides for "api.default.XXX" that they won't cross-pollinate against the operator's wishes. Fixes #6159
2019-08-02 03:03:34 +00:00
}
if tc.setup != nil {
tc.setup(&req)
}
res, err := Compile(req)
if tc.expectErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectErr)
_, ok := err.(*structs.ConfigEntryGraphError)
if tc.expectGraphErr {
require.True(t, ok, "%T is not a *ConfigEntryGraphError", err)
} else {
require.False(t, ok, "did not expect a *ConfigEntryGraphError here: %v", err)
}
} else {
require.NoError(t, err)
// Avoid requiring unnecessary test boilerplate and inject these
// ourselves.
tc.expect.ServiceName = "main"
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
}
}
connect: reconcile how upstream configuration works with discovery chains (#6225) * connect: reconcile how upstream configuration works with discovery chains The following upstream config fields for connect sidecars sanely integrate into discovery chain resolution: - Destination Namespace/Datacenter: Compilation occurs locally but using different default values for namespaces and datacenters. The xDS clusters that are created are named as they normally would be. - Mesh Gateway Mode (single upstream): If set this value overrides any value computed for any resolver for the entire discovery chain. The xDS clusters that are created may be named differently (see below). - Mesh Gateway Mode (whole sidecar): If set this value overrides any value computed for any resolver for the entire discovery chain. If this is specifically overridden for a single upstream this value is ignored in that case. The xDS clusters that are created may be named differently (see below). - Protocol (in opaque config): If set this value overrides the value computed when evaluating the entire discovery chain. If the normal chain would be TCP or if this override is set to TCP then the result is that we explicitly disable L7 Routing and Splitting. The xDS clusters that are created may be named differently (see below). - Connect Timeout (in opaque config): If set this value overrides the value for any resolver in the entire discovery chain. The xDS clusters that are created may be named differently (see below). If any of the above overrides affect the actual result of compiling the discovery chain (i.e. "tcp" becomes "grpc" instead of being a no-op override to "tcp") then the relevant parameters are hashed and provided to the xDS layer as a prefix for use in naming the Clusters. This is to ensure that if one Upstream discovery chain has no overrides and tangentially needs a cluster named "api.default.XXX", and another Upstream does have overrides for "api.default.XXX" that they won't cross-pollinate against the operator's wishes. Fixes #6159
2019-08-02 03:03:34 +00:00
if tc.expectCustom {
require.NotEmpty(t, res.CustomizationHash)
res.CustomizationHash = ""
} else {
require.Empty(t, res.CustomizationHash)
}
require.Equal(t, tc.expect, res)
require.Equal(t, tc.expectIsDefault, res.IsDefault())
}
})
}
}
func testcase_JustRouterWithDefaults() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
entries.AddRouters(
&structs.ServiceRouterConfigEntry{
Kind: "service-router",
Name: "main",
},
)
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"),
},
},
},
},
},
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}
}
func testcase_RouterWithDefaults_NoSplit_WithResolver() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
entries.AddRouters(
&structs.ServiceRouterConfigEntry{
Kind: "service-router",
Name: "main",
},
)
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
ConnectTimeout: 33 * time.Second,
},
)
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"),
},
},
},
},
},
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}
}
func testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
entries.AddRouters(
&structs.ServiceRouterConfigEntry{
Kind: "service-router",
Name: "main",
},
)
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 100},
},
},
)
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"),
},
},
},
},
},
},
},
},
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}
}
func testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults() compileTestCase {
entries := newEntries()
setGlobalProxyProtocol(entries, "http")
entries.AddRouters(
&structs.ServiceRouterConfigEntry{
Kind: "service-router",
Name: "main",
},
)
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 100},
},
},
)
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"),
},
},
},
},
},
},
},
},
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}
}
func testcase_RouterWithDefaults_WithNoopSplit_WithResolver() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
entries.AddRouters(
&structs.ServiceRouterConfigEntry{
Kind: "service-router",
Name: "main",
},
)
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 100},
},
},
)
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
ConnectTimeout: 33 * time.Second,
},
)
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"),
},
},
},
},
},
},
},
},
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}
}
func testcase_RouteBypassesSplit() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
setServiceProtocol(entries, "other", "http")
entries.AddRouters(
&structs.ServiceRouterConfigEntry{
Kind: "service-router",
Name: "main",
Routes: []structs.ServiceRoute{
// route direct subset reference (bypass split)
newSimpleRoute("other", func(r *structs.ServiceRoute) {
r.Destination.ServiceSubset = "bypass"
}),
},
},
)
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "other",
Splits: []structs.ServiceSplit{
{Weight: 100, Service: "ignored"},
},
},
)
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "other",
Subsets: map[string]structs.ServiceResolverSubset{
"bypass": {
Filter: "Service.Meta.version == bypass",
},
},
},
)
router := entries.GetRouter("main")
resolverOther := entries.GetResolver("other")
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"),
},
},
},
{
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"),
},
},
},
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_NoopSplit_DefaultResolver() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 100},
},
},
)
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"),
},
},
},
},
},
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}
}
func testcase_NoopSplit_WithResolver() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 100},
},
},
)
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
ConnectTimeout: 33 * time.Second,
},
)
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"),
},
},
},
},
},
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}
}
func testcase_SubsetSplit() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 60, ServiceSubset: "v2"},
{Weight: 40, ServiceSubset: "v1"},
},
},
)
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
Subsets: map[string]structs.ServiceResolverSubset{
"v1": {
Filter: "Service.Meta.version == 1",
},
"v2": {
Filter: "Service.Meta.version == 2",
},
},
},
)
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"),
},
},
},
{
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"),
},
},
},
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_ServiceSplit() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
setServiceProtocol(entries, "foo", "http")
setServiceProtocol(entries, "bar", "http")
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 60, Service: "foo"},
{Weight: 40, Service: "bar"},
},
},
)
resolverFoo := newDefaultServiceResolver("foo")
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"),
},
},
},
{
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"),
},
},
},
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_SplitBypassesSplit() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
setServiceProtocol(entries, "next", "http")
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "main",
Splits: []structs.ServiceSplit{
{
Weight: 100,
Service: "next",
ServiceSubset: "bypassed",
},
},
},
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "next",
Splits: []structs.ServiceSplit{
{
Weight: 100,
ServiceSubset: "not-bypassed",
},
},
},
)
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "next",
Subsets: map[string]structs.ServiceResolverSubset{
"bypassed": {
Filter: "Service.Meta.version == bypass",
},
"not-bypassed": {
Filter: "Service.Meta.version != bypass",
},
},
},
)
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"),
},
},
},
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_ServiceRedirect() compileTestCase {
entries := newEntries()
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
Redirect: &structs.ServiceResolverRedirect{
Service: "other",
},
},
)
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"),
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_ServiceAndSubsetRedirect() compileTestCase {
entries := newEntries()
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
Redirect: &structs.ServiceResolverRedirect{
Service: "other",
ServiceSubset: "v2",
},
},
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "other",
Subsets: map[string]structs.ServiceResolverSubset{
"v1": {
Filter: "Service.Meta.version == 1",
},
"v2": {
Filter: "Service.Meta.version == 2",
},
},
},
)
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"),
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_DatacenterRedirect() compileTestCase {
entries := newEntries()
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
Redirect: &structs.ServiceResolverRedirect{
Datacenter: "dc9",
},
},
)
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"),
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_ServiceFailover() compileTestCase {
entries := newEntries()
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
Failover: map[string]structs.ServiceResolverFailover{
"*": {Service: "backup"},
},
},
)
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"),
},
},
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_ServiceAndSubsetFailover() compileTestCase {
entries := newEntries()
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
Subsets: map[string]structs.ServiceResolverSubset{
"backup": {
Filter: "Service.Meta.version == backup",
},
},
Failover: map[string]structs.ServiceResolverFailover{
"*": {ServiceSubset: "backup"},
},
},
)
resolver := entries.GetResolver("main")
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"),
},
},
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_DatacenterFailover() compileTestCase {
entries := newEntries()
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
Failover: map[string]structs.ServiceResolverFailover{
"*": {Datacenters: []string{"dc2", "dc4"}},
},
},
)
resolver := entries.GetResolver("main")
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"),
},
},
},
},
Resolvers: map[string]*structs.ServiceResolverConfigEntry{
"main": resolver,
},
Targets: []structs.DiscoveryTarget{
newTarget("main", "", "default", "dc1"),
newTarget("main", "", "default", "dc2"),
newTarget("main", "", "default", "dc4"),
},
GroupResolverNodes: map[structs.DiscoveryTarget]*structs.DiscoveryGraphNode{
newTarget("main", "", "default", "dc1"): nil,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_NoopSplit_WithDefaultSubset() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 100},
},
},
)
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
DefaultSubset: "v2",
Subsets: map[string]structs.ServiceResolverSubset{
"v1": {Filter: "Service.Meta.version == 1"},
"v2": {Filter: "Service.Meta.version == 2"},
},
},
)
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"),
},
},
},
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_DefaultResolver() compileTestCase {
entries := newEntries()
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"),
},
},
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}
}
func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
entries := newEntries()
entries.GlobalProxy = &structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
Config: map[string]interface{}{
"protocol": "grpc",
},
MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote,
},
}
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,
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}
}
func testcase_RedirectToDefaultResolverIsNotDefaultChain() compileTestCase {
entries := newEntries()
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "main",
Redirect: &structs.ServiceResolverRedirect{
Service: "other",
},
},
)
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"),
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect, expectIsDefault: false /*being explicit here because this is the whole point of this test*/}
}
func testcase_Resolve_WithDefaultSubset() compileTestCase {
entries := newEntries()
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
DefaultSubset: "v2",
Subsets: map[string]structs.ServiceResolverSubset{
"v1": {Filter: "Service.Meta.version == 1"},
"v2": {Filter: "Service.Meta.version == 2"},
},
},
)
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"),
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_CircularSplit() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
setServiceProtocol(entries, "other", "http")
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 60, Service: "other"},
{Weight: 40, Service: "main"}, // goes straight to resolver
},
},
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "other",
Splits: []structs.ServiceSplit{
{Weight: 60, Service: "main"},
{Weight: 40, Service: "other"}, // goes straight to resolver
},
},
)
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"),
},
},
},
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_MultiDatacenterCanary() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
setServiceProtocol(entries, "main-dc2", "http")
setServiceProtocol(entries, "main-dc3", "http")
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 60, Service: "main-dc2"},
{Weight: 40, Service: "main-dc3"},
},
},
)
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main-dc2",
Redirect: &structs.ServiceResolverRedirect{
Service: "main",
Datacenter: "dc2",
},
},
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main-dc3",
Redirect: &structs.ServiceResolverRedirect{
Service: "main",
Datacenter: "dc3",
},
},
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
ConnectTimeout: 33 * time.Second,
},
)
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"),
},
},
},
{
Weight: 40,
Node: &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeGroupResolver,
Name: "main",
GroupResolver: &structs.DiscoveryGroupResolver{
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_AllBellsAndWhistles() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "http")
setServiceProtocol(entries, "svc-redirect", "http")
setServiceProtocol(entries, "svc-redirect-again", "http")
setServiceProtocol(entries, "svc-split", "http")
setServiceProtocol(entries, "svc-split-again", "http")
setServiceProtocol(entries, "svc-split-one-more-time", "http")
setServiceProtocol(entries, "redirected", "http")
entries.AddRouters(
&structs.ServiceRouterConfigEntry{
Kind: "service-router",
Name: "main",
Routes: []structs.ServiceRoute{
newSimpleRoute("svc-redirect"), // double redirected to a default subset
newSimpleRoute("svc-split"), // one split is split further
},
},
)
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "svc-split",
Splits: []structs.ServiceSplit{
{Weight: 60, Service: "svc-redirect"}, // double redirected to a default subset
{Weight: 40, Service: "svc-split-again"}, // split again
},
},
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "svc-split-again",
Splits: []structs.ServiceSplit{
{Weight: 75, Service: "main", ServiceSubset: "v1"},
{Weight: 25, Service: "svc-split-one-more-time"},
},
},
&structs.ServiceSplitterConfigEntry{
Kind: "service-splitter",
Name: "svc-split-one-more-time",
Splits: []structs.ServiceSplit{
{Weight: 80, Service: "main", ServiceSubset: "v2"},
{Weight: 20, Service: "main", ServiceSubset: "v3"},
},
},
)
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "svc-redirect",
Redirect: &structs.ServiceResolverRedirect{
Service: "svc-redirect-again",
},
},
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "svc-redirect-again",
Redirect: &structs.ServiceResolverRedirect{
Service: "redirected",
},
},
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "redirected",
DefaultSubset: "prod",
Subsets: map[string]structs.ServiceResolverSubset{
"prod": {Filter: "ServiceMeta.env == prod"},
"qa": {Filter: "ServiceMeta.env == qa"},
},
},
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
DefaultSubset: "default-subset",
Subsets: map[string]structs.ServiceResolverSubset{
"v1": {Filter: "Service.Meta.version == 1"},
"v2": {Filter: "Service.Meta.version == 2"},
"v3": {Filter: "Service.Meta.version == 3"},
"default-subset": {OnlyPassing: true},
},
},
)
var (
router = entries.GetRouter("main")
resolverMain = entries.GetResolver("main")
resolverRedirected = entries.GetResolver("redirected")
)
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"),
},
},
},
{
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: 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"),
},
},
},
},
},
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,
},
}
return compileTestCase{entries: entries, expect: expect}
}
func testcase_SplitterRequiresValidProtocol() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "tcp")
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 90, Namespace: "v1"},
{Weight: 10, Namespace: "v2"},
},
},
)
return compileTestCase{
entries: entries,
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
}
}
func testcase_RouterRequiresValidProtocol() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "tcp")
entries.AddRouters(
&structs.ServiceRouterConfigEntry{
Kind: structs.ServiceRouter,
Name: "main",
Routes: []structs.ServiceRoute{
{
Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{
PathExact: "/other",
},
},
Destination: &structs.ServiceRouteDestination{
Namespace: "other",
},
},
},
},
)
return compileTestCase{
entries: entries,
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
}
}
func testcase_SplitToUnsplittableProtocol() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "tcp")
setServiceProtocol(entries, "other", "tcp")
entries.AddSplitters(
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 90},
{Weight: 10, Service: "other"},
},
},
)
return compileTestCase{
entries: entries,
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
}
}
func testcase_RouteToUnroutableProtocol() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "tcp")
setServiceProtocol(entries, "other", "tcp")
entries.AddRouters(
&structs.ServiceRouterConfigEntry{
Kind: structs.ServiceRouter,
Name: "main",
Routes: []structs.ServiceRoute{
{
Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{
PathExact: "/other",
},
},
Destination: &structs.ServiceRouteDestination{
Service: "other",
},
},
},
},
)
return compileTestCase{
entries: entries,
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
}
}
func testcase_FailoverCrossesProtocols() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "grpc")
setServiceProtocol(entries, "other", "tcp")
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "main",
Failover: map[string]structs.ServiceResolverFailover{
"*": structs.ServiceResolverFailover{
Service: "other",
},
},
},
)
return compileTestCase{
entries: entries,
expectErr: "uses inconsistent protocols",
expectGraphErr: true,
}
}
func testcase_RedirectCrossesProtocols() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "grpc")
setServiceProtocol(entries, "other", "tcp")
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "main",
Redirect: &structs.ServiceResolverRedirect{
Service: "other",
},
},
)
return compileTestCase{
entries: entries,
expectErr: "uses inconsistent protocols",
expectGraphErr: true,
}
}
func testcase_RedirectToMissingSubset() compileTestCase {
entries := newEntries()
entries.AddResolvers(
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "other",
ConnectTimeout: 33 * time.Second,
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "main",
Redirect: &structs.ServiceResolverRedirect{
Service: "other",
ServiceSubset: "v1",
},
},
)
return compileTestCase{
entries: entries,
expectErr: `does not have a subset named "v1"`,
expectGraphErr: true,
}
}
connect: reconcile how upstream configuration works with discovery chains (#6225) * connect: reconcile how upstream configuration works with discovery chains The following upstream config fields for connect sidecars sanely integrate into discovery chain resolution: - Destination Namespace/Datacenter: Compilation occurs locally but using different default values for namespaces and datacenters. The xDS clusters that are created are named as they normally would be. - Mesh Gateway Mode (single upstream): If set this value overrides any value computed for any resolver for the entire discovery chain. The xDS clusters that are created may be named differently (see below). - Mesh Gateway Mode (whole sidecar): If set this value overrides any value computed for any resolver for the entire discovery chain. If this is specifically overridden for a single upstream this value is ignored in that case. The xDS clusters that are created may be named differently (see below). - Protocol (in opaque config): If set this value overrides the value computed when evaluating the entire discovery chain. If the normal chain would be TCP or if this override is set to TCP then the result is that we explicitly disable L7 Routing and Splitting. The xDS clusters that are created may be named differently (see below). - Connect Timeout (in opaque config): If set this value overrides the value for any resolver in the entire discovery chain. The xDS clusters that are created may be named differently (see below). If any of the above overrides affect the actual result of compiling the discovery chain (i.e. "tcp" becomes "grpc" instead of being a no-op override to "tcp") then the relevant parameters are hashed and provided to the xDS layer as a prefix for use in naming the Clusters. This is to ensure that if one Upstream discovery chain has no overrides and tangentially needs a cluster named "api.default.XXX", and another Upstream does have overrides for "api.default.XXX" that they won't cross-pollinate against the operator's wishes. Fixes #6159
2019-08-02 03:03:34 +00:00
func testcase_ResolverProtocolOverride() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "grpc")
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"),
},
},
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,
expectCustom: true,
setup: func(req *CompileRequest) {
req.OverrideProtocol = "http2"
},
}
}
func testcase_ResolverProtocolOverrideIgnored() compileTestCase {
// This shows that if you try to override the protocol to its current value
// the override is completely ignored.
entries := newEntries()
setServiceProtocol(entries, "main", "http2")
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"),
},
},
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,
setup: func(req *CompileRequest) {
req.OverrideProtocol = "http2"
},
}
}
func testcase_RouterIgnored_ResolverProtocolOverride() compileTestCase {
entries := newEntries()
setServiceProtocol(entries, "main", "grpc")
entries.AddRouters(
&structs.ServiceRouterConfigEntry{
Kind: "service-router",
Name: "main",
},
)
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"),
},
},
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,
expectCustom: true,
setup: func(req *CompileRequest) {
req.OverrideProtocol = "tcp"
},
}
}
func newSimpleRoute(name string, muts ...func(*structs.ServiceRoute)) structs.ServiceRoute {
r := structs.ServiceRoute{
Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{PathPrefix: "/" + name},
},
Destination: &structs.ServiceRouteDestination{Service: name},
}
for _, mut := range muts {
mut(&r)
}
return r
}
func setGlobalProxyProtocol(entries *structs.DiscoveryChainConfigEntries, protocol string) {
entries.GlobalProxy = &structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
Config: map[string]interface{}{
"protocol": protocol,
},
}
}
func setServiceProtocol(entries *structs.DiscoveryChainConfigEntries, name, protocol string) {
entries.AddServices(&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: name,
Protocol: protocol,
})
}
func newEntries() *structs.DiscoveryChainConfigEntries {
return &structs.DiscoveryChainConfigEntries{
Routers: make(map[string]*structs.ServiceRouterConfigEntry),
Splitters: make(map[string]*structs.ServiceSplitterConfigEntry),
Resolvers: make(map[string]*structs.ServiceResolverConfigEntry),
}
}
func newTarget(service, serviceSubset, namespace, datacenter string) structs.DiscoveryTarget {
return structs.DiscoveryTarget{
Service: service,
ServiceSubset: serviceSubset,
Namespace: namespace,
Datacenter: datacenter,
}
}