Merge pull request #11757 from hashicorp/ap/discovery-chain

This commit is contained in:
Freddy 2021-12-06 15:22:51 -07:00 committed by GitHub
commit 46a530434e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 272 additions and 144 deletions

3
.changelog/11757.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
connect: **(Enterprise only)** add support for targeting partitions in discovery chain routes, splits, and redirects.
```

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 {

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
}
@ -1022,7 +1040,7 @@ func readDiscoveryChainConfigEntriesTxn(
}
}
return idx, res, nil
return maxIdx, res, nil
}
// anyKey returns any key from the provided map if any exist. Useful for using

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 only supported in the default partition")
}
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,9 +984,10 @@ 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 only supported in the default partition")
}
if subset != "*" && !isSubset(subset) {
return fmt.Errorf("Bad Failover[%q]: not a valid subset", subset)
}
@ -1141,6 +1164,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"`
@ -1309,19 +1336,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 +1381,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 +1428,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 +1455,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 +1463,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

@ -63,10 +63,10 @@ type ServiceRouteHTTPMatchQueryParam struct {
}
type ServiceRouteDestination struct {
Service string `json:",omitempty"`
ServiceSubset string `json:",omitempty" alias:"service_subset"`
// Referencing other partitions is not supported.
Service string `json:",omitempty"`
ServiceSubset string `json:",omitempty" alias:"service_subset"`
Namespace string `json:",omitempty"`
Partition string `json:",omitempty"`
PrefixRewrite string `json:",omitempty" alias:"prefix_rewrite"`
RequestTimeout time.Duration `json:",omitempty" alias:"request_timeout"`
NumRetries uint32 `json:",omitempty" alias:"num_retries"`
@ -134,11 +134,11 @@ func (e *ServiceSplitterConfigEntry) GetCreateIndex() uint64 { return e.Crea
func (e *ServiceSplitterConfigEntry) GetModifyIndex() uint64 { return e.ModifyIndex }
type ServiceSplit struct {
Weight float32
Service string `json:",omitempty"`
ServiceSubset string `json:",omitempty" alias:"service_subset"`
// Referencing other partitions is not supported.
Weight float32
Service string `json:",omitempty"`
ServiceSubset string `json:",omitempty" alias:"service_subset"`
Namespace string `json:",omitempty"`
Partition string `json:",omitempty"`
RequestHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"request_headers"`
ResponseHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"response_headers"`
}
@ -216,9 +216,9 @@ type ServiceResolverSubset struct {
type ServiceResolverRedirect struct {
Service string `json:",omitempty"`
ServiceSubset string `json:",omitempty" alias:"service_subset"`
// Referencing other partitions is not supported.
Namespace string `json:",omitempty"`
Datacenter string `json:",omitempty"`
Namespace string `json:",omitempty"`
Partition string `json:",omitempty"`
Datacenter string `json:",omitempty"`
}
type ServiceResolverFailover struct {

View File

@ -242,6 +242,7 @@ func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) {
Service: "test-failover",
ServiceSubset: "v2",
Namespace: defaultNamespace,
Partition: defaultPartition,
PrefixRewrite: "/",
RequestTimeout: 5 * time.Second,
NumRetries: 5,

View File

@ -791,6 +791,7 @@ func TestParseConfigEntry(t *testing.T) {
snake: `
kind = "service-router"
name = "main"
partition = "pepper"
meta {
"foo" = "bar"
"gir" = "zim"
@ -830,12 +831,13 @@ func TestParseConfigEntry(t *testing.T) {
}
}
destination {
service = "carrot"
service_subset = "kale"
namespace = "leek"
prefix_rewrite = "/alternate"
request_timeout = "99s"
num_retries = 12345
service = "carrot"
service_subset = "kale"
namespace = "leek"
partition = "chard"
prefix_rewrite = "/alternate"
request_timeout = "99s"
num_retries = 12345
retry_on_connect_failure = true
retry_on_status_codes = [401, 209]
}
@ -874,6 +876,7 @@ func TestParseConfigEntry(t *testing.T) {
camel: `
Kind = "service-router"
Name = "main"
Partition = "pepper"
Meta {
"foo" = "bar"
"gir" = "zim"
@ -916,6 +919,7 @@ func TestParseConfigEntry(t *testing.T) {
Service = "carrot"
ServiceSubset = "kale"
Namespace = "leek"
Partition = "chard"
PrefixRewrite = "/alternate"
RequestTimeout = "99s"
NumRetries = 12345
@ -958,6 +962,7 @@ func TestParseConfigEntry(t *testing.T) {
{
"kind": "service-router",
"name": "main",
"partition": "pepper",
"meta" : {
"foo": "bar",
"gir": "zim"
@ -1000,6 +1005,7 @@ func TestParseConfigEntry(t *testing.T) {
"service": "carrot",
"service_subset": "kale",
"namespace": "leek",
"partition": "chard",
"prefix_rewrite": "/alternate",
"request_timeout": "99s",
"num_retries": 12345,
@ -1049,6 +1055,7 @@ func TestParseConfigEntry(t *testing.T) {
{
"Kind": "service-router",
"Name": "main",
"Partition": "pepper",
"Meta" : {
"foo": "bar",
"gir": "zim"
@ -1091,6 +1098,7 @@ func TestParseConfigEntry(t *testing.T) {
"Service": "carrot",
"ServiceSubset": "kale",
"Namespace": "leek",
"Partition": "chard",
"PrefixRewrite": "/alternate",
"RequestTimeout": "99s",
"NumRetries": 12345,
@ -1137,8 +1145,9 @@ func TestParseConfigEntry(t *testing.T) {
}
`,
expect: &api.ServiceRouterConfigEntry{
Kind: "service-router",
Name: "main",
Kind: "service-router",
Name: "main",
Partition: "pepper",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
@ -1181,6 +1190,7 @@ func TestParseConfigEntry(t *testing.T) {
Service: "carrot",
ServiceSubset: "kale",
Namespace: "leek",
Partition: "chard",
PrefixRewrite: "/alternate",
RequestTimeout: 99 * time.Second,
NumRetries: 12345,
@ -1225,6 +1235,7 @@ func TestParseConfigEntry(t *testing.T) {
snake: `
kind = "service-splitter"
name = "main"
partition = "east"
meta {
"foo" = "bar"
"gir" = "zim"
@ -1242,12 +1253,14 @@ func TestParseConfigEntry(t *testing.T) {
weight = 0.9
service = "other"
namespace = "alt"
partition = "west"
},
]
`,
camel: `
Kind = "service-splitter"
Name = "main"
Partition = "east"
Meta {
"foo" = "bar"
"gir" = "zim"
@ -1265,6 +1278,7 @@ func TestParseConfigEntry(t *testing.T) {
Weight = 0.9
Service = "other"
Namespace = "alt"
Partition = "west"
},
]
`,
@ -1272,6 +1286,7 @@ func TestParseConfigEntry(t *testing.T) {
{
"kind": "service-splitter",
"name": "main",
"partition": "east",
"meta" : {
"foo": "bar",
"gir": "zim"
@ -1288,7 +1303,8 @@ func TestParseConfigEntry(t *testing.T) {
{
"weight": 0.9,
"service": "other",
"namespace": "alt"
"namespace": "alt",
"partition": "west"
}
]
}
@ -1297,6 +1313,7 @@ func TestParseConfigEntry(t *testing.T) {
{
"Kind": "service-splitter",
"Name": "main",
"Partition": "east",
"Meta" : {
"foo": "bar",
"gir": "zim"
@ -1313,14 +1330,16 @@ func TestParseConfigEntry(t *testing.T) {
{
"Weight": 0.9,
"Service": "other",
"Namespace": "alt"
"Namespace": "alt",
"Partition": "west"
}
]
}
`,
expect: &api.ServiceSplitterConfigEntry{
Kind: api.ServiceSplitter,
Name: "main",
Kind: api.ServiceSplitter,
Name: "main",
Partition: "east",
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
@ -1338,6 +1357,7 @@ func TestParseConfigEntry(t *testing.T) {
Weight: 0.9,
Service: "other",
Namespace: "alt",
Partition: "west",
},
},
},
@ -1512,20 +1532,24 @@ func TestParseConfigEntry(t *testing.T) {
snake: `
kind = "service-resolver"
name = "main"
partition = "east"
redirect {
service = "other"
service_subset = "backup"
namespace = "alt"
partition = "west"
datacenter = "dc9"
}
`,
camel: `
Kind = "service-resolver"
Name = "main"
Partition = "east"
Redirect {
Service = "other"
ServiceSubset = "backup"
Namespace = "alt"
Partition = "west"
Datacenter = "dc9"
}
`,
@ -1533,10 +1557,12 @@ func TestParseConfigEntry(t *testing.T) {
{
"kind": "service-resolver",
"name": "main",
"partition": "east",
"redirect": {
"service": "other",
"service_subset": "backup",
"namespace": "alt",
"partition": "west",
"datacenter": "dc9"
}
}
@ -1545,21 +1571,25 @@ func TestParseConfigEntry(t *testing.T) {
{
"Kind": "service-resolver",
"Name": "main",
"Partition": "east",
"Redirect": {
"Service": "other",
"ServiceSubset": "backup",
"Namespace": "alt",
"Partition": "west",
"Datacenter": "dc9"
}
}
`,
expect: &api.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
Kind: "service-resolver",
Name: "main",
Partition: "east",
Redirect: &api.ServiceResolverRedirect{
Service: "other",
ServiceSubset: "backup",
Namespace: "alt",
Partition: "west",
Datacenter: "dc9",
},
},

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"