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:
parent
5ea4b82940
commit
a1c1e36be7
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/hashicorp/consul/acl"
|
||||
"github.com/hashicorp/consul/agent/cache"
|
||||
"github.com/hashicorp/consul/agent/checks"
|
||||
"github.com/hashicorp/consul/agent/consul"
|
||||
|
@ -4085,6 +4086,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
|
|||
Service: "carrot",
|
||||
ServiceSubset: "kale",
|
||||
Namespace: "leek",
|
||||
Partition: acl.DefaultPartitionName,
|
||||
PrefixRewrite: "/alternate",
|
||||
RequestTimeout: 99 * time.Second,
|
||||
NumRetries: 12345,
|
||||
|
|
|
@ -185,7 +185,7 @@ type customizationMarkers struct {
|
|||
// the String() method on the type itself. It is this way to be more
|
||||
// consistent with other string ids within the discovery chain.
|
||||
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 {
|
||||
|
@ -213,10 +213,10 @@ func (c *compiler) recordServiceProtocol(sid structs.ServiceID) error {
|
|||
if serviceDefault := c.entries.GetService(sid); serviceDefault != nil {
|
||||
return c.recordProtocol(sid, serviceDefault.Protocol)
|
||||
}
|
||||
if c.entries.GlobalProxy != nil {
|
||||
if proxyDefault := c.entries.GetProxyDefaults(sid.PartitionOrDefault()); proxyDefault != nil {
|
||||
var cfg proxyConfig
|
||||
// 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 != "" {
|
||||
return c.recordProtocol(sid, cfg.Protocol)
|
||||
}
|
||||
|
@ -567,11 +567,12 @@ func (c *compiler) assembleChain() error {
|
|||
dest = &structs.ServiceRouteDestination{
|
||||
Service: c.serviceName,
|
||||
Namespace: router.NamespaceOrDefault(),
|
||||
Partition: router.PartitionOrDefault(),
|
||||
}
|
||||
}
|
||||
svc := defaultIfEmpty(dest.Service, c.serviceName)
|
||||
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.
|
||||
var (
|
||||
|
@ -602,7 +603,7 @@ func (c *compiler) assembleChain() error {
|
|||
}
|
||||
|
||||
defaultRoute := &structs.DiscoveryRoute{
|
||||
Definition: newDefaultServiceRoute(router.Name, router.NamespaceOrDefault()),
|
||||
Definition: newDefaultServiceRoute(router.Name, router.NamespaceOrDefault(), router.PartitionOrDefault()),
|
||||
NextNode: defaultDestinationNode.MapKey(),
|
||||
}
|
||||
routeNode.Routes = append(routeNode.Routes, defaultRoute)
|
||||
|
@ -613,7 +614,7 @@ func (c *compiler) assembleChain() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func newDefaultServiceRoute(serviceName string, namespace string) *structs.ServiceRoute {
|
||||
func newDefaultServiceRoute(serviceName, namespace, partition string) *structs.ServiceRoute {
|
||||
return &structs.ServiceRoute{
|
||||
Match: &structs.ServiceRouteMatch{
|
||||
HTTP: &structs.ServiceRouteHTTPMatch{
|
||||
|
@ -623,6 +624,7 @@ func newDefaultServiceRoute(serviceName string, namespace string) *structs.Servi
|
|||
Destination: &structs.ServiceRouteDestination{
|
||||
Service: serviceName,
|
||||
Namespace: namespace,
|
||||
Partition: partition,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -836,7 +838,7 @@ RESOLVE_AGAIN:
|
|||
target,
|
||||
redirect.Service,
|
||||
redirect.ServiceSubset,
|
||||
target.Partition,
|
||||
redirect.Partition,
|
||||
redirect.Namespace,
|
||||
redirect.Datacenter,
|
||||
)
|
||||
|
@ -940,9 +942,9 @@ RESOLVE_AGAIN:
|
|||
if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil {
|
||||
target.MeshGateway = serviceDefault.MeshGateway
|
||||
}
|
||||
|
||||
if c.entries.GlobalProxy != nil && target.MeshGateway.Mode == structs.MeshGatewayModeDefault {
|
||||
target.MeshGateway.Mode = c.entries.GlobalProxy.MeshGateway.Mode
|
||||
proxyDefault := c.entries.GetProxyDefaults(targetID.PartitionOrDefault())
|
||||
if proxyDefault != nil && target.MeshGateway.Mode == structs.MeshGatewayModeDefault {
|
||||
target.MeshGateway.Mode = proxyDefault.MeshGateway.Mode
|
||||
}
|
||||
|
||||
if c.overrideMeshGateway.Mode != structs.MeshGatewayModeDefault {
|
||||
|
@ -987,7 +989,7 @@ RESOLVE_AGAIN:
|
|||
target,
|
||||
failover.Service,
|
||||
failover.ServiceSubset,
|
||||
target.Partition,
|
||||
failover.Partition,
|
||||
failover.Namespace,
|
||||
dc,
|
||||
)
|
||||
|
@ -1001,7 +1003,7 @@ RESOLVE_AGAIN:
|
|||
target,
|
||||
failover.Service,
|
||||
failover.ServiceSubset,
|
||||
target.Partition,
|
||||
failover.Partition,
|
||||
failover.Namespace,
|
||||
"",
|
||||
)
|
||||
|
|
|
@ -158,14 +158,14 @@ func testcase_JustRouterWithDefaults() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "resolver:main.default.default.dc1",
|
||||
},
|
||||
},
|
||||
|
@ -210,11 +210,11 @@ func testcase_JustRouterWithNoDestination() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: &structs.ServiceRoute{
|
||||
|
@ -227,7 +227,7 @@ func testcase_JustRouterWithNoDestination() compileTestCase {
|
|||
NextNode: "resolver:main.default.default.dc1",
|
||||
},
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "resolver:main.default.default.dc1",
|
||||
},
|
||||
},
|
||||
|
@ -270,14 +270,14 @@ func testcase_RouterWithDefaults_NoSplit_WithResolver() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "resolver:main.default.default.dc1",
|
||||
},
|
||||
},
|
||||
|
@ -321,21 +321,21 @@ func testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver() compileTestCase
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
NextNode: "splitter:main.default",
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "splitter:main.default.default",
|
||||
},
|
||||
},
|
||||
},
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -386,21 +386,21 @@ func testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults() compileTestC
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
NextNode: "splitter:main.default",
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "splitter:main.default.default",
|
||||
},
|
||||
},
|
||||
},
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -458,21 +458,21 @@ func testcase_RouterWithDefaults_WithNoopSplit_WithResolver() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
NextNode: "splitter:main.default",
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "splitter:main.default.default",
|
||||
},
|
||||
},
|
||||
},
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -542,18 +542,18 @@ func testcase_RouteBypassesSplit() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: &router.Routes[0],
|
||||
NextNode: "resolver:bypass.other.default.default.dc1",
|
||||
},
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "resolver:main.default.default.dc1",
|
||||
},
|
||||
},
|
||||
|
@ -605,11 +605,11 @@ func testcase_NoopSplit_DefaultResolver() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -661,11 +661,11 @@ func testcase_NoopSplit_WithResolver() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -724,11 +724,11 @@ func testcase_SubsetSplit() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -801,11 +801,11 @@ func testcase_ServiceSplit() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -898,11 +898,11 @@ func testcase_SplitBypassesSplit() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -1053,13 +1053,14 @@ func testcase_DatacenterRedirect() compileTestCase {
|
|||
|
||||
func testcase_DatacenterRedirect_WithMeshGateways() compileTestCase {
|
||||
entries := newEntries()
|
||||
entries.GlobalProxy = &structs.ProxyConfigEntry{
|
||||
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
|
||||
Kind: structs.ProxyDefaults,
|
||||
Name: structs.ProxyConfigGlobal,
|
||||
MeshGateway: structs.MeshGatewayConfig{
|
||||
Mode: structs.MeshGatewayModeRemote,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
entries.AddResolvers(
|
||||
&structs.ServiceResolverConfigEntry{
|
||||
Kind: "service-resolver",
|
||||
|
@ -1300,13 +1301,15 @@ func testcase_DatacenterFailover() compileTestCase {
|
|||
|
||||
func testcase_DatacenterFailover_WithMeshGateways() compileTestCase {
|
||||
entries := newEntries()
|
||||
entries.GlobalProxy = &structs.ProxyConfigEntry{
|
||||
|
||||
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
|
||||
Kind: structs.ProxyDefaults,
|
||||
Name: structs.ProxyConfigGlobal,
|
||||
MeshGateway: structs.MeshGatewayConfig{
|
||||
Mode: structs.MeshGatewayModeRemote,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
entries.AddResolvers(
|
||||
&structs.ServiceResolverConfigEntry{
|
||||
Kind: "service-resolver",
|
||||
|
@ -1384,11 +1387,11 @@ func testcase_NoopSplit_WithDefaultSubset() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -1446,7 +1449,8 @@ func testcase_DefaultResolver() compileTestCase {
|
|||
|
||||
func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
|
||||
entries := newEntries()
|
||||
entries.GlobalProxy = &structs.ProxyConfigEntry{
|
||||
|
||||
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
|
||||
Kind: structs.ProxyDefaults,
|
||||
Name: structs.ProxyConfigGlobal,
|
||||
Config: map[string]interface{}{
|
||||
|
@ -1455,7 +1459,7 @@ func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
|
|||
MeshGateway: structs.MeshGatewayConfig{
|
||||
Mode: structs.MeshGatewayModeRemote,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "grpc",
|
||||
|
@ -1699,11 +1703,11 @@ func testcase_MultiDatacenterCanary() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -1880,11 +1884,11 @@ func testcase_AllBellsAndWhistles() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: &router.Routes[0],
|
||||
|
@ -1892,17 +1896,17 @@ func testcase_AllBellsAndWhistles() compileTestCase {
|
|||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
"splitter:svc-split.default": {
|
||||
"splitter:svc-split.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "svc-split.default",
|
||||
Name: "svc-split.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -2455,11 +2459,11 @@ func testcase_LBSplitterAndResolver() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -2642,13 +2646,13 @@ func newSimpleRoute(name string, muts ...func(*structs.ServiceRoute)) structs.Se
|
|||
}
|
||||
|
||||
func setGlobalProxyProtocol(entries *structs.DiscoveryChainConfigEntries, protocol string) {
|
||||
entries.GlobalProxy = &structs.ProxyConfigEntry{
|
||||
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
|
||||
Kind: structs.ProxyDefaults,
|
||||
Name: structs.ProxyConfigGlobal,
|
||||
Config: map[string]interface{}{
|
||||
"protocol": protocol,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func setServiceProtocol(entries *structs.DiscoveryChainConfigEntries, name, protocol string) {
|
||||
|
|
|
@ -880,24 +880,21 @@ func readDiscoveryChainConfigEntriesTxn(
|
|||
|
||||
sid := structs.NewServiceID(serviceName, entMeta)
|
||||
|
||||
// Grab the proxy defaults if they exist.
|
||||
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.
|
||||
// At every step we'll need service and proxy defaults.
|
||||
todoDefaults[sid] = struct{}{}
|
||||
|
||||
var maxIdx uint64
|
||||
|
||||
// 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 {
|
||||
return 0, nil, err
|
||||
} else if router != nil {
|
||||
res.Routers[sid] = router
|
||||
}
|
||||
if idx > maxIdx {
|
||||
maxIdx = idx
|
||||
}
|
||||
|
||||
if router != nil {
|
||||
for _, svc := range router.ListRelatedServices() {
|
||||
|
@ -922,10 +919,13 @@ func readDiscoveryChainConfigEntriesTxn(
|
|||
// Yes, even for splitters.
|
||||
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 {
|
||||
return 0, nil, err
|
||||
}
|
||||
if idx > maxIdx {
|
||||
maxIdx = idx
|
||||
}
|
||||
|
||||
if splitter == nil {
|
||||
res.Splitters[splitID] = nil
|
||||
|
@ -959,10 +959,13 @@ func readDiscoveryChainConfigEntriesTxn(
|
|||
// And resolvers, too.
|
||||
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 {
|
||||
return 0, nil, err
|
||||
}
|
||||
if idx > maxIdx {
|
||||
maxIdx = idx
|
||||
}
|
||||
|
||||
if resolver == nil {
|
||||
res.Resolvers[resolverID] = nil
|
||||
|
@ -987,16 +990,31 @@ func readDiscoveryChainConfigEntriesTxn(
|
|||
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 {
|
||||
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 {
|
||||
res.Services[svcID] = nil
|
||||
continue
|
||||
}
|
||||
|
||||
res.Services[svcID] = entry
|
||||
}
|
||||
|
||||
|
|
|
@ -1347,6 +1347,13 @@ func entrySetToKindNames(entrySet *structs.DiscoveryChainConfigEntries) []Config
|
|||
&entry.EnterpriseMeta,
|
||||
))
|
||||
}
|
||||
for _, entry := range entrySet.ProxyDefaults {
|
||||
out = append(out, NewConfigEntryKindName(
|
||||
entry.Kind,
|
||||
entry.Name,
|
||||
&entry.EnterpriseMeta,
|
||||
))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
|
|
|
@ -119,6 +119,9 @@ func (e *ServiceRouterConfigEntry) Normalize() error {
|
|||
if route.Destination != nil && route.Destination.Namespace == "" {
|
||||
route.Destination.Namespace = e.EnterpriseMeta.NamespaceOrEmpty()
|
||||
}
|
||||
if route.Destination != nil && route.Destination.Partition == "" {
|
||||
route.Destination.Partition = e.EnterpriseMeta.PartitionOrEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -381,6 +384,13 @@ type ServiceRouteDestination struct {
|
|||
// splitting.
|
||||
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
|
||||
// prefix modified before being sent to the destination. Described more
|
||||
// below in the envoy implementation section.
|
||||
|
@ -557,8 +567,8 @@ func (e *ServiceSplitterConfigEntry) Validate() error {
|
|||
}
|
||||
if _, ok := found[splitKey]; ok {
|
||||
return fmt.Errorf(
|
||||
"split destination occurs more than once: service=%q, subset=%q, namespace=%q",
|
||||
splitKey.Service, splitKey.ServiceSubset, splitKey.Namespace,
|
||||
"split destination occurs more than once: service=%q, subset=%q, namespace=%q, partition=%q",
|
||||
splitKey.Service, splitKey.ServiceSubset, splitKey.Namespace, splitKey.Partition,
|
||||
)
|
||||
}
|
||||
found[splitKey] = struct{}{}
|
||||
|
@ -665,7 +675,12 @@ type ServiceSplit struct {
|
|||
// splitting.
|
||||
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
|
||||
// proxy needs special handling MergeParent below.
|
||||
|
@ -930,9 +945,13 @@ func (e *ServiceResolverConfigEntry) Validate() error {
|
|||
}
|
||||
|
||||
if e.Redirect != nil {
|
||||
if e.PartitionOrEmpty() != acl.DefaultPartitionName && e.Redirect.Datacenter != "" {
|
||||
return fmt.Errorf("Cross datacenters redirect is not allowed for non default partition")
|
||||
if !e.InDefaultPartition() && e.Redirect.Datacenter != "" {
|
||||
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
|
||||
|
||||
if len(e.Failover) > 0 {
|
||||
|
@ -941,7 +960,7 @@ func (e *ServiceResolverConfigEntry) Validate() error {
|
|||
|
||||
// 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")
|
||||
}
|
||||
|
||||
|
@ -952,6 +971,9 @@ func (e *ServiceResolverConfigEntry) Validate() error {
|
|||
if r.Namespace != "" {
|
||||
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 {
|
||||
if r.ServiceSubset != "" && !isSubset(r.ServiceSubset) {
|
||||
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 {
|
||||
|
||||
for subset, f := range e.Failover {
|
||||
if e.PartitionOrEmpty() != acl.DefaultPartitionName && len(f.Datacenters) != 0 {
|
||||
return fmt.Errorf("Cross datacenters failover is not allowed for non default partition")
|
||||
if !e.InDefaultPartition() && len(f.Datacenters) != 0 {
|
||||
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) {
|
||||
return fmt.Errorf("Bad Failover[%q]: not a valid subset", subset)
|
||||
}
|
||||
|
||||
if f.Service == "" && f.ServiceSubset == "" && f.Namespace == "" && len(f.Datacenters) == 0 {
|
||||
return fmt.Errorf("Bad Failover[%q] one of Service, ServiceSubset, Namespace, or Datacenters is required", subset)
|
||||
if f.Service == "" && f.ServiceSubset == "" && f.Namespace == "" && f.Partition == "" && len(f.Datacenters) == 0 {
|
||||
return fmt.Errorf("Bad Failover[%q] one of Service, ServiceSubset, Namespace, Partition, or Datacenters is required", subset)
|
||||
}
|
||||
|
||||
if f.ServiceSubset != "" {
|
||||
|
@ -1141,6 +1167,10 @@ type ServiceResolverRedirect struct {
|
|||
// current one (optional).
|
||||
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
|
||||
// current one (optional).
|
||||
Datacenter string `json:",omitempty"`
|
||||
|
@ -1172,6 +1202,13 @@ type ServiceResolverFailover struct {
|
|||
// This is a DESTINATION during failover.
|
||||
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
|
||||
// datacenter multiple times, so those are subtracted from this list before
|
||||
// proceeding.
|
||||
|
@ -1309,19 +1346,20 @@ func canWriteDiscoveryChain(entry discoveryChainConfigEntry, authz acl.Authorize
|
|||
// DiscoveryChainConfigEntries wraps just the raw cross-referenced config
|
||||
// entries. None of these are defaulted.
|
||||
type DiscoveryChainConfigEntries struct {
|
||||
Routers map[ServiceID]*ServiceRouterConfigEntry
|
||||
Splitters map[ServiceID]*ServiceSplitterConfigEntry
|
||||
Resolvers map[ServiceID]*ServiceResolverConfigEntry
|
||||
Services map[ServiceID]*ServiceConfigEntry
|
||||
GlobalProxy *ProxyConfigEntry
|
||||
Routers map[ServiceID]*ServiceRouterConfigEntry
|
||||
Splitters map[ServiceID]*ServiceSplitterConfigEntry
|
||||
Resolvers map[ServiceID]*ServiceResolverConfigEntry
|
||||
Services map[ServiceID]*ServiceConfigEntry
|
||||
ProxyDefaults map[string]*ProxyConfigEntry
|
||||
}
|
||||
|
||||
func NewDiscoveryChainConfigEntries() *DiscoveryChainConfigEntries {
|
||||
return &DiscoveryChainConfigEntries{
|
||||
Routers: make(map[ServiceID]*ServiceRouterConfigEntry),
|
||||
Splitters: make(map[ServiceID]*ServiceSplitterConfigEntry),
|
||||
Resolvers: make(map[ServiceID]*ServiceResolverConfigEntry),
|
||||
Services: make(map[ServiceID]*ServiceConfigEntry),
|
||||
Routers: make(map[ServiceID]*ServiceRouterConfigEntry),
|
||||
Splitters: make(map[ServiceID]*ServiceSplitterConfigEntry),
|
||||
Resolvers: make(map[ServiceID]*ServiceResolverConfigEntry),
|
||||
Services: make(map[ServiceID]*ServiceConfigEntry),
|
||||
ProxyDefaults: make(map[string]*ProxyConfigEntry),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1353,6 +1391,13 @@ func (e *DiscoveryChainConfigEntries) GetService(sid ServiceID) *ServiceConfigEn
|
|||
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.
|
||||
func (e *DiscoveryChainConfigEntries) AddRouters(entries ...*ServiceRouterConfigEntry) {
|
||||
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
|
||||
// operator error.
|
||||
func (e *DiscoveryChainConfigEntries) AddEntries(entries ...ConfigEntry) {
|
||||
|
@ -1410,7 +1465,7 @@ func (e *DiscoveryChainConfigEntries) AddEntries(entries ...ConfigEntry) {
|
|||
if entry.GetName() != ProxyConfigGlobal {
|
||||
panic("the only supported proxy-defaults name is '" + ProxyConfigGlobal + "'")
|
||||
}
|
||||
e.GlobalProxy = entry.(*ProxyConfigEntry)
|
||||
e.AddProxyDefaults(entry.(*ProxyConfigEntry))
|
||||
default:
|
||||
panic("unhandled config entry kind: " + entry.GetKind())
|
||||
}
|
||||
|
@ -1418,7 +1473,7 @@ func (e *DiscoveryChainConfigEntries) AddEntries(entries ...ConfigEntry) {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -695,7 +695,7 @@ func TestServiceResolverConfigEntry(t *testing.T) {
|
|||
"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",
|
||||
|
|
|
@ -513,6 +513,14 @@ function run_container_s2-secondary {
|
|||
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 {
|
||||
local service="$1"
|
||||
local CLUSTER="$2"
|
||||
|
@ -581,6 +589,14 @@ function run_container_s2-sidecar-proxy-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 {
|
||||
local name="$1"
|
||||
local DC="$2"
|
||||
|
|
Loading…
Reference in New Issue