add sameness groups to discovery chains (#16671)

This commit is contained in:
Eric Haberkorn 2023-03-20 09:12:37 -04:00 committed by GitHub
parent b9d8552e25
commit 7477f52a16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 152 additions and 30 deletions

View File

@ -59,6 +59,13 @@ func (e *DiscoveryChainSet) GetService(sid structs.ServiceID) *structs.ServiceCo
return nil
}
func (e *DiscoveryChainSet) GetSamenessGroup(name string) *structs.SamenessGroupConfigEntry {
if e.SamenessGroups != nil {
return e.SamenessGroups[name]
}
return nil
}
func (e *DiscoveryChainSet) GetProxyDefaults(partition string) *structs.ProxyConfigEntry {
if e.ProxyDefaults != nil {
return e.ProxyDefaults[partition]
@ -106,6 +113,16 @@ func (e *DiscoveryChainSet) AddServices(entries ...*structs.ServiceConfigEntry)
}
}
// AddSamenessGroup adds service configs. Convenience function for testing.
func (e *DiscoveryChainSet) AddSamenessGroup(entries ...*structs.SamenessGroupConfigEntry) {
if e.Services == nil {
e.SamenessGroups = make(map[string]*structs.SamenessGroupConfigEntry)
}
for _, entry := range entries {
e.SamenessGroups[entry.Name] = entry
}
}
// AddProxyDefaults adds proxy-defaults configs. Convenience function for testing.
func (e *DiscoveryChainSet) AddProxyDefaults(entries ...*structs.ProxyConfigEntry) {
if e.ProxyDefaults == nil {
@ -139,6 +156,8 @@ func (e *DiscoveryChainSet) AddEntries(entries ...structs.ConfigEntry) {
e.AddResolvers(entry.(*structs.ServiceResolverConfigEntry))
case structs.ServiceDefaults:
e.AddServices(entry.(*structs.ServiceConfigEntry))
case structs.SamenessGroup:
e.AddSamenessGroup(entry.(*structs.SamenessGroupConfigEntry))
case structs.ProxyDefaults:
if entry.GetName() != structs.ProxyConfigGlobal {
panic("the only supported proxy-defaults name is '" + structs.ProxyConfigGlobal + "'")

View File

@ -930,7 +930,8 @@ RESOLVE_AGAIN:
//
// TODO(rb): What about a redirected subset reference? (web/v2, but web redirects to alt/"")
if resolver.Redirect != nil {
// Redirects to sameness groups are technically failovers.
if resolver.Redirect != nil && resolver.Redirect.SamenessGroup == "" {
redirect := resolver.Redirect
redirectedTarget := c.rewriteTarget(
@ -1070,6 +1071,23 @@ RESOLVE_AGAIN:
// reasonably if there is some sort of graph loop below.
c.recordNode(node)
var err error
// Determine which failover definitions apply.
var failoverTargets []*structs.DiscoveryTarget
var failoverPolicy *structs.ServiceResolverFailoverPolicy
proxyDefault := c.entries.GetProxyDefaults(targetID.PartitionOrDefault())
if proxyDefault != nil {
failoverPolicy = proxyDefault.FailoverPolicy
}
if resolver.Redirect != nil && resolver.Redirect.SamenessGroup != "" {
opts := resolver.Redirect.ToDiscoveryTargetOpts()
failoverTargets, err = c.makeSamenessGroupFailover(target, opts, resolver.Redirect.SamenessGroup)
if err != nil {
return nil, err
}
}
if len(resolver.Failover) > 0 {
f := resolver.Failover
@ -1083,8 +1101,10 @@ RESOLVE_AGAIN:
return node, nil
}
// Determine which failover definitions apply.
var failoverTargets []*structs.DiscoveryTarget
if failover.Policy != nil {
failoverPolicy = failover.Policy
}
if len(failover.Datacenters) > 0 {
opts := failover.ToDiscoveryTargetOpts()
for _, dc := range failover.Datacenters {
@ -1103,6 +1123,11 @@ RESOLVE_AGAIN:
failoverTargets = append(failoverTargets, failoverTarget)
}
}
} else if failover.SamenessGroup != "" {
failoverTargets, err = c.makeSamenessGroupFailover(target, failover.ToDiscoveryTargetOpts(), failover.SamenessGroup)
if err != nil {
return nil, err
}
} else {
// Rewrite the target as per the failover policy.
failoverTarget := c.rewriteTarget(target, failover.ToDiscoveryTargetOpts())
@ -1111,37 +1136,55 @@ RESOLVE_AGAIN:
}
}
// If we filtered everything out then no point in having a failover.
if len(failoverTargets) > 0 {
df := &structs.DiscoveryFailover{}
node.Resolver.Failover = df
}
if failover.Policy == nil || failover.Policy.Mode == "" {
proxyDefault := c.entries.GetProxyDefaults(targetID.PartitionOrDefault())
if proxyDefault != nil {
df.Policy = proxyDefault.FailoverPolicy
}
} else {
df.Policy = failover.Policy
}
// If we filtered everything out then no point in having a failover.
if len(failoverTargets) > 0 {
df := &structs.DiscoveryFailover{}
node.Resolver.Failover = df
// Take care of doing any redirects or configuration loading
// related to targets by cheating a bit and recursing into
// ourselves.
for _, target := range failoverTargets {
failoverResolveNode, err := c.getResolverNode(target, true)
if err != nil {
return nil, err
}
failoverTarget := failoverResolveNode.Resolver.Target
df.Targets = append(df.Targets, failoverTarget)
df.Policy = failoverPolicy
// Take care of doing any redirects or configuration loading
// related to targets by cheating a bit and recursing into
// ourselves.
for _, target := range failoverTargets {
failoverResolveNode, err := c.getResolverNode(target, true)
if err != nil {
return nil, err
}
failoverTarget := failoverResolveNode.Resolver.Target
df.Targets = append(df.Targets, failoverTarget)
}
}
return node, nil
}
func (c *compiler) makeSamenessGroupFailover(target *structs.DiscoveryTarget, opts structs.DiscoveryTargetOpts, samenessGroupName string) ([]*structs.DiscoveryTarget, error) {
samenessGroup := c.entries.GetSamenessGroup(samenessGroupName)
if samenessGroup == nil {
return nil, &structs.ConfigEntryGraphError{
Message: fmt.Sprintf(
"sameness group missing for service %q",
target.Service,
),
}
}
var failoverTargets []*structs.DiscoveryTarget
for _, t := range samenessGroup.ToFailoverTargets() {
// Rewrite the target as per the failover policy.
targetOpts := structs.MergeDiscoveryTargetOpts(opts, t.ToDiscoveryTargetOpts())
failoverTarget := c.rewriteTarget(target, targetOpts)
if failoverTarget.ID != target.ID { // don't failover to yourself
failoverTargets = append(failoverTargets, failoverTarget)
}
}
return failoverTargets, nil
}
func newDefaultServiceResolver(sid structs.ServiceID) *structs.ServiceResolverConfigEntry {
return &structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,

View File

@ -725,6 +725,16 @@ func validateProposedConfigEntryInServiceGraph(
// Exported services and mesh config do not influence discovery chains.
return nil
case structs.SamenessGroup:
// Any service resolver could reference a sameness group.
_, entries, err := configEntriesByKindTxn(tx, nil, structs.ServiceResolver, wildcardEntMeta)
if err != nil {
return err
}
for _, entry := range entries {
checkChains[structs.NewServiceID(entry.GetName(), entry.GetEnterpriseMeta())] = struct{}{}
}
case structs.ProxyDefaults:
// Check anything that has a discovery chain entry. In the future we could
// somehow omit the ones that have a default protocol configured.
@ -1408,8 +1418,8 @@ func readDiscoveryChainConfigEntriesTxn(
todoPeers[peer] = struct{}{}
}
for _, peer := range resolver.RelatedSamenessGroups() {
todoSamenessGroups[peer] = struct{}{}
for _, sg := range resolver.RelatedSamenessGroups() {
todoSamenessGroups[sg] = struct{}{}
}
}

View File

@ -1103,7 +1103,7 @@ func (e *ServiceResolverConfigEntry) Validate() error {
}
if f.isEmpty() {
return fmt.Errorf(errorPrefix + "one of Service, ServiceSubset, Namespace, Targets, or Datacenters is required")
return fmt.Errorf(errorPrefix + "one of Service, ServiceSubset, Namespace, Targets, SamenessGroup, or Datacenters is required")
}
if err := f.Policy.ValidateEnterprise(); err != nil {
@ -1447,7 +1447,12 @@ func (f *ServiceResolverFailover) ToDiscoveryTargetOpts() DiscoveryTargetOpts {
}
func (f *ServiceResolverFailover) isEmpty() bool {
return f.Service == "" && f.ServiceSubset == "" && f.Namespace == "" && len(f.Datacenters) == 0 && len(f.Targets) == 0
return f.Service == "" &&
f.ServiceSubset == "" &&
f.Namespace == "" &&
len(f.Datacenters) == 0 &&
len(f.Targets) == 0 &&
f.SamenessGroup == ""
}
func (fp *ServiceResolverFailoverPolicy) isValid() bool {

View File

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

View File

@ -70,3 +70,20 @@ type SamenessGroupMember struct {
Partition string
Peer string
}
func (s *SamenessGroupConfigEntry) ToFailoverTargets() []ServiceResolverFailoverTarget {
if s == nil {
return nil
}
var targets []ServiceResolverFailoverTarget
for _, m := range s.Members {
targets = append(targets, ServiceResolverFailoverTarget{
Peer: m.Peer,
Partition: m.Partition,
})
}
return targets
}

View File

@ -262,6 +262,34 @@ type DiscoveryTargetOpts struct {
Peer string
}
func MergeDiscoveryTargetOpts(o1 DiscoveryTargetOpts, o2 DiscoveryTargetOpts) DiscoveryTargetOpts {
if o2.Service != "" {
o1.Service = o2.Service
}
if o2.ServiceSubset != "" {
o1.ServiceSubset = o2.ServiceSubset
}
if o2.Namespace != "" {
o1.Namespace = o2.Namespace
}
if o2.Partition != "" {
o1.Partition = o2.Partition
}
if o2.Datacenter != "" {
o1.Datacenter = o2.Datacenter
}
if o2.Peer != "" {
o1.Peer = o2.Peer
}
return o1
}
func NewDiscoveryTarget(opts DiscoveryTargetOpts) *DiscoveryTarget {
t := &DiscoveryTarget{
Service: opts.Service,