Allow cross-partition references in disco chain

* Add partition fields to targets like service route destinations
* Update validation to prevent cross-DC + cross-partition references
* Handle partitions when reading config entries for disco chain
* Encode partition in compiled targets
This commit is contained in:
freddygv 2021-11-12 18:57:05 -07:00
parent b1605639fc
commit 5c1f7aa372
8 changed files with 228 additions and 124 deletions

View File

@ -23,6 +23,7 @@ import (
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/cache" "github.com/hashicorp/consul/agent/cache"
"github.com/hashicorp/consul/agent/checks" "github.com/hashicorp/consul/agent/checks"
"github.com/hashicorp/consul/agent/consul" "github.com/hashicorp/consul/agent/consul"
@ -4085,6 +4086,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
Service: "carrot", Service: "carrot",
ServiceSubset: "kale", ServiceSubset: "kale",
Namespace: "leek", Namespace: "leek",
Partition: acl.DefaultPartitionName,
PrefixRewrite: "/alternate", PrefixRewrite: "/alternate",
RequestTimeout: 99 * time.Second, RequestTimeout: 99 * time.Second,
NumRetries: 12345, NumRetries: 12345,

View File

@ -185,7 +185,7 @@ type customizationMarkers struct {
// the String() method on the type itself. It is this way to be more // the String() method on the type itself. It is this way to be more
// consistent with other string ids within the discovery chain. // consistent with other string ids within the discovery chain.
func serviceIDString(sid structs.ServiceID) string { func serviceIDString(sid structs.ServiceID) string {
return fmt.Sprintf("%s.%s", sid.ID, sid.NamespaceOrDefault()) return fmt.Sprintf("%s.%s.%s", sid.ID, sid.NamespaceOrDefault(), sid.PartitionOrDefault())
} }
func (m *customizationMarkers) IsZero() bool { func (m *customizationMarkers) IsZero() bool {
@ -213,10 +213,10 @@ func (c *compiler) recordServiceProtocol(sid structs.ServiceID) error {
if serviceDefault := c.entries.GetService(sid); serviceDefault != nil { if serviceDefault := c.entries.GetService(sid); serviceDefault != nil {
return c.recordProtocol(sid, serviceDefault.Protocol) return c.recordProtocol(sid, serviceDefault.Protocol)
} }
if c.entries.GlobalProxy != nil { if proxyDefault := c.entries.GetProxyDefaults(sid.PartitionOrDefault()); proxyDefault != nil {
var cfg proxyConfig var cfg proxyConfig
// Ignore errors and fallback on defaults if it does happen. // Ignore errors and fallback on defaults if it does happen.
_ = mapstructure.WeakDecode(c.entries.GlobalProxy.Config, &cfg) _ = mapstructure.WeakDecode(proxyDefault.Config, &cfg)
if cfg.Protocol != "" { if cfg.Protocol != "" {
return c.recordProtocol(sid, cfg.Protocol) return c.recordProtocol(sid, cfg.Protocol)
} }
@ -567,11 +567,12 @@ func (c *compiler) assembleChain() error {
dest = &structs.ServiceRouteDestination{ dest = &structs.ServiceRouteDestination{
Service: c.serviceName, Service: c.serviceName,
Namespace: router.NamespaceOrDefault(), Namespace: router.NamespaceOrDefault(),
Partition: router.PartitionOrDefault(),
} }
} }
svc := defaultIfEmpty(dest.Service, c.serviceName) svc := defaultIfEmpty(dest.Service, c.serviceName)
destNamespace := defaultIfEmpty(dest.Namespace, router.NamespaceOrDefault()) destNamespace := defaultIfEmpty(dest.Namespace, router.NamespaceOrDefault())
destPartition := router.PartitionOrDefault() destPartition := defaultIfEmpty(dest.Partition, router.PartitionOrDefault())
// Check to see if the destination is eligible for splitting. // Check to see if the destination is eligible for splitting.
var ( var (
@ -602,7 +603,7 @@ func (c *compiler) assembleChain() error {
} }
defaultRoute := &structs.DiscoveryRoute{ defaultRoute := &structs.DiscoveryRoute{
Definition: newDefaultServiceRoute(router.Name, router.NamespaceOrDefault()), Definition: newDefaultServiceRoute(router.Name, router.NamespaceOrDefault(), router.PartitionOrDefault()),
NextNode: defaultDestinationNode.MapKey(), NextNode: defaultDestinationNode.MapKey(),
} }
routeNode.Routes = append(routeNode.Routes, defaultRoute) routeNode.Routes = append(routeNode.Routes, defaultRoute)
@ -613,7 +614,7 @@ func (c *compiler) assembleChain() error {
return nil return nil
} }
func newDefaultServiceRoute(serviceName string, namespace string) *structs.ServiceRoute { func newDefaultServiceRoute(serviceName, namespace, partition string) *structs.ServiceRoute {
return &structs.ServiceRoute{ return &structs.ServiceRoute{
Match: &structs.ServiceRouteMatch{ Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{ HTTP: &structs.ServiceRouteHTTPMatch{
@ -623,6 +624,7 @@ func newDefaultServiceRoute(serviceName string, namespace string) *structs.Servi
Destination: &structs.ServiceRouteDestination{ Destination: &structs.ServiceRouteDestination{
Service: serviceName, Service: serviceName,
Namespace: namespace, Namespace: namespace,
Partition: partition,
}, },
} }
} }
@ -836,7 +838,7 @@ RESOLVE_AGAIN:
target, target,
redirect.Service, redirect.Service,
redirect.ServiceSubset, redirect.ServiceSubset,
target.Partition, redirect.Partition,
redirect.Namespace, redirect.Namespace,
redirect.Datacenter, redirect.Datacenter,
) )
@ -940,9 +942,9 @@ RESOLVE_AGAIN:
if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil { if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil {
target.MeshGateway = serviceDefault.MeshGateway target.MeshGateway = serviceDefault.MeshGateway
} }
proxyDefault := c.entries.GetProxyDefaults(targetID.PartitionOrDefault())
if c.entries.GlobalProxy != nil && target.MeshGateway.Mode == structs.MeshGatewayModeDefault { if proxyDefault != nil && target.MeshGateway.Mode == structs.MeshGatewayModeDefault {
target.MeshGateway.Mode = c.entries.GlobalProxy.MeshGateway.Mode target.MeshGateway.Mode = proxyDefault.MeshGateway.Mode
} }
if c.overrideMeshGateway.Mode != structs.MeshGatewayModeDefault { if c.overrideMeshGateway.Mode != structs.MeshGatewayModeDefault {
@ -987,7 +989,7 @@ RESOLVE_AGAIN:
target, target,
failover.Service, failover.Service,
failover.ServiceSubset, failover.ServiceSubset,
target.Partition, failover.Partition,
failover.Namespace, failover.Namespace,
dc, dc,
) )
@ -1001,7 +1003,7 @@ RESOLVE_AGAIN:
target, target,
failover.Service, failover.Service,
failover.ServiceSubset, failover.ServiceSubset,
target.Partition, failover.Partition,
failover.Namespace, failover.Namespace,
"", "",
) )

View File

@ -158,14 +158,14 @@ func testcase_JustRouterWithDefaults() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "resolver:main.default.default.dc1", NextNode: "resolver:main.default.default.dc1",
}, },
}, },
@ -210,11 +210,11 @@ func testcase_JustRouterWithNoDestination() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: &structs.ServiceRoute{ Definition: &structs.ServiceRoute{
@ -227,7 +227,7 @@ func testcase_JustRouterWithNoDestination() compileTestCase {
NextNode: "resolver:main.default.default.dc1", NextNode: "resolver:main.default.default.dc1",
}, },
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "resolver:main.default.default.dc1", NextNode: "resolver:main.default.default.dc1",
}, },
}, },
@ -270,14 +270,14 @@ func testcase_RouterWithDefaults_NoSplit_WithResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "resolver:main.default.default.dc1", NextNode: "resolver:main.default.default.dc1",
}, },
}, },
@ -321,21 +321,21 @@ func testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver() compileTestCase
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "splitter:main.default", NextNode: "splitter:main.default.default",
}, },
}, },
}, },
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -386,21 +386,21 @@ func testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults() compileTestC
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "splitter:main.default", NextNode: "splitter:main.default.default",
}, },
}, },
}, },
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -458,21 +458,21 @@ func testcase_RouterWithDefaults_WithNoopSplit_WithResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "splitter:main.default", NextNode: "splitter:main.default.default",
}, },
}, },
}, },
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -542,18 +542,18 @@ func testcase_RouteBypassesSplit() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: &router.Routes[0], Definition: &router.Routes[0],
NextNode: "resolver:bypass.other.default.default.dc1", NextNode: "resolver:bypass.other.default.default.dc1",
}, },
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "resolver:main.default.default.dc1", NextNode: "resolver:main.default.default.dc1",
}, },
}, },
@ -605,11 +605,11 @@ func testcase_NoopSplit_DefaultResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -661,11 +661,11 @@ func testcase_NoopSplit_WithResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -724,11 +724,11 @@ func testcase_SubsetSplit() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -801,11 +801,11 @@ func testcase_ServiceSplit() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -898,11 +898,11 @@ func testcase_SplitBypassesSplit() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -1053,13 +1053,14 @@ func testcase_DatacenterRedirect() compileTestCase {
func testcase_DatacenterRedirect_WithMeshGateways() compileTestCase { func testcase_DatacenterRedirect_WithMeshGateways() compileTestCase {
entries := newEntries() entries := newEntries()
entries.GlobalProxy = &structs.ProxyConfigEntry{ entries.AddProxyDefaults(&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults, Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal, Name: structs.ProxyConfigGlobal,
MeshGateway: structs.MeshGatewayConfig{ MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote, Mode: structs.MeshGatewayModeRemote,
}, },
} })
entries.AddResolvers( entries.AddResolvers(
&structs.ServiceResolverConfigEntry{ &structs.ServiceResolverConfigEntry{
Kind: "service-resolver", Kind: "service-resolver",
@ -1300,13 +1301,15 @@ func testcase_DatacenterFailover() compileTestCase {
func testcase_DatacenterFailover_WithMeshGateways() compileTestCase { func testcase_DatacenterFailover_WithMeshGateways() compileTestCase {
entries := newEntries() entries := newEntries()
entries.GlobalProxy = &structs.ProxyConfigEntry{
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults, Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal, Name: structs.ProxyConfigGlobal,
MeshGateway: structs.MeshGatewayConfig{ MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote, Mode: structs.MeshGatewayModeRemote,
}, },
} })
entries.AddResolvers( entries.AddResolvers(
&structs.ServiceResolverConfigEntry{ &structs.ServiceResolverConfigEntry{
Kind: "service-resolver", Kind: "service-resolver",
@ -1384,11 +1387,11 @@ func testcase_NoopSplit_WithDefaultSubset() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -1446,7 +1449,8 @@ func testcase_DefaultResolver() compileTestCase {
func testcase_DefaultResolver_WithProxyDefaults() compileTestCase { func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
entries := newEntries() entries := newEntries()
entries.GlobalProxy = &structs.ProxyConfigEntry{
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults, Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal, Name: structs.ProxyConfigGlobal,
Config: map[string]interface{}{ Config: map[string]interface{}{
@ -1455,7 +1459,7 @@ func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
MeshGateway: structs.MeshGatewayConfig{ MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote, Mode: structs.MeshGatewayModeRemote,
}, },
} })
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "grpc", Protocol: "grpc",
@ -1699,11 +1703,11 @@ func testcase_MultiDatacenterCanary() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -1880,11 +1884,11 @@ func testcase_AllBellsAndWhistles() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: &router.Routes[0], Definition: &router.Routes[0],
@ -1892,17 +1896,17 @@ func testcase_AllBellsAndWhistles() compileTestCase {
}, },
{ {
Definition: &router.Routes[1], Definition: &router.Routes[1],
NextNode: "splitter:svc-split.default", NextNode: "splitter:svc-split.default.default",
}, },
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "resolver:default-subset.main.default.default.dc1", NextNode: "resolver:default-subset.main.default.default.dc1",
}, },
}, },
}, },
"splitter:svc-split.default": { "splitter:svc-split.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "svc-split.default", Name: "svc-split.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -2455,11 +2459,11 @@ func testcase_LBSplitterAndResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -2642,13 +2646,13 @@ func newSimpleRoute(name string, muts ...func(*structs.ServiceRoute)) structs.Se
} }
func setGlobalProxyProtocol(entries *structs.DiscoveryChainConfigEntries, protocol string) { func setGlobalProxyProtocol(entries *structs.DiscoveryChainConfigEntries, protocol string) {
entries.GlobalProxy = &structs.ProxyConfigEntry{ entries.AddProxyDefaults(&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults, Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal, Name: structs.ProxyConfigGlobal,
Config: map[string]interface{}{ Config: map[string]interface{}{
"protocol": protocol, "protocol": protocol,
}, },
} })
} }
func setServiceProtocol(entries *structs.DiscoveryChainConfigEntries, name, protocol string) { func setServiceProtocol(entries *structs.DiscoveryChainConfigEntries, name, protocol string) {

View File

@ -880,24 +880,21 @@ func readDiscoveryChainConfigEntriesTxn(
sid := structs.NewServiceID(serviceName, entMeta) sid := structs.NewServiceID(serviceName, entMeta)
// Grab the proxy defaults if they exist. // At every step we'll need service and proxy defaults.
idx, proxy, err := getProxyConfigEntryTxn(tx, ws, structs.ProxyConfigGlobal, overrides, entMeta)
if err != nil {
return 0, nil, err
} else if proxy != nil {
res.GlobalProxy = proxy
}
// At every step we'll need service defaults.
todoDefaults[sid] = struct{}{} todoDefaults[sid] = struct{}{}
var maxIdx uint64
// first fetch the router, of which we only collect 1 per chain eval // first fetch the router, of which we only collect 1 per chain eval
_, router, err := getRouterConfigEntryTxn(tx, ws, serviceName, overrides, entMeta) idx, router, err := getRouterConfigEntryTxn(tx, ws, serviceName, overrides, entMeta)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} else if router != nil { } else if router != nil {
res.Routers[sid] = router res.Routers[sid] = router
} }
if idx > maxIdx {
maxIdx = idx
}
if router != nil { if router != nil {
for _, svc := range router.ListRelatedServices() { for _, svc := range router.ListRelatedServices() {
@ -922,10 +919,13 @@ func readDiscoveryChainConfigEntriesTxn(
// Yes, even for splitters. // Yes, even for splitters.
todoDefaults[splitID] = struct{}{} todoDefaults[splitID] = struct{}{}
_, splitter, err := getSplitterConfigEntryTxn(tx, ws, splitID.ID, overrides, &splitID.EnterpriseMeta) idx, splitter, err := getSplitterConfigEntryTxn(tx, ws, splitID.ID, overrides, &splitID.EnterpriseMeta)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
if idx > maxIdx {
maxIdx = idx
}
if splitter == nil { if splitter == nil {
res.Splitters[splitID] = nil res.Splitters[splitID] = nil
@ -959,10 +959,13 @@ func readDiscoveryChainConfigEntriesTxn(
// And resolvers, too. // And resolvers, too.
todoDefaults[resolverID] = struct{}{} todoDefaults[resolverID] = struct{}{}
_, resolver, err := getResolverConfigEntryTxn(tx, ws, resolverID.ID, overrides, &resolverID.EnterpriseMeta) idx, resolver, err := getResolverConfigEntryTxn(tx, ws, resolverID.ID, overrides, &resolverID.EnterpriseMeta)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
if idx > maxIdx {
maxIdx = idx
}
if resolver == nil { if resolver == nil {
res.Resolvers[resolverID] = nil res.Resolvers[resolverID] = nil
@ -987,16 +990,31 @@ func readDiscoveryChainConfigEntriesTxn(
continue // already fetched continue // already fetched
} }
_, entry, err := getServiceConfigEntryTxn(tx, ws, svcID.ID, overrides, &svcID.EnterpriseMeta) if _, ok := res.ProxyDefaults[svcID.PartitionOrDefault()]; !ok {
idx, proxy, err := getProxyConfigEntryTxn(tx, ws, structs.ProxyConfigGlobal, overrides, &svcID.EnterpriseMeta)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
if idx > maxIdx {
maxIdx = idx
}
if proxy != nil {
res.ProxyDefaults[proxy.PartitionOrDefault()] = proxy
}
}
idx, entry, err := getServiceConfigEntryTxn(tx, ws, svcID.ID, overrides, &svcID.EnterpriseMeta)
if err != nil {
return 0, nil, err
}
if idx > maxIdx {
maxIdx = idx
}
if entry == nil { if entry == nil {
res.Services[svcID] = nil res.Services[svcID] = nil
continue continue
} }
res.Services[svcID] = entry res.Services[svcID] = entry
} }

View File

@ -1347,6 +1347,13 @@ func entrySetToKindNames(entrySet *structs.DiscoveryChainConfigEntries) []Config
&entry.EnterpriseMeta, &entry.EnterpriseMeta,
)) ))
} }
for _, entry := range entrySet.ProxyDefaults {
out = append(out, NewConfigEntryKindName(
entry.Kind,
entry.Name,
&entry.EnterpriseMeta,
))
}
return out return out
} }

View File

@ -119,6 +119,9 @@ func (e *ServiceRouterConfigEntry) Normalize() error {
if route.Destination != nil && route.Destination.Namespace == "" { if route.Destination != nil && route.Destination.Namespace == "" {
route.Destination.Namespace = e.EnterpriseMeta.NamespaceOrEmpty() route.Destination.Namespace = e.EnterpriseMeta.NamespaceOrEmpty()
} }
if route.Destination != nil && route.Destination.Partition == "" {
route.Destination.Partition = e.EnterpriseMeta.PartitionOrEmpty()
}
} }
return nil return nil
@ -381,6 +384,13 @@ type ServiceRouteDestination struct {
// splitting. // splitting.
Namespace string `json:",omitempty"` Namespace string `json:",omitempty"`
// Partition is the partition to resolve the service from instead of the
// current partition. If empty the current partition is assumed.
//
// If this field is specified then this route is ineligible for further
// splitting.
Partition string `json:",omitempty"`
// PrefixRewrite allows for the proxied request to have its matching path // PrefixRewrite allows for the proxied request to have its matching path
// prefix modified before being sent to the destination. Described more // prefix modified before being sent to the destination. Described more
// below in the envoy implementation section. // below in the envoy implementation section.
@ -557,8 +567,8 @@ func (e *ServiceSplitterConfigEntry) Validate() error {
} }
if _, ok := found[splitKey]; ok { if _, ok := found[splitKey]; ok {
return fmt.Errorf( return fmt.Errorf(
"split destination occurs more than once: service=%q, subset=%q, namespace=%q", "split destination occurs more than once: service=%q, subset=%q, namespace=%q, partition=%q",
splitKey.Service, splitKey.ServiceSubset, splitKey.Namespace, splitKey.Service, splitKey.ServiceSubset, splitKey.Namespace, splitKey.Partition,
) )
} }
found[splitKey] = struct{}{} found[splitKey] = struct{}{}
@ -665,7 +675,12 @@ type ServiceSplit struct {
// splitting. // splitting.
Namespace string `json:",omitempty"` Namespace string `json:",omitempty"`
// NOTE: Partition is not represented here by design. Do not add it. // Partition is the partition to resolve the service from instead of the
// current partition. If empty the current partition is assumed (optional).
//
// If this field is specified then this route is ineligible for further
// splitting.
Partition string `json:",omitempty"`
// NOTE: Any configuration added to Splits that needs to be passed to the // NOTE: Any configuration added to Splits that needs to be passed to the
// proxy needs special handling MergeParent below. // proxy needs special handling MergeParent below.
@ -930,9 +945,13 @@ func (e *ServiceResolverConfigEntry) Validate() error {
} }
if e.Redirect != nil { if e.Redirect != nil {
if e.PartitionOrEmpty() != acl.DefaultPartitionName && e.Redirect.Datacenter != "" { if !e.InDefaultPartition() && e.Redirect.Datacenter != "" {
return fmt.Errorf("Cross datacenters redirect is not allowed for non default partition") return fmt.Errorf("Cross-datacenter redirect is not supported in non-default partitions")
} }
if PartitionOrDefault(e.Redirect.Partition) != e.PartitionOrDefault() && e.Redirect.Datacenter != "" {
return fmt.Errorf("Cross-datacenter and cross-partition redirect is not supported")
}
r := e.Redirect r := e.Redirect
if len(e.Failover) > 0 { if len(e.Failover) > 0 {
@ -941,7 +960,7 @@ func (e *ServiceResolverConfigEntry) Validate() error {
// TODO(rb): prevent subsets and default subsets from being defined? // TODO(rb): prevent subsets and default subsets from being defined?
if r.Service == "" && r.ServiceSubset == "" && r.Namespace == "" && r.Datacenter == "" { if r.Service == "" && r.ServiceSubset == "" && r.Namespace == "" && r.Partition == "" && r.Datacenter == "" {
return fmt.Errorf("Redirect is empty") return fmt.Errorf("Redirect is empty")
} }
@ -952,6 +971,9 @@ func (e *ServiceResolverConfigEntry) Validate() error {
if r.Namespace != "" { if r.Namespace != "" {
return fmt.Errorf("Redirect.Namespace defined without Redirect.Service") return fmt.Errorf("Redirect.Namespace defined without Redirect.Service")
} }
if r.Partition != "" {
return fmt.Errorf("Redirect.Partition defined without Redirect.Service")
}
} else if r.Service == e.Name { } else if r.Service == e.Name {
if r.ServiceSubset != "" && !isSubset(r.ServiceSubset) { if r.ServiceSubset != "" && !isSubset(r.ServiceSubset) {
return fmt.Errorf("Redirect.ServiceSubset %q is not a valid subset of %q", r.ServiceSubset, r.Service) return fmt.Errorf("Redirect.ServiceSubset %q is not a valid subset of %q", r.ServiceSubset, r.Service)
@ -962,15 +984,19 @@ func (e *ServiceResolverConfigEntry) Validate() error {
if len(e.Failover) > 0 { if len(e.Failover) > 0 {
for subset, f := range e.Failover { for subset, f := range e.Failover {
if e.PartitionOrEmpty() != acl.DefaultPartitionName && len(f.Datacenters) != 0 { if !e.InDefaultPartition() && len(f.Datacenters) != 0 {
return fmt.Errorf("Cross datacenters failover is not allowed for non default partition") return fmt.Errorf("Cross-datacenter failover is not supported in non-default partitions")
} }
if PartitionOrDefault(f.Partition) != e.PartitionOrDefault() && len(f.Datacenters) != 0 {
return fmt.Errorf("Cross-datacenter and cross-partition failover is not supported")
}
if subset != "*" && !isSubset(subset) { if subset != "*" && !isSubset(subset) {
return fmt.Errorf("Bad Failover[%q]: not a valid subset", subset) return fmt.Errorf("Bad Failover[%q]: not a valid subset", subset)
} }
if f.Service == "" && f.ServiceSubset == "" && f.Namespace == "" && len(f.Datacenters) == 0 { if f.Service == "" && f.ServiceSubset == "" && f.Namespace == "" && f.Partition == "" && len(f.Datacenters) == 0 {
return fmt.Errorf("Bad Failover[%q] one of Service, ServiceSubset, Namespace, or Datacenters is required", subset) return fmt.Errorf("Bad Failover[%q] one of Service, ServiceSubset, Namespace, Partition, or Datacenters is required", subset)
} }
if f.ServiceSubset != "" { if f.ServiceSubset != "" {
@ -1141,6 +1167,10 @@ type ServiceResolverRedirect struct {
// current one (optional). // current one (optional).
Namespace string `json:",omitempty"` Namespace string `json:",omitempty"`
// Partition is the partition to resolve the service from instead of the
// current one (optional).
Partition string `json:",omitempty"`
// Datacenter is the datacenter to resolve the service from instead of the // Datacenter is the datacenter to resolve the service from instead of the
// current one (optional). // current one (optional).
Datacenter string `json:",omitempty"` Datacenter string `json:",omitempty"`
@ -1172,6 +1202,13 @@ type ServiceResolverFailover struct {
// This is a DESTINATION during failover. // This is a DESTINATION during failover.
Namespace string `json:",omitempty"` Namespace string `json:",omitempty"`
// Partition is the partition to resolve the requested service from to form
// the failover group of instances. If empty the current partition is used
// (optional).
//
// This is a DESTINATION during failover.
Partition string `json:",omitempty"`
// Datacenters is a fixed list of datacenters to try. We never try a // Datacenters is a fixed list of datacenters to try. We never try a
// datacenter multiple times, so those are subtracted from this list before // datacenter multiple times, so those are subtracted from this list before
// proceeding. // proceeding.
@ -1313,7 +1350,7 @@ type DiscoveryChainConfigEntries struct {
Splitters map[ServiceID]*ServiceSplitterConfigEntry Splitters map[ServiceID]*ServiceSplitterConfigEntry
Resolvers map[ServiceID]*ServiceResolverConfigEntry Resolvers map[ServiceID]*ServiceResolverConfigEntry
Services map[ServiceID]*ServiceConfigEntry Services map[ServiceID]*ServiceConfigEntry
GlobalProxy *ProxyConfigEntry ProxyDefaults map[string]*ProxyConfigEntry
} }
func NewDiscoveryChainConfigEntries() *DiscoveryChainConfigEntries { func NewDiscoveryChainConfigEntries() *DiscoveryChainConfigEntries {
@ -1322,6 +1359,7 @@ func NewDiscoveryChainConfigEntries() *DiscoveryChainConfigEntries {
Splitters: make(map[ServiceID]*ServiceSplitterConfigEntry), Splitters: make(map[ServiceID]*ServiceSplitterConfigEntry),
Resolvers: make(map[ServiceID]*ServiceResolverConfigEntry), Resolvers: make(map[ServiceID]*ServiceResolverConfigEntry),
Services: make(map[ServiceID]*ServiceConfigEntry), Services: make(map[ServiceID]*ServiceConfigEntry),
ProxyDefaults: make(map[string]*ProxyConfigEntry),
} }
} }
@ -1353,6 +1391,13 @@ func (e *DiscoveryChainConfigEntries) GetService(sid ServiceID) *ServiceConfigEn
return nil return nil
} }
func (e *DiscoveryChainConfigEntries) GetProxyDefaults(partition string) *ProxyConfigEntry {
if e.ProxyDefaults != nil {
return e.ProxyDefaults[partition]
}
return nil
}
// AddRouters adds router configs. Convenience function for testing. // AddRouters adds router configs. Convenience function for testing.
func (e *DiscoveryChainConfigEntries) AddRouters(entries ...*ServiceRouterConfigEntry) { func (e *DiscoveryChainConfigEntries) AddRouters(entries ...*ServiceRouterConfigEntry) {
if e.Routers == nil { if e.Routers == nil {
@ -1393,6 +1438,16 @@ func (e *DiscoveryChainConfigEntries) AddServices(entries ...*ServiceConfigEntry
} }
} }
// AddProxyDefaults adds proxy-defaults configs. Convenience function for testing.
func (e *DiscoveryChainConfigEntries) AddProxyDefaults(entries ...*ProxyConfigEntry) {
if e.ProxyDefaults == nil {
e.ProxyDefaults = make(map[string]*ProxyConfigEntry)
}
for _, entry := range entries {
e.ProxyDefaults[entry.PartitionOrDefault()] = entry
}
}
// AddEntries adds generic configs. Convenience function for testing. Panics on // AddEntries adds generic configs. Convenience function for testing. Panics on
// operator error. // operator error.
func (e *DiscoveryChainConfigEntries) AddEntries(entries ...ConfigEntry) { func (e *DiscoveryChainConfigEntries) AddEntries(entries ...ConfigEntry) {
@ -1410,7 +1465,7 @@ func (e *DiscoveryChainConfigEntries) AddEntries(entries ...ConfigEntry) {
if entry.GetName() != ProxyConfigGlobal { if entry.GetName() != ProxyConfigGlobal {
panic("the only supported proxy-defaults name is '" + ProxyConfigGlobal + "'") panic("the only supported proxy-defaults name is '" + ProxyConfigGlobal + "'")
} }
e.GlobalProxy = entry.(*ProxyConfigEntry) e.AddProxyDefaults(entry.(*ProxyConfigEntry))
default: default:
panic("unhandled config entry kind: " + entry.GetKind()) panic("unhandled config entry kind: " + entry.GetKind())
} }
@ -1418,7 +1473,7 @@ func (e *DiscoveryChainConfigEntries) AddEntries(entries ...ConfigEntry) {
} }
func (e *DiscoveryChainConfigEntries) IsEmpty() bool { func (e *DiscoveryChainConfigEntries) IsEmpty() bool {
return e.IsChainEmpty() && len(e.Services) == 0 && e.GlobalProxy == nil return e.IsChainEmpty() && len(e.Services) == 0 && len(e.ProxyDefaults) == 0
} }
func (e *DiscoveryChainConfigEntries) IsChainEmpty() bool { func (e *DiscoveryChainConfigEntries) IsChainEmpty() bool {

View File

@ -695,7 +695,7 @@ func TestServiceResolverConfigEntry(t *testing.T) {
"v1": {}, "v1": {},
}, },
}, },
validateErr: `Bad Failover["v1"] one of Service, ServiceSubset, Namespace, or Datacenters is required`, validateErr: `Bad Failover["v1"] one of Service, ServiceSubset, Namespace, Partition, or Datacenters is required`,
}, },
{ {
name: "failover to self using invalid subset", name: "failover to self using invalid subset",

View File

@ -513,6 +513,14 @@ function run_container_s2-secondary {
common_run_container_service s2-secondary secondary 8181 8179 common_run_container_service s2-secondary secondary 8181 8179
} }
function run_container_s2-ap1 {
common_run_container_service s2 ap1 8480 8479
}
function run_container_s3-ap1 {
common_run_container_service s3 ap1 8580 8579
}
function common_run_container_sidecar_proxy { function common_run_container_sidecar_proxy {
local service="$1" local service="$1"
local CLUSTER="$2" local CLUSTER="$2"
@ -581,6 +589,14 @@ function run_container_s2-sidecar-proxy-secondary {
common_run_container_sidecar_proxy s2 secondary common_run_container_sidecar_proxy s2 secondary
} }
function run_container_s2-ap1-sidecar-proxy {
common_run_container_sidecar_proxy s2 ap1
}
function run_container_s3-ap1-sidecar-proxy {
common_run_container_sidecar_proxy s3 ap1
}
function common_run_container_gateway { function common_run_container_gateway {
local name="$1" local name="$1"
local DC="$2" local DC="$2"