Skip to content

Commit

Permalink
Add support for failover policies
Browse files Browse the repository at this point in the history
  • Loading branch information
erichaberkorn committed Mar 2, 2023
1 parent 21c3095 commit b13c7bb
Show file tree
Hide file tree
Showing 14 changed files with 1,476 additions and 1,245 deletions.
9 changes: 9 additions & 0 deletions agent/consul/discoverychain/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,15 @@ RESOLVE_AGAIN:
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
}

// Take care of doing any redirects or configuration loading
// related to targets by cheating a bit and recursing into
// ourselves.
Expand Down
17 changes: 11 additions & 6 deletions agent/structs/config_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,12 +362,13 @@ type ProxyConfigEntry struct {
Kind string
Name string
Config map[string]interface{}
Mode ProxyMode `json:",omitempty"`
TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
Expose ExposeConfig `json:",omitempty"`
AccessLogs AccessLogsConfig `json:",omitempty" alias:"access_logs"`
EnvoyExtensions EnvoyExtensions `json:",omitempty" alias:"envoy_extensions"`
Mode ProxyMode `json:",omitempty"`
TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"`
MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"`
Expose ExposeConfig `json:",omitempty"`
AccessLogs AccessLogsConfig `json:",omitempty" alias:"access_logs"`
EnvoyExtensions EnvoyExtensions `json:",omitempty" alias:"envoy_extensions"`
FailoverPolicy *ServiceResolverFailoverPolicy `json:",omitempty" alias:"failover_policy"`

Meta map[string]string `json:",omitempty"`
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
Expand Down Expand Up @@ -434,6 +435,10 @@ func (e *ProxyConfigEntry) Validate() error {
return err
}

if !e.FailoverPolicy.isValid() {
return fmt.Errorf("Failover policy must be one of '', 'default', or 'order-by-locality'")
}

return e.validateEnterpriseMeta()
}

Expand Down
41 changes: 37 additions & 4 deletions agent/structs/config_entry_discoverychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,14 @@ func (e *ServiceResolverConfigEntry) Validate() error {
return fmt.Errorf(errorPrefix + "one of Service, ServiceSubset, Namespace, Targets, or Datacenters is required")
}

if err := f.Policy.ValidateEnterprise(); err != nil {
return fmt.Errorf("Bad Failover[%q]: %s", subset, err)
}

if !f.Policy.isValid() {
return fmt.Errorf("Bad Failover[%q]: Policy must be one of '', 'default', or 'order-by-locality'", subset)
}

if f.ServiceSubset != "" {
if f.Service == "" || f.Service == e.Name {
if !isSubset(f.ServiceSubset) {
Expand Down Expand Up @@ -1347,20 +1355,45 @@ type ServiceResolverFailover struct {
//
// This is a DESTINATION during failover.
Targets []ServiceResolverFailoverTarget `json:",omitempty"`

// Policy specifies the exact mechanism used for failover.
Policy *ServiceResolverFailoverPolicy `json:",omitempty"`
}

func (t *ServiceResolverFailover) ToDiscoveryTargetOpts() DiscoveryTargetOpts {
type ServiceResolverFailoverPolicy struct {
// Mode specifies the type of failover that will be performed. Valid values are
// "default", "" (equivalent to "default") and "order-by-locality".
Mode string `json:",omitempty"`
}

func (f *ServiceResolverFailover) ToDiscoveryTargetOpts() DiscoveryTargetOpts {
return DiscoveryTargetOpts{
Service: t.Service,
ServiceSubset: t.ServiceSubset,
Namespace: t.Namespace,
Service: f.Service,
ServiceSubset: f.ServiceSubset,
Namespace: f.Namespace,
}
}

func (f *ServiceResolverFailover) isEmpty() bool {
return f.Service == "" && f.ServiceSubset == "" && f.Namespace == "" && len(f.Datacenters) == 0 && len(f.Targets) == 0
}

func (fp *ServiceResolverFailoverPolicy) isValid() bool {
if fp == nil {
return true
}

switch fp.Mode {
case "":
case "default":
case "order-by-locality":
default:
return false
}

return true
}

type ServiceResolverFailoverTarget struct {
// Service specifies the name of the service to try during failover.
Service string `json:",omitempty"`
Expand Down
14 changes: 14 additions & 0 deletions agent/structs/config_entry_discoverychain_oss.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,17 @@ func (req *DiscoveryChainRequest) GetEnterpriseMeta() *acl.EnterpriseMeta {
func (req *DiscoveryChainRequest) WithEnterpriseMeta(_ *acl.EnterpriseMeta) {
// do nothing
}

// ValidateEnterprise validates that enterprise fields are only set
// with enterprise binaries.
func (f *ServiceResolverFailoverPolicy) ValidateEnterprise() error {
if f == nil {
return nil
}

if f.Mode != "" {
return fmt.Errorf("Setting failover policies requires Consul Enterprise")
}

return nil
}
11 changes: 11 additions & 0 deletions agent/structs/config_entry_discoverychain_oss_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ func TestServiceResolverConfigEntry_OSS(t *testing.T) {
},
validateErr: `Bad Failover["*"]: Setting Namespace requires Consul Enterprise`,
},
{
name: "setting failover Namespace on OSS",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
Failover: map[string]ServiceResolverFailover{
"*": {Service: "s1", Policy: &ServiceResolverFailoverPolicy{Mode: "something"}},
},
},
validateErr: `Bad Failover["*"]: Setting failover policies requires Consul Enterprise`,
},
{
name: "setting redirect Namespace on OSS",
entry: &ServiceResolverConfigEntry{
Expand Down
19 changes: 19 additions & 0 deletions agent/structs/config_entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3195,6 +3195,25 @@ func TestProxyConfigEntry(t *testing.T) {
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
},
},
"proxy config has invalid failover policy": {
entry: &ProxyConfigEntry{
Name: "global",
FailoverPolicy: &ServiceResolverFailoverPolicy{Mode: "bad"},
},
validateErr: `Failover policy must be one of '', 'default', or 'order-by-locality'`,
},
"proxy config with valid failover policy": {
entry: &ProxyConfigEntry{
Name: "global",
FailoverPolicy: &ServiceResolverFailoverPolicy{Mode: "order-by-locality"},
},
expected: &ProxyConfigEntry{
Name: ProxyConfigGlobal,
Kind: ProxyDefaults,
FailoverPolicy: &ServiceResolverFailoverPolicy{Mode: "order-by-locality"},
EnterpriseMeta: *acl.DefaultEnterpriseMeta(),
},
},
"proxy config has invalid access log type": {
entry: &ProxyConfigEntry{
Name: "global",
Expand Down
3 changes: 2 additions & 1 deletion agent/structs/discovery_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ type DiscoverySplit struct {

// compiled form of ServiceResolverFailover
type DiscoveryFailover struct {
Targets []string `json:",omitempty"`
Targets []string `json:",omitempty"`
Policy *ServiceResolverFailoverPolicy `json:",omitempty"`
}

// DiscoveryTarget represents all of the inputs necessary to use a resolver
Expand Down
8 changes: 8 additions & 0 deletions agent/structs/structs.deepcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ func (o *DiscoveryFailover) DeepCopy() *DiscoveryFailover {
cp.Targets = make([]string, len(o.Targets))
copy(cp.Targets, o.Targets)
}
if o.Policy != nil {
cp.Policy = new(ServiceResolverFailoverPolicy)
*cp.Policy = *o.Policy
}
return &cp
}

Expand Down Expand Up @@ -894,6 +898,10 @@ func (o *ServiceResolverFailover) DeepCopy() *ServiceResolverFailover {
cp.Targets = make([]ServiceResolverFailoverTarget, len(o.Targets))
copy(cp.Targets, o.Targets)
}
if o.Policy != nil {
cp.Policy = new(ServiceResolverFailoverPolicy)
*cp.Policy = *o.Policy
}
return &cp
}

Expand Down
5 changes: 5 additions & 0 deletions api/config_entry_discoverychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ type ServiceResolverFailover struct {
Namespace string `json:",omitempty"`
Datacenters []string `json:",omitempty"`
Targets []ServiceResolverFailoverTarget `json:",omitempty"`
Policy *ServiceResolverFailoverPolicy `json:",omitempty"`
}

type ServiceResolverFailoverTarget struct {
Expand All @@ -253,6 +254,10 @@ type ServiceResolverFailoverTarget struct {
Peer string `json:",omitempty"`
}

type ServiceResolverFailoverPolicy struct {
Mode string `json:",omitempty"`
}

// LoadBalancer determines the load balancing policy and configuration for services
// issuing requests to this upstream service.
type LoadBalancer struct {
Expand Down
1 change: 1 addition & 0 deletions api/discovery_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ func (r *DiscoveryResolver) UnmarshalJSON(data []byte) error {
// compiled form of ServiceResolverFailover
type DiscoveryFailover struct {
Targets []string
Policy ServiceResolverFailoverPolicy `json:",omitempty"`
}

// DiscoveryTarget represents all of the inputs necessary to use a resolver
Expand Down
22 changes: 22 additions & 0 deletions proto/private/pbconfigentry/config_entry.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions proto/private/pbconfigentry/config_entry.pb.binary.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b13c7bb

Please sign in to comment.