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 5ea4b82940
commit a1c1e36be7
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/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,

View File

@ -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,
"",
)

View File

@ -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) {

View File

@ -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
}

View File

@ -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
}

View File

@ -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.
@ -1313,7 +1350,7 @@ type DiscoveryChainConfigEntries struct {
Splitters map[ServiceID]*ServiceSplitterConfigEntry
Resolvers map[ServiceID]*ServiceResolverConfigEntry
Services map[ServiceID]*ServiceConfigEntry
GlobalProxy *ProxyConfigEntry
ProxyDefaults map[string]*ProxyConfigEntry
}
func NewDiscoveryChainConfigEntries() *DiscoveryChainConfigEntries {
@ -1322,6 +1359,7 @@ func NewDiscoveryChainConfigEntries() *DiscoveryChainConfigEntries {
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 {

View File

@ -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",

View File

@ -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"