Add support for failover policies (#16505)

This commit is contained in:
Eric Haberkorn 2023-03-03 11:12:38 -05:00 committed by GitHub
parent 6ca1c9f15c
commit 5c8414e772
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1493 additions and 1262 deletions

View File

@ -1110,6 +1110,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.

View File

@ -368,6 +368,7 @@ type ProxyConfigEntry struct {
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"`
@ -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()
}

View File

@ -1079,6 +1079,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) {
@ -1368,13 +1376,22 @@ 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,
}
}
@ -1382,6 +1399,22 @@ 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"`

View File

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

View File

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

View File

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

View File

@ -179,6 +179,7 @@ type DiscoverySplit struct {
// compiled form of ServiceResolverFailover
type DiscoveryFailover struct {
Targets []string `json:",omitempty"`
Policy *ServiceResolverFailoverPolicy `json:",omitempty"`
}
// DiscoveryTarget represents all of the inputs necessary to use a resolver

View File

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

View File

@ -243,6 +243,7 @@ type ServiceResolverFailover struct {
Namespace string `json:",omitempty"`
Datacenters []string `json:",omitempty"`
Targets []ServiceResolverFailoverTarget `json:",omitempty"`
Policy *ServiceResolverFailoverPolicy `json:",omitempty"`
}
type ServiceResolverFailoverTarget struct {
@ -254,6 +255,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 {

View File

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

View File

@ -1410,6 +1410,11 @@ func ServiceResolverFailoverToStructs(s *ServiceResolverFailover, t *structs.Ser
}
}
}
if s.Policy != nil {
var x structs.ServiceResolverFailoverPolicy
ServiceResolverFailoverPolicyToStructs(s.Policy, &x)
t.Policy = &x
}
}
func ServiceResolverFailoverFromStructs(t *structs.ServiceResolverFailover, s *ServiceResolverFailover) {
if s == nil {
@ -1429,6 +1434,23 @@ func ServiceResolverFailoverFromStructs(t *structs.ServiceResolverFailover, s *S
}
}
}
if t.Policy != nil {
var x ServiceResolverFailoverPolicy
ServiceResolverFailoverPolicyFromStructs(t.Policy, &x)
s.Policy = &x
}
}
func ServiceResolverFailoverPolicyToStructs(s *ServiceResolverFailoverPolicy, t *structs.ServiceResolverFailoverPolicy) {
if s == nil {
return
}
t.Mode = s.Mode
}
func ServiceResolverFailoverPolicyFromStructs(t *structs.ServiceResolverFailoverPolicy, s *ServiceResolverFailoverPolicy) {
if s == nil {
return
}
s.Mode = t.Mode
}
func ServiceResolverFailoverTargetToStructs(s *ServiceResolverFailoverTarget, t *structs.ServiceResolverFailoverTarget) {
if s == nil {

View File

@ -117,6 +117,16 @@ func (msg *ServiceResolverFailover) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *ServiceResolverFailoverPolicy) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *ServiceResolverFailoverPolicy) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *ServiceResolverFailoverTarget) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)

File diff suppressed because it is too large Load Diff

View File

@ -160,6 +160,16 @@ message ServiceResolverFailover {
string Namespace = 3;
repeated string Datacenters = 4;
repeated ServiceResolverFailoverTarget Targets = 5;
ServiceResolverFailoverPolicy Policy = 6;
}
// mog annotation:
//
// target=github.com/hashicorp/consul/agent/structs.ServiceResolverFailoverPolicy
// output=config_entry.gen.go
// name=Structs
message ServiceResolverFailoverPolicy {
string Mode = 1;
}
// mog annotation: