Restructure structs and other PR comments

pull/8585/head
freddygv 2020-09-02 09:10:50 -06:00
parent 0236e169bb
commit 63f79e5f9b
43 changed files with 1228 additions and 2284 deletions

View File

@ -745,11 +745,13 @@ func (c *compiler) getSplitterNode(sid structs.ServiceID) (*structs.DiscoveryGra
// with distinct hash-based load balancer configs specified in their service resolvers.
// We cannot apply multiple hash policies to a splitter node's route action.
// Therefore, we attach the first hash-based load balancer config we encounter.
if !hasLB && node.LoadBalancer.IsHashBased() {
if !hasLB {
if lb := node.LoadBalancer; lb != nil && lb.EnvoyLBConfig != nil && lb.EnvoyLBConfig.IsHashBased() {
splitNode.LoadBalancer = node.LoadBalancer
hasLB = true
}
}
}
c.usesAdvancedRoutingFeatures = true
return splitNode, nil

View File

@ -1761,14 +1761,16 @@ func testcase_AllBellsAndWhistles() compileTestCase {
"prod": {Filter: "ServiceMeta.env == prod"},
"qa": {Filter: "ServiceMeta.env == qa"},
},
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "ring_hash",
RingHashConfig: structs.RingHashConfig{
RingHashConfig: &structs.RingHashConfig{
MaximumRingSize: 100,
},
HashPolicies: []structs.HashPolicy{
{
SourceAddress: true,
SourceIP: true,
},
},
},
},
@ -1833,14 +1835,16 @@ func testcase_AllBellsAndWhistles() compileTestCase {
NextNode: "resolver:v3.main.default.dc1",
},
},
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "ring_hash",
RingHashConfig: structs.RingHashConfig{
RingHashConfig: &structs.RingHashConfig{
MaximumRingSize: 100,
},
HashPolicies: []structs.HashPolicy{
{
SourceAddress: true,
SourceIP: true,
},
},
},
},
@ -1852,14 +1856,16 @@ func testcase_AllBellsAndWhistles() compileTestCase {
ConnectTimeout: 5 * time.Second,
Target: "prod.redirected.default.dc1",
},
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "ring_hash",
RingHashConfig: structs.RingHashConfig{
RingHashConfig: &structs.RingHashConfig{
MaximumRingSize: 100,
},
HashPolicies: []structs.HashPolicy{
{
SourceAddress: true,
SourceIP: true,
},
},
},
},
@ -2275,24 +2281,28 @@ func testcase_LBConfig() compileTestCase {
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "foo",
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "least_request",
LeastRequestConfig: structs.LeastRequestConfig{
LeastRequestConfig: &structs.LeastRequestConfig{
ChoiceCount: 3,
},
},
},
},
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "bar",
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "ring_hash",
RingHashConfig: structs.RingHashConfig{
RingHashConfig: &structs.RingHashConfig{
MaximumRingSize: 101,
},
HashPolicies: []structs.HashPolicy{
{
SourceAddress: true,
SourceIP: true,
},
},
},
},
@ -2300,17 +2310,23 @@ func testcase_LBConfig() compileTestCase {
&structs.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "baz",
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "maglev",
HashPolicies: []structs.HashPolicy{
{
Field: "cookie",
FieldMatchValue: "chocolate-chip",
FieldValue: "chocolate-chip",
CookieConfig: &structs.CookieConfig{
TTL: 2 * time.Minute,
Path: "/bowl",
},
Terminal: true,
},
},
},
},
},
)
expect := &structs.CompiledDiscoveryChain{
@ -2336,14 +2352,16 @@ func testcase_LBConfig() compileTestCase {
},
// The LB config from bar is attached because splitters only care about hash-based policies,
// and it's the config from bar not baz because we pick the first one we encounter in the Splits.
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "ring_hash",
RingHashConfig: structs.RingHashConfig{
RingHashConfig: &structs.RingHashConfig{
MaximumRingSize: 101,
},
HashPolicies: []structs.HashPolicy{
{
SourceAddress: true,
SourceIP: true,
},
},
},
},
@ -2357,13 +2375,15 @@ func testcase_LBConfig() compileTestCase {
ConnectTimeout: 5 * time.Second,
Target: "foo.default.dc1",
},
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "least_request",
LeastRequestConfig: structs.LeastRequestConfig{
LeastRequestConfig: &structs.LeastRequestConfig{
ChoiceCount: 3,
},
},
},
},
"resolver:bar.default.dc1": {
Type: structs.DiscoveryGraphNodeTypeResolver,
Name: "bar.default.dc1",
@ -2372,14 +2392,16 @@ func testcase_LBConfig() compileTestCase {
ConnectTimeout: 5 * time.Second,
Target: "bar.default.dc1",
},
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "ring_hash",
RingHashConfig: structs.RingHashConfig{
RingHashConfig: &structs.RingHashConfig{
MaximumRingSize: 101,
},
HashPolicies: []structs.HashPolicy{
{
SourceAddress: true,
SourceIP: true,
},
},
},
},
@ -2392,18 +2414,24 @@ func testcase_LBConfig() compileTestCase {
ConnectTimeout: 5 * time.Second,
Target: "baz.default.dc1",
},
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "maglev",
HashPolicies: []structs.HashPolicy{
{
Field: "cookie",
FieldMatchValue: "chocolate-chip",
FieldValue: "chocolate-chip",
CookieConfig: &structs.CookieConfig{
TTL: 2 * time.Minute,
Path: "/bowl",
},
Terminal: true,
},
},
},
},
},
},
Targets: map[string]*structs.DiscoveryTarget{
"foo.default.dc1": newTarget("foo", "", "default", "dc1", nil),
"bar.default.dc1": newTarget("bar", "", "default", "dc1", nil),

View File

@ -1274,29 +1274,31 @@ func setupTestVariationConfigEntriesAndSnapshot(
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "db",
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "ring_hash",
RingHashConfig: structs.RingHashConfig{
RingHashConfig: &structs.RingHashConfig{
MinimumRingSize: 20,
MaximumRingSize: 30,
},
HashPolicies: []structs.HashPolicy{
{
Field: "cookie",
FieldMatchValue: "chocolate-chip",
FieldValue: "chocolate-chip",
Terminal: true,
},
{
Field: "header",
FieldMatchValue: "x-user-id",
FieldValue: "x-user-id",
},
{
SourceAddress: true,
SourceIP: true,
Terminal: true,
},
},
},
},
},
)
case "http-multiple-services":
default:

View File

@ -3,6 +3,7 @@ package structs
import (
"encoding/json"
"fmt"
"github.com/golang/protobuf/ptypes"
"math"
"regexp"
"sort"
@ -10,12 +11,29 @@ import (
"strings"
"time"
envoy "github.com/envoyproxy/go-control-plane/envoy/api/v2"
envoyroute "github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/cache"
"github.com/hashicorp/consul/lib"
"github.com/mitchellh/hashstructure"
)
const (
// Names of Envoy's LB policies
LBPolicyMaglev = "maglev"
LBPolicyRingHash = "ring_hash"
LBPolicyRandom = "random"
LBPolicyLeastRequest = "least_request"
LBPolicyRoundRobin = "round_robin"
// Names of Envoy's LB policies
HashPolicyCookie = "cookie"
HashPolicyHeader = "header"
HashPolicyQueryParam = "query_parameter"
)
// ServiceRouterConfigEntry defines L7 (e.g. http) routing rules for a named
// service exposed in Connect.
//
@ -641,7 +659,7 @@ type ServiceResolverConfigEntry struct {
// LoadBalancer determines the load balancing policy and configuration for services
// issuing requests to this upstream service.
LoadBalancer LoadBalancer `json:",omitempty" alias:"load_balancer"`
LoadBalancer *LoadBalancer `json:",omitempty" alias:"load_balancer"`
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex
@ -811,53 +829,60 @@ func (e *ServiceResolverConfigEntry) Validate() error {
return fmt.Errorf("Bad ConnectTimeout '%s', must be >= 0", e.ConnectTimeout)
}
if e.LoadBalancer != nil && e.LoadBalancer.EnvoyLBConfig != nil {
ec := e.LoadBalancer.EnvoyLBConfig
validPolicies := map[string]bool{
"": true,
"random": true,
"round_robin": true,
"least_request": true,
"ring_hash": true,
"maglev": true,
LBPolicyRandom: true,
LBPolicyRoundRobin: true,
LBPolicyLeastRequest: true,
LBPolicyRingHash: true,
LBPolicyMaglev: true,
}
if ok := validPolicies[e.LoadBalancer.Policy]; !ok {
return fmt.Errorf("Bad LoadBalancer policy: %q is not supported", e.LoadBalancer.Policy)
if ok := validPolicies[ec.Policy]; !ok {
return fmt.Errorf("Bad LoadBalancer policy: %q is not supported", ec.Policy)
}
if e.LoadBalancer.Policy != "ring_hash" && e.LoadBalancer.RingHashConfig != (RingHashConfig{}) {
if ec.Policy != LBPolicyRingHash && ec.RingHashConfig != nil {
return fmt.Errorf("Bad LoadBalancer configuration. "+
"RingHashConfig specified for incompatible load balancing policy %q", e.LoadBalancer.Policy)
"RingHashConfig specified for incompatible load balancing policy %q", ec.Policy)
}
if e.LoadBalancer.Policy != "least_request" && e.LoadBalancer.LeastRequestConfig != (LeastRequestConfig{}) {
if ec.Policy != LBPolicyLeastRequest && ec.LeastRequestConfig != nil {
return fmt.Errorf("Bad LoadBalancer configuration. "+
"LeastRequestConfig specified for incompatible load balancing policy %q", e.LoadBalancer.Policy)
"LeastRequestConfig specified for incompatible load balancing policy %q", ec.Policy)
}
if !e.LoadBalancer.IsHashBased() && len(e.LoadBalancer.HashPolicies) > 0 {
if !ec.IsHashBased() && len(ec.HashPolicies) > 0 {
return fmt.Errorf("Bad LoadBalancer configuration: "+
"HashPolicies specified for non-hash-based Policy: %q", e.LoadBalancer.Policy)
"HashPolicies specified for non-hash-based Policy: %q", ec.Policy)
}
validFields := map[string]bool{
"header": true,
"cookie": true,
"query_parameter": true,
HashPolicyHeader: true,
HashPolicyCookie: true,
HashPolicyQueryParam: true,
}
for i, hp := range e.LoadBalancer.HashPolicies {
for i, hp := range ec.HashPolicies {
if ok := validFields[hp.Field]; hp.Field != "" && !ok {
return fmt.Errorf("Bad LoadBalancer HashPolicy[%d]: %q is not a supported field", i, hp.Field)
}
if hp.SourceAddress && hp.Field != "" {
if hp.SourceIP && hp.Field != "" {
return fmt.Errorf("Bad LoadBalancer HashPolicy[%d]: "+
"A single hash policy cannot hash both a source address and a %q", i, hp.Field)
}
if hp.SourceAddress && hp.FieldMatchValue != "" {
if hp.SourceIP && hp.FieldValue != "" {
return fmt.Errorf("Bad LoadBalancer HashPolicy[%d]: "+
"A FieldMatchValue cannot be specified when hashing SourceAddress", i)
"A FieldValue cannot be specified when hashing SourceIP", i)
}
if hp.Field != "" && hp.FieldMatchValue == "" {
return fmt.Errorf("Bad LoadBalancer HashPolicy[%d]: Field %q was specified without a FieldMatchValue", i, hp.Field)
if hp.Field != "" && hp.FieldValue == "" {
return fmt.Errorf("Bad LoadBalancer HashPolicy[%d]: Field %q was specified without a FieldValue", i, hp.Field)
}
if hp.FieldValue != "" && hp.Field == "" {
return fmt.Errorf("Bad LoadBalancer HashPolicy[%d]: FieldValue requires a Field to apply to", i)
}
if hp.CookieConfig != nil && hp.Field != HashPolicyCookie {
return fmt.Errorf("Bad LoadBalancer HashPolicy[%d]: cookie_config provided for %q", i, hp.Field)
}
if hp.FieldMatchValue != "" && hp.Field == "" {
return fmt.Errorf("Bad LoadBalancer HashPolicy[%d]: FieldMatchValue requires a Field to apply to", i)
}
}
@ -1000,14 +1025,22 @@ type ServiceResolverFailover struct {
// LoadBalancer determines the load balancing policy and configuration for services
// issuing requests to this upstream service.
type LoadBalancer struct {
// EnvoyLBConfig contains Envoy-specific load balancing configuration for this upstream
EnvoyLBConfig *EnvoyLBConfig `json:",omitempty" alias:"envoy_lb_config"`
// OpaqueConfig contains load balancing configuration opaque to Consul for 3rd party proxies
OpaqueConfig string `json:",omitempty" alias:"opaque_config"`
}
type EnvoyLBConfig struct {
// Policy is the load balancing policy used to select a host
Policy string `json:",omitempty"`
// RingHashConfig contains configuration for the "ring_hash" policy type
RingHashConfig RingHashConfig `json:",omitempty" alias:"ring_hash_config"`
RingHashConfig *RingHashConfig `json:",omitempty" alias:"ring_hash_config"`
// LeastRequestConfig contains configuration for the "least_request" policy type
LeastRequestConfig LeastRequestConfig `json:",omitempty" alias:"least_request_config"`
LeastRequestConfig *LeastRequestConfig `json:",omitempty" alias:"least_request_config"`
// HashPolicies is a list of hash policies to use for hashing load balancing algorithms.
// Hash policies are evaluated individually and combined such that identical lists
@ -1017,15 +1050,6 @@ type LoadBalancer struct {
HashPolicies []HashPolicy `json:",omitempty" alias:"hash_policies"`
}
func (l LoadBalancer) IsHashBased() bool {
switch l.Policy {
case "maglev", "ring_hash":
return true
default:
return false
}
}
// RingHashConfig contains configuration for the "ring_hash" policy type
type RingHashConfig struct {
// MinimumRingSize determines the minimum number of entries in the hash ring
@ -1041,25 +1065,24 @@ type LeastRequestConfig struct {
ChoiceCount uint32 `json:",omitempty" alias:"choice_count"`
}
// HashPolicy is a list of hash policies to use for hashing load balancing algorithms.
// Hash policies are evaluated individually and combined such that identical lists
// result in the same hash.
// If no hash policies are present, or none are successfully evaluated,
// then a random backend host will be selected.
// HashPolicy defines which attributes will be hashed by hash-based LB algorithms
type HashPolicy struct {
// Field is the attribute type to hash on.
// Must be one of "header","cookie", or "query_parameter".
// Cannot be specified along with SourceIP.
Field string `json:",omitempty"`
// FieldMatchValue is the value to hash.
// FieldValue is the value to hash.
// ie. header name, cookie name, URL query parameter name
// Cannot be specified along with SourceIP.
FieldMatchValue string `json:",omitempty" alias:"field_value"`
FieldValue string `json:",omitempty" alias:"field_value"`
// SourceAddress determines whether the hash should be of the source IP rather than of a field and field value.
// Cannot be specified along with Field or FieldMatchValue.
SourceAddress bool `json:",omitempty" alias:"source_address"`
// CookieConfig contains configuration for the "cookie" hash policy type.
CookieConfig *CookieConfig `json:",omitempty" alias:"cookie_config"`
// SourceIP determines whether the hash should be of the source IP rather than of a field and field value.
// Cannot be specified along with Field or FieldValue.
SourceIP bool `json:",omitempty" alias:"source_ip"`
// Terminal will short circuit the computation of the hash when multiple hash policies are present.
// If a hash is computed when a Terminal policy is evaluated,
@ -1067,6 +1090,134 @@ type HashPolicy struct {
Terminal bool `json:",omitempty"`
}
// CookieConfig contains configuration for the "cookie" hash policy type.
// This is specified to have Envoy generate a cookie for a client on its first request.
type CookieConfig struct {
// TTL for generated cookies
TTL time.Duration `json:",omitempty"`
// The path to set for the cookie
Path string `json:",omitempty"`
}
func (ec *EnvoyLBConfig) IsHashBased() bool {
if ec == nil {
return false
}
switch ec.Policy {
case LBPolicyMaglev, LBPolicyRingHash:
return true
default:
return false
}
}
func (ec *EnvoyLBConfig) InjectToCluster(c *envoy.Cluster) error {
if ec == nil {
return nil
}
switch ec.Policy {
case "":
return nil
case LBPolicyLeastRequest:
c.LbPolicy = envoy.Cluster_LEAST_REQUEST
if ec.LeastRequestConfig != nil {
c.LbConfig = &envoy.Cluster_LeastRequestLbConfig_{
LeastRequestLbConfig: &envoy.Cluster_LeastRequestLbConfig{
ChoiceCount: &wrappers.UInt32Value{Value: ec.LeastRequestConfig.ChoiceCount},
},
}
}
case LBPolicyRoundRobin:
c.LbPolicy = envoy.Cluster_ROUND_ROBIN
case LBPolicyRandom:
c.LbPolicy = envoy.Cluster_RANDOM
case LBPolicyRingHash:
c.LbPolicy = envoy.Cluster_RING_HASH
if ec.RingHashConfig != nil {
c.LbConfig = &envoy.Cluster_RingHashLbConfig_{
RingHashLbConfig: &envoy.Cluster_RingHashLbConfig{
MinimumRingSize: &wrappers.UInt64Value{Value: ec.RingHashConfig.MinimumRingSize},
MaximumRingSize: &wrappers.UInt64Value{Value: ec.RingHashConfig.MaximumRingSize},
},
}
}
case LBPolicyMaglev:
c.LbPolicy = envoy.Cluster_MAGLEV
default:
return fmt.Errorf("unsupported load balancer policy %q for cluster %q", ec.Policy, c.Name)
}
return nil
}
func (ec *EnvoyLBConfig) InjectToRouteAction(action *envoyroute.RouteAction) error {
if ec == nil || !ec.IsHashBased() {
return nil
}
result := make([]*envoyroute.RouteAction_HashPolicy, 0, len(ec.HashPolicies))
for _, policy := range ec.HashPolicies {
if policy.SourceIP {
result = append(result, &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_ConnectionProperties_{
ConnectionProperties: &envoyroute.RouteAction_HashPolicy_ConnectionProperties{
SourceIp: true,
},
},
Terminal: policy.Terminal,
})
continue
}
switch policy.Field {
case HashPolicyHeader:
result = append(result, &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Header_{
Header: &envoyroute.RouteAction_HashPolicy_Header{
HeaderName: policy.FieldValue,
},
},
Terminal: policy.Terminal,
})
case HashPolicyCookie:
cookie := envoyroute.RouteAction_HashPolicy_Cookie{
Name: policy.FieldValue,
}
if policy.CookieConfig != nil {
cookie.Ttl = ptypes.DurationProto(policy.CookieConfig.TTL)
cookie.Path = policy.CookieConfig.Path
}
result = append(result, &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &cookie,
},
Terminal: policy.Terminal,
})
case HashPolicyQueryParam:
result = append(result, &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_QueryParameter_{
QueryParameter: &envoyroute.RouteAction_HashPolicy_QueryParameter{
Name: policy.FieldValue,
},
},
Terminal: policy.Terminal,
})
default:
return fmt.Errorf("unsupported load balancer hash policy field: %v", policy.Field)
}
}
action.HashPolicy = result
return nil
}
type discoveryChainConfigEntry interface {
ConfigEntry
// ListRelatedServices returns a list of other names of services referenced

View File

@ -3,10 +3,14 @@ package structs
import (
"bytes"
"fmt"
"github.com/golang/protobuf/ptypes"
"strings"
"testing"
"time"
envoy "github.com/envoyproxy/go-control-plane/envoy/api/v2"
envoyroute "github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/hashicorp/consul/acl"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -554,7 +558,9 @@ func TestServiceResolverConfigEntry_LoadBalancer(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{Policy: ""},
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{Policy: ""},
},
},
},
{
@ -562,7 +568,9 @@ func TestServiceResolverConfigEntry_LoadBalancer(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{Policy: "random"},
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{Policy: LBPolicyRandom},
},
},
},
{
@ -570,7 +578,9 @@ func TestServiceResolverConfigEntry_LoadBalancer(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{Policy: "fake-policy"},
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{Policy: "fake-policy"},
},
},
validateErr: `"fake-policy" is not supported`,
},
@ -579,9 +589,11 @@ func TestServiceResolverConfigEntry_LoadBalancer(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "ring_hash",
LeastRequestConfig: LeastRequestConfig{ChoiceCount: 2},
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyRingHash,
LeastRequestConfig: &LeastRequestConfig{ChoiceCount: 10},
},
},
},
validateErr: `LeastRequestConfig specified for incompatible load balancing policy`,
@ -591,9 +603,11 @@ func TestServiceResolverConfigEntry_LoadBalancer(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "least_request",
RingHashConfig: RingHashConfig{MinimumRingSize: 1024},
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyLeastRequest,
RingHashConfig: &RingHashConfig{MinimumRingSize: 1024},
},
},
},
validateErr: `RingHashConfig specified for incompatible load balancing policy`,
@ -603,9 +617,11 @@ func TestServiceResolverConfigEntry_LoadBalancer(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "ring_hash",
RingHashConfig: RingHashConfig{MinimumRingSize: 1024},
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyRingHash,
RingHashConfig: &RingHashConfig{MinimumRingSize: 1024},
},
},
},
},
@ -614,9 +630,11 @@ func TestServiceResolverConfigEntry_LoadBalancer(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "least_request",
LeastRequestConfig: LeastRequestConfig{ChoiceCount: 2},
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyLeastRequest,
LeastRequestConfig: &LeastRequestConfig{ChoiceCount: 2},
},
},
},
},
@ -625,12 +643,12 @@ func TestServiceResolverConfigEntry_LoadBalancer(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "",
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{Policy: ""},
},
},
check: func(t *testing.T, entry *ServiceResolverConfigEntry) {
require.Equal(t, "", entry.LoadBalancer.Policy)
require.Equal(t, "", entry.LoadBalancer.EnvoyLBConfig.Policy)
},
},
{
@ -638,28 +656,77 @@ func TestServiceResolverConfigEntry_LoadBalancer(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: "",
HashPolicies: []HashPolicy{
{
SourceAddress: true,
SourceIP: true,
},
},
},
},
},
validateErr: `HashPolicies specified for non-hash-based Policy`,
},
{
name: "empty policy with hash policy",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyMaglev,
HashPolicies: []HashPolicy{
{
Field: HashPolicyHeader,
FieldValue: "x-user-id",
CookieConfig: &CookieConfig{
TTL: 10 * time.Second,
Path: "/root",
},
},
},
},
},
},
validateErr: `cookie_config provided for "header"`,
},
{
name: "empty policy with hash policy",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyMaglev,
HashPolicies: []HashPolicy{
{
Field: HashPolicyCookie,
FieldValue: "good-cookie",
CookieConfig: &CookieConfig{
TTL: 10 * time.Second,
Path: "/oven",
},
},
},
},
},
},
},
{
name: "supported match field",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "maglev",
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyMaglev,
HashPolicies: []HashPolicy{
{
Field: "header",
FieldMatchValue: "X-Consul-Token",
FieldValue: "X-Consul-Token",
},
},
},
},
@ -670,28 +737,32 @@ func TestServiceResolverConfigEntry_LoadBalancer(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "maglev",
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyMaglev,
HashPolicies: []HashPolicy{
{
Field: "not-header",
Field: "fake-field",
},
},
},
},
validateErr: `"not-header" is not a supported field`,
},
validateErr: `"fake-field" is not a supported field`,
},
{
name: "cannot match on source address and custom field",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "maglev",
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyMaglev,
HashPolicies: []HashPolicy{
{
Field: "header",
SourceAddress: true,
SourceIP: true,
},
},
},
},
@ -703,25 +774,28 @@ func TestServiceResolverConfigEntry_LoadBalancer(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "maglev",
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyMaglev,
HashPolicies: []HashPolicy{
{
FieldMatchValue: "X-Consul-Token",
SourceAddress: true,
FieldValue: "X-Consul-Token",
SourceIP: true,
},
},
},
},
validateErr: `A FieldMatchValue cannot be specified when hashing SourceAddress`,
},
validateErr: `A FieldValue cannot be specified when hashing SourceIP`,
},
{
name: "field without match value",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "maglev",
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyMaglev,
HashPolicies: []HashPolicy{
{
Field: "header",
@ -729,54 +803,61 @@ func TestServiceResolverConfigEntry_LoadBalancer(t *testing.T) {
},
},
},
validateErr: `Field "header" was specified without a FieldMatchValue`,
},
validateErr: `Field "header" was specified without a FieldValue`,
},
{
name: "field without match value",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "maglev",
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyMaglev,
HashPolicies: []HashPolicy{
{
FieldMatchValue: "my-cookie",
FieldValue: "my-cookie",
},
},
},
},
validateErr: `FieldMatchValue requires a Field to apply to`,
},
validateErr: `FieldValue requires a Field to apply to`,
},
{
name: "ring hash kitchen sink",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "ring_hash",
RingHashConfig: RingHashConfig{MaximumRingSize: 10, MinimumRingSize: 2},
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyRingHash,
RingHashConfig: &RingHashConfig{MaximumRingSize: 10, MinimumRingSize: 2},
HashPolicies: []HashPolicy{
{
Field: "cookie",
FieldMatchValue: "my-cookie",
FieldValue: "my-cookie",
},
{
Field: "header",
FieldMatchValue: "alt-header",
FieldValue: "alt-header",
Terminal: true,
},
},
},
},
},
},
{
name: "least request kitchen sink",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test",
LoadBalancer: LoadBalancer{
Policy: "least_request",
LeastRequestConfig: LeastRequestConfig{ChoiceCount: 20},
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: LBPolicyLeastRequest,
LeastRequestConfig: &LeastRequestConfig{ChoiceCount: 20},
},
},
},
},
@ -1444,3 +1525,267 @@ func TestIsProtocolHTTPLike(t *testing.T) {
assert.True(t, IsProtocolHTTPLike("http2"))
assert.True(t, IsProtocolHTTPLike("grpc"))
}
func TestEnvoyLBConfig_InjectToRouteAction(t *testing.T) {
var tests = []struct {
name string
lb *EnvoyLBConfig
expected envoyroute.RouteAction
}{
{
name: "empty",
lb: &EnvoyLBConfig{
Policy: "",
},
// we only modify route actions for hash-based LB policies
expected: envoyroute.RouteAction{},
},
{
name: "least request",
lb: &EnvoyLBConfig{
Policy: LBPolicyLeastRequest,
LeastRequestConfig: &LeastRequestConfig{
ChoiceCount: 3,
},
},
// we only modify route actions for hash-based LB policies
expected: envoyroute.RouteAction{},
},
{
name: "headers",
lb: &EnvoyLBConfig{
Policy: "ring_hash",
RingHashConfig: &RingHashConfig{
MinimumRingSize: 3,
MaximumRingSize: 7,
},
HashPolicies: []HashPolicy{
{
Field: HashPolicyHeader,
FieldValue: "x-route-key",
Terminal: true,
},
},
},
expected: envoyroute.RouteAction{
HashPolicy: []*envoyroute.RouteAction_HashPolicy{
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Header_{
Header: &envoyroute.RouteAction_HashPolicy_Header{
HeaderName: "x-route-key",
},
},
Terminal: true,
},
},
},
},
{
name: "cookies",
lb: &EnvoyLBConfig{
Policy: LBPolicyMaglev,
HashPolicies: []HashPolicy{
{
Field: HashPolicyCookie,
FieldValue: "red-velvet",
Terminal: true,
},
{
Field: HashPolicyCookie,
FieldValue: "oatmeal",
},
},
},
expected: envoyroute.RouteAction{
HashPolicy: []*envoyroute.RouteAction_HashPolicy{
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: "red-velvet",
},
},
Terminal: true,
},
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: "oatmeal",
},
},
},
},
},
},
{
name: "source addr",
lb: &EnvoyLBConfig{
Policy: LBPolicyMaglev,
HashPolicies: []HashPolicy{
{
SourceIP: true,
Terminal: true,
},
},
},
expected: envoyroute.RouteAction{
HashPolicy: []*envoyroute.RouteAction_HashPolicy{
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_ConnectionProperties_{
ConnectionProperties: &envoyroute.RouteAction_HashPolicy_ConnectionProperties{
SourceIp: true,
},
},
Terminal: true,
},
},
},
},
{
name: "kitchen sink",
lb: &EnvoyLBConfig{
Policy: LBPolicyMaglev,
HashPolicies: []HashPolicy{
{
SourceIP: true,
Terminal: true,
},
{
Field: HashPolicyCookie,
FieldValue: "oatmeal",
CookieConfig: &CookieConfig{
TTL: 10 * time.Second,
Path: "/oven",
},
},
{
Field: HashPolicyHeader,
FieldValue: "special-header",
Terminal: true,
},
},
},
expected: envoyroute.RouteAction{
HashPolicy: []*envoyroute.RouteAction_HashPolicy{
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_ConnectionProperties_{
ConnectionProperties: &envoyroute.RouteAction_HashPolicy_ConnectionProperties{
SourceIp: true,
},
},
Terminal: true,
},
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: "oatmeal",
Ttl: ptypes.DurationProto(10 * time.Second),
Path: "/oven",
},
},
},
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Header_{
Header: &envoyroute.RouteAction_HashPolicy_Header{
HeaderName: "special-header",
},
},
Terminal: true,
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var ra envoyroute.RouteAction
err := tc.lb.InjectToRouteAction(&ra)
require.NoError(t, err)
require.Equal(t, &tc.expected, &ra)
})
}
}
func TestEnvoyLBConfig_InjectToCluster(t *testing.T) {
var tests = []struct {
name string
lb *EnvoyLBConfig
expected envoy.Cluster
}{
{
name: "skip empty",
lb: &EnvoyLBConfig{
Policy: "",
},
expected: envoy.Cluster{},
},
{
name: "round robin",
lb: &EnvoyLBConfig{
Policy: LBPolicyRoundRobin,
},
expected: envoy.Cluster{LbPolicy: envoy.Cluster_ROUND_ROBIN},
},
{
name: "random",
lb: &EnvoyLBConfig{
Policy: LBPolicyRandom,
},
expected: envoy.Cluster{LbPolicy: envoy.Cluster_RANDOM},
},
{
name: "maglev",
lb: &EnvoyLBConfig{
Policy: LBPolicyMaglev,
},
expected: envoy.Cluster{LbPolicy: envoy.Cluster_MAGLEV},
},
{
name: "ring_hash",
lb: &EnvoyLBConfig{
Policy: LBPolicyRingHash,
RingHashConfig: &RingHashConfig{
MinimumRingSize: 3,
MaximumRingSize: 7,
},
},
expected: envoy.Cluster{
LbPolicy: envoy.Cluster_RING_HASH,
LbConfig: &envoy.Cluster_RingHashLbConfig_{
RingHashLbConfig: &envoy.Cluster_RingHashLbConfig{
MinimumRingSize: &wrappers.UInt64Value{Value: 3},
MaximumRingSize: &wrappers.UInt64Value{Value: 7},
},
},
},
},
{
name: "least_request",
lb: &EnvoyLBConfig{
Policy: "least_request",
LeastRequestConfig: &LeastRequestConfig{
ChoiceCount: 3,
},
},
expected: envoy.Cluster{
LbPolicy: envoy.Cluster_LEAST_REQUEST,
LbConfig: &envoy.Cluster_LeastRequestLbConfig_{
LeastRequestLbConfig: &envoy.Cluster_LeastRequestLbConfig{
ChoiceCount: &wrappers.UInt32Value{Value: 3},
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var c envoy.Cluster
err := tc.lb.InjectToCluster(&c)
require.NoError(t, err)
require.Equal(t, tc.expected, c)
})
}
}

View File

@ -109,7 +109,7 @@ type DiscoveryGraphNode struct {
Resolver *DiscoveryResolver `json:",omitempty"`
// shared by Type==resolver || Type==splitter
LoadBalancer LoadBalancer `json:",omitempty"`
LoadBalancer *LoadBalancer `json:",omitempty"`
}
func (s *DiscoveryGraphNode) IsRouter() bool {

View File

@ -16,7 +16,6 @@ import (
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/any"
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/proxycfg"
"github.com/hashicorp/consul/agent/structs"
@ -206,13 +205,14 @@ func (s *Server) makeGatewayServiceClusters(cfgSnap *proxycfg.ConfigSnapshot) ([
clusterName := connect.ServiceSNI(svc.Name, "", svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
resolver, hasResolver := resolvers[svc]
var lb structs.LoadBalancer
var loadBalancer *structs.EnvoyLBConfig
if !hasResolver {
if hasResolver && resolver.LoadBalancer != nil {
loadBalancer = resolver.LoadBalancer.EnvoyLBConfig
} else {
// Use a zero value resolver with no timeout and no subsets
resolver = &structs.ServiceResolverConfigEntry{}
} else {
lb = resolver.LoadBalancer
}
// When making service clusters we only pass endpoints with hostnames if the kind is a terminating gateway
@ -232,16 +232,14 @@ func (s *Server) makeGatewayServiceClusters(cfgSnap *proxycfg.ConfigSnapshot) ([
case structs.ServiceKindTerminatingGateway:
injectTerminatingGatewayTLSContext(cfgSnap, cluster, svc)
err := injectLBToCluster(lb, cluster)
if err != nil {
if err := loadBalancer.InjectToCluster(cluster); err != nil {
return nil, fmt.Errorf("failed to apply load balancer configuration to cluster %q: %v", clusterName, err)
}
case structs.ServiceKindMeshGateway:
// We can't apply hash based LB config to mesh gateways because they rely on inspecting HTTP attributes
// and mesh gateways do not decrypt traffic
if !lb.IsHashBased() {
err := injectLBToCluster(lb, cluster)
if err != nil {
if !loadBalancer.IsHashBased() {
if err := loadBalancer.InjectToCluster(cluster); err != nil {
return nil, fmt.Errorf("failed to apply load balancer configuration to cluster %q: %v", clusterName, err)
}
}
@ -267,16 +265,15 @@ func (s *Server) makeGatewayServiceClusters(cfgSnap *proxycfg.ConfigSnapshot) ([
case structs.ServiceKindTerminatingGateway:
injectTerminatingGatewayTLSContext(cfgSnap, cluster, svc)
err := injectLBToCluster(lb, cluster)
if err != nil {
if err := loadBalancer.InjectToCluster(cluster); err != nil {
return nil, fmt.Errorf("failed to apply load balancer configuration to cluster %q: %v", clusterName, err)
}
case structs.ServiceKindMeshGateway:
// We can't apply hash based LB config to mesh gateways because they rely on inspecting HTTP attributes
// and mesh gateways do not decrypt traffic
if !lb.IsHashBased() {
err := injectLBToCluster(lb, cluster)
if err != nil {
if !loadBalancer.IsHashBased() {
if err := loadBalancer.InjectToCluster(cluster); err != nil {
return nil, fmt.Errorf("failed to apply load balancer configuration to cluster %q: %v", clusterName, err)
}
}
@ -516,8 +513,12 @@ func (s *Server) makeUpstreamClustersForDiscoveryChain(
OutlierDetection: cfg.PassiveHealthCheck.AsOutlierDetection(),
}
if err := injectLBToCluster(node.LoadBalancer, c); err != nil {
return nil, fmt.Errorf("failed to apply load balancer configuration to cluster %q: %v", sni, err)
var lb *structs.EnvoyLBConfig
if node.LoadBalancer != nil {
lb = node.LoadBalancer.EnvoyLBConfig
}
if err := lb.InjectToCluster(c); err != nil {
return nil, fmt.Errorf("failed to apply load balancer configuration to cluster %q: %v", clusterName, err)
}
proto := cfg.Protocol
@ -557,37 +558,6 @@ func (s *Server) makeUpstreamClustersForDiscoveryChain(
return out, nil
}
func injectLBToCluster(l structs.LoadBalancer, c *envoy.Cluster) error {
switch l.Policy {
case "":
return nil
case "least_request":
c.LbPolicy = envoy.Cluster_LEAST_REQUEST
c.LbConfig = &envoy.Cluster_LeastRequestLbConfig_{
LeastRequestLbConfig: &envoy.Cluster_LeastRequestLbConfig{
ChoiceCount: &wrappers.UInt32Value{Value: l.LeastRequestConfig.ChoiceCount},
},
}
case "round_robin":
c.LbPolicy = envoy.Cluster_ROUND_ROBIN
case "random":
c.LbPolicy = envoy.Cluster_RANDOM
case "ring_hash":
c.LbPolicy = envoy.Cluster_RING_HASH
c.LbConfig = &envoy.Cluster_RingHashLbConfig_{
RingHashLbConfig: &envoy.Cluster_RingHashLbConfig{
MinimumRingSize: &wrappers.UInt64Value{Value: l.RingHashConfig.MinimumRingSize},
MaximumRingSize: &wrappers.UInt64Value{Value: l.RingHashConfig.MaximumRingSize},
},
}
case "maglev":
c.LbPolicy = envoy.Cluster_MAGLEV
default:
return fmt.Errorf("unsupported load balancer policy %q for cluster %q", l.Policy, c.Name)
}
return nil
}
// makeClusterFromUserConfig returns the listener config decoded from an
// arbitrary proto3 json format string or an error if it's invalid.
//

View File

@ -2,7 +2,6 @@ package xds
import (
"bytes"
"github.com/golang/protobuf/ptypes/wrappers"
"path/filepath"
"sort"
"testing"
@ -367,13 +366,15 @@ func TestClustersFromSnapshot(t *testing.T) {
OnlyPassing: true,
},
},
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "least_request",
LeastRequestConfig: structs.LeastRequestConfig{
LeastRequestConfig: &structs.LeastRequestConfig{
ChoiceCount: 5,
},
},
},
},
}
},
},
@ -394,14 +395,16 @@ func TestClustersFromSnapshot(t *testing.T) {
OnlyPassing: true,
},
},
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "ring_hash",
RingHashConfig: structs.RingHashConfig{
RingHashConfig: &structs.RingHashConfig{
MinimumRingSize: 20,
MaximumRingSize: 50,
},
},
},
},
}
},
},
@ -605,14 +608,16 @@ func TestClustersFromSnapshot(t *testing.T) {
OnlyPassing: true,
},
},
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "ring_hash",
RingHashConfig: structs.RingHashConfig{
RingHashConfig: &structs.RingHashConfig{
MinimumRingSize: 20,
MaximumRingSize: 50,
},
},
},
},
}
},
},
@ -834,86 +839,3 @@ func setupTLSRootsAndLeaf(t *testing.T, snap *proxycfg.ConfigSnapshot) {
snap.Roots.Roots[0].RootCert = golden(t, "test-root-cert", "", "")
}
}
func TestLoadBalancer_injectLBToCluster(t *testing.T) {
var tests = []struct {
name string
lb structs.LoadBalancer
expected envoy.Cluster
}{
{
name: "skip empty",
lb: structs.LoadBalancer{
Policy: "",
},
expected: envoy.Cluster{},
},
{
name: "round_robin",
lb: structs.LoadBalancer{
Policy: "round_robin",
},
expected: envoy.Cluster{LbPolicy: envoy.Cluster_ROUND_ROBIN},
},
{
name: "random",
lb: structs.LoadBalancer{
Policy: "random",
},
expected: envoy.Cluster{LbPolicy: envoy.Cluster_RANDOM},
},
{
name: "maglev",
lb: structs.LoadBalancer{
Policy: "maglev",
},
expected: envoy.Cluster{LbPolicy: envoy.Cluster_MAGLEV},
},
{
name: "ring_hash",
lb: structs.LoadBalancer{
Policy: "ring_hash",
RingHashConfig: structs.RingHashConfig{
MinimumRingSize: 3,
MaximumRingSize: 7,
},
},
expected: envoy.Cluster{
LbPolicy: envoy.Cluster_RING_HASH,
LbConfig: &envoy.Cluster_RingHashLbConfig_{
RingHashLbConfig: &envoy.Cluster_RingHashLbConfig{
MinimumRingSize: &wrappers.UInt64Value{Value: 3},
MaximumRingSize: &wrappers.UInt64Value{Value: 7},
},
},
},
},
{
name: "least_request",
lb: structs.LoadBalancer{
Policy: "least_request",
LeastRequestConfig: structs.LeastRequestConfig{
ChoiceCount: 3,
},
},
expected: envoy.Cluster{
LbPolicy: envoy.Cluster_LEAST_REQUEST,
LbConfig: &envoy.Cluster_LeastRequestLbConfig_{
LeastRequestLbConfig: &envoy.Cluster_LeastRequestLbConfig{
ChoiceCount: &wrappers.UInt32Value{Value: 3},
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var c envoy.Cluster
err := injectLBToCluster(tc.lb, &c)
require.NoError(t, err)
require.Equal(t, tc.expected, c)
})
}
}

View File

@ -43,14 +43,14 @@ func routesFromSnapshotTerminatingGateway(_ connectionInfo, cfgSnap *proxycfg.Co
}
var resources []proto.Message
for svc := range cfgSnap.TerminatingGateway.ServiceGroups {
for _, svc := range cfgSnap.TerminatingGateway.ValidServices() {
clusterName := connect.ServiceSNI(svc.Name, "", svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
resolver, hasResolver := cfgSnap.TerminatingGateway.ServiceResolvers[svc]
svcConfig := cfgSnap.TerminatingGateway.ServiceConfigs[svc]
cfg, err := ParseProxyConfig(svcConfig.ProxyConfig)
if err != nil || cfg.Protocol != "http" {
if err != nil || structs.IsProtocolHTTPLike(cfg.Protocol) {
// Routes can only be defined for HTTP services
continue
}
@ -59,7 +59,12 @@ func routesFromSnapshotTerminatingGateway(_ connectionInfo, cfgSnap *proxycfg.Co
// Use a zero value resolver with no timeout and no subsets
resolver = &structs.ServiceResolverConfigEntry{}
}
route, err := makeNamedDefaultRouteWithLB(clusterName, resolver.LoadBalancer)
var lb *structs.EnvoyLBConfig
if resolver.LoadBalancer != nil {
lb = resolver.LoadBalancer.EnvoyLBConfig
}
route, err := makeNamedDefaultRouteWithLB(clusterName, lb)
if err != nil {
continue
}
@ -68,7 +73,7 @@ func routesFromSnapshotTerminatingGateway(_ connectionInfo, cfgSnap *proxycfg.Co
// If there is a service-resolver for this service then also setup routes for each subset
for name := range resolver.Subsets {
clusterName = connect.ServiceSNI(svc.Name, name, svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
route, err := makeNamedDefaultRouteWithLB(clusterName, resolver.LoadBalancer)
route, err := makeNamedDefaultRouteWithLB(clusterName, lb)
if err != nil {
continue
}
@ -76,13 +81,13 @@ func routesFromSnapshotTerminatingGateway(_ connectionInfo, cfgSnap *proxycfg.Co
}
}
// TODO(rb): make sure we don't generate an empty result
return resources, nil
}
func makeNamedDefaultRouteWithLB(clusterName string, lb structs.LoadBalancer) (*envoy.RouteConfiguration, error) {
func makeNamedDefaultRouteWithLB(clusterName string, lb *structs.EnvoyLBConfig) (*envoy.RouteConfiguration, error) {
action := makeRouteActionFromName(clusterName)
if err := injectLBToRouteAction(lb, action.Route); err != nil {
if err := lb.InjectToRouteAction(action.Route); err != nil {
return nil, fmt.Errorf("failed to apply load balancer configuration to route action: %v", err)
}
@ -250,6 +255,8 @@ func makeUpstreamRouteForDiscoveryChain(
return nil, fmt.Errorf("missing first node in compiled discovery chain for: %s", chain.ServiceName)
}
var lb *structs.EnvoyLBConfig
switch startNode.Type {
case structs.DiscoveryGraphNodeTypeRouter:
routes = make([]*envoyroute.Route, 0, len(startNode.Routes))
@ -263,6 +270,10 @@ func makeUpstreamRouteForDiscoveryChain(
)
nextNode := chain.Nodes[discoveryRoute.NextNode]
if nextNode.LoadBalancer != nil {
lb = nextNode.LoadBalancer.EnvoyLBConfig
}
switch nextNode.Type {
case structs.DiscoveryGraphNodeTypeSplitter:
routeAction, err = makeRouteActionForSplitter(nextNode.Splits, chain)
@ -270,14 +281,14 @@ func makeUpstreamRouteForDiscoveryChain(
return nil, err
}
if err := injectLBToRouteAction(nextNode.LoadBalancer, routeAction.Route); err != nil {
if err := lb.InjectToRouteAction(routeAction.Route); err != nil {
return nil, fmt.Errorf("failed to apply load balancer configuration to route action: %v", err)
}
case structs.DiscoveryGraphNodeTypeResolver:
routeAction = makeRouteActionForChainCluster(nextNode.Resolver.Target, chain)
if err := injectLBToRouteAction(nextNode.LoadBalancer, routeAction.Route); err != nil {
if err := lb.InjectToRouteAction(routeAction.Route); err != nil {
return nil, fmt.Errorf("failed to apply load balancer configuration to route action: %v", err)
}
@ -332,7 +343,10 @@ func makeUpstreamRouteForDiscoveryChain(
return nil, err
}
if err := injectLBToRouteAction(startNode.LoadBalancer, routeAction.Route); err != nil {
if startNode.LoadBalancer != nil {
lb = startNode.LoadBalancer.EnvoyLBConfig
}
if err := lb.InjectToRouteAction(routeAction.Route); err != nil {
return nil, fmt.Errorf("failed to apply load balancer configuration to route action: %v", err)
}
@ -346,7 +360,10 @@ func makeUpstreamRouteForDiscoveryChain(
case structs.DiscoveryGraphNodeTypeResolver:
routeAction := makeRouteActionForChainCluster(startNode.Resolver.Target, chain)
if err := injectLBToRouteAction(startNode.LoadBalancer, routeAction.Route); err != nil {
if startNode.LoadBalancer != nil {
lb = startNode.LoadBalancer.EnvoyLBConfig
}
if err := lb.InjectToRouteAction(routeAction.Route); err != nil {
return nil, fmt.Errorf("failed to apply load balancer configuration to route action: %v", err)
}
@ -370,62 +387,6 @@ func makeUpstreamRouteForDiscoveryChain(
return host, nil
}
func injectLBToRouteAction(lb structs.LoadBalancer, action *envoyroute.RouteAction) error {
if !lb.IsHashBased() {
return nil
}
result := make([]*envoyroute.RouteAction_HashPolicy, 0, len(lb.HashPolicies))
for _, policy := range lb.HashPolicies {
if policy.SourceAddress {
result = append(result, &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_ConnectionProperties_{
ConnectionProperties: &envoyroute.RouteAction_HashPolicy_ConnectionProperties{
SourceIp: true,
},
},
Terminal: policy.Terminal,
})
continue
}
switch policy.Field {
case "header":
result = append(result, &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Header_{
Header: &envoyroute.RouteAction_HashPolicy_Header{
HeaderName: policy.FieldMatchValue,
},
},
Terminal: policy.Terminal,
})
case "cookie":
result = append(result, &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: policy.FieldMatchValue,
},
},
Terminal: policy.Terminal,
})
case "query_parameter":
result = append(result, &envoyroute.RouteAction_HashPolicy{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_QueryParameter_{
QueryParameter: &envoyroute.RouteAction_HashPolicy_QueryParameter{
Name: policy.FieldMatchValue,
},
},
Terminal: policy.Terminal,
})
default:
return fmt.Errorf("unsupported load balancer hash policy field: %v", policy.Field)
}
}
action.HashPolicy = result
return nil
}
func makeRouteMatchForDiscoveryRoute(_ connectionInfo, discoveryRoute *structs.DiscoveryRoute) *envoyroute.RouteMatch {
match := discoveryRoute.Definition.Match
if match == nil || match.IsEmpty() {

View File

@ -7,7 +7,6 @@ import (
"time"
envoy "github.com/envoyproxy/go-control-plane/envoy/api/v2"
envoyroute "github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
"github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/consul/discoverychain"
"github.com/hashicorp/consul/agent/proxycfg"
@ -203,29 +202,31 @@ func TestRoutesFromSnapshot(t *testing.T) {
OnlyPassing: true,
},
},
LoadBalancer: structs.LoadBalancer{
LoadBalancer: &structs.LoadBalancer{
EnvoyLBConfig: &structs.EnvoyLBConfig{
Policy: "ring_hash",
RingHashConfig: structs.RingHashConfig{
RingHashConfig: &structs.RingHashConfig{
MinimumRingSize: 20,
MaximumRingSize: 50,
},
HashPolicies: []structs.HashPolicy{
{
Field: "cookie",
FieldMatchValue: "chocolate-chip",
Field: structs.HashPolicyCookie,
FieldValue: "chocolate-chip",
Terminal: true,
},
{
Field: "header",
FieldMatchValue: "x-user-id",
Field: structs.HashPolicyHeader,
FieldValue: "x-user-id",
},
{
SourceAddress: true,
SourceIP: true,
Terminal: true,
},
},
},
},
},
}
},
},
@ -276,178 +277,3 @@ func TestRoutesFromSnapshot(t *testing.T) {
})
}
}
func TestLoadBalancer_injectLBToRouteAction(t *testing.T) {
var tests = []struct {
name string
lb structs.LoadBalancer
expected envoyroute.RouteAction
}{
{
name: "empty",
lb: structs.LoadBalancer{
Policy: "",
},
// we only modify route actions for hash-based LB policies
expected: envoyroute.RouteAction{},
},
{
name: "least_request",
lb: structs.LoadBalancer{
Policy: "least_request",
LeastRequestConfig: structs.LeastRequestConfig{
ChoiceCount: 3,
},
},
// we only modify route actions for hash-based LB policies
expected: envoyroute.RouteAction{},
},
{
name: "header",
lb: structs.LoadBalancer{
Policy: "ring_hash",
RingHashConfig: structs.RingHashConfig{
MinimumRingSize: 3,
MaximumRingSize: 7,
},
HashPolicies: []structs.HashPolicy{
{
Field: "header",
FieldMatchValue: "x-route-key",
Terminal: true,
},
},
},
expected: envoyroute.RouteAction{
HashPolicy: []*envoyroute.RouteAction_HashPolicy{
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Header_{
Header: &envoyroute.RouteAction_HashPolicy_Header{
HeaderName: "x-route-key",
},
},
Terminal: true,
},
},
},
},
{
name: "cookies",
lb: structs.LoadBalancer{
Policy: "maglev",
HashPolicies: []structs.HashPolicy{
{
Field: "cookie",
FieldMatchValue: "red-velvet",
Terminal: true,
},
{
Field: "cookie",
FieldMatchValue: "oatmeal",
},
},
},
expected: envoyroute.RouteAction{
HashPolicy: []*envoyroute.RouteAction_HashPolicy{
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: "red-velvet",
},
},
Terminal: true,
},
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: "oatmeal",
},
},
},
},
},
},
{
name: "source addr",
lb: structs.LoadBalancer{
Policy: "maglev",
HashPolicies: []structs.HashPolicy{
{
SourceAddress: true,
Terminal: true,
},
},
},
expected: envoyroute.RouteAction{
HashPolicy: []*envoyroute.RouteAction_HashPolicy{
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_ConnectionProperties_{
ConnectionProperties: &envoyroute.RouteAction_HashPolicy_ConnectionProperties{
SourceIp: true,
},
},
Terminal: true,
},
},
},
},
{
name: "kitchen sink",
lb: structs.LoadBalancer{
Policy: "maglev",
HashPolicies: []structs.HashPolicy{
{
SourceAddress: true,
Terminal: true,
},
{
Field: "cookie",
FieldMatchValue: "oatmeal",
},
{
Field: "header",
FieldMatchValue: "special-header",
Terminal: true,
},
},
},
expected: envoyroute.RouteAction{
HashPolicy: []*envoyroute.RouteAction_HashPolicy{
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_ConnectionProperties_{
ConnectionProperties: &envoyroute.RouteAction_HashPolicy_ConnectionProperties{
SourceIp: true,
},
},
Terminal: true,
},
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Cookie_{
Cookie: &envoyroute.RouteAction_HashPolicy_Cookie{
Name: "oatmeal",
},
},
},
{
PolicySpecifier: &envoyroute.RouteAction_HashPolicy_Header_{
Header: &envoyroute.RouteAction_HashPolicy_Header{
HeaderName: "special-header",
},
},
Terminal: true,
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ra := &envoyroute.RouteAction{}
err := injectLBToRouteAction(tc.lb, ra)
require.NoError(t, err)
require.Equal(t, &tc.expected, ra)
})
}
}

View File

@ -111,38 +111,6 @@
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
}
],

View File

@ -111,38 +111,6 @@
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
}
],

View File

@ -111,38 +111,6 @@
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
}
],

View File

@ -111,38 +111,6 @@
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
}
],

View File

@ -111,38 +111,6 @@
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
}
],

View File

@ -111,38 +111,6 @@
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
}
],

View File

@ -111,38 +111,6 @@
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
}
],

View File

@ -111,38 +111,6 @@
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"outlierDetection": {
}
}
],

View File

@ -12,7 +12,7 @@
}
}
},
"connectTimeout": "10s",
"connectTimeout": "5s",
"outlierDetection": {
}
@ -111,38 +111,6 @@
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "10s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "10s",
"outlierDetection": {
}
}
],

View File

@ -12,7 +12,7 @@
}
}
},
"connectTimeout": "10s",
"connectTimeout": "5s",
"outlierDetection": {
}
@ -111,38 +111,6 @@
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "10s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "10s",
"outlierDetection": {
}
}
],

View File

@ -12,7 +12,7 @@
}
}
},
"connectTimeout": "10s",
"connectTimeout": "5s",
"outlierDetection": {
}
@ -111,38 +111,6 @@
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "10s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "10s",
"outlierDetection": {
}
}
],

View File

@ -12,7 +12,7 @@
}
}
},
"connectTimeout": "10s",
"connectTimeout": "5s",
"outlierDetection": {
}
@ -111,38 +111,6 @@
"connectTimeout": "5s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "10s",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.bar.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "10s",
"outlierDetection": {
}
}
],

View File

@ -1,60 +1,6 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "alt.api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "LOGICAL_DNS",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "alt.api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "api.altdomain",
"portValue": 8081
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"tlsCertificates": [
{
"certificateChain": {
"filename": "api.cert.pem"
},
"privateKey": {
"filename": "api.key.pem"
}
}
],
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"dnsRefreshRate": "10s",
"dnsLookupFamily": "V4_ONLY",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
@ -173,38 +119,6 @@
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "LOGICAL_DNS",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "cache.mydomain",
"portValue": 8081
}
}
},
"healthStatus": "UNHEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
"dnsRefreshRate": "10s",
"dnsLookupFamily": "V4_ONLY",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",

View File

@ -1,60 +1,6 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "alt.api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "LOGICAL_DNS",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "alt.api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "api.altdomain",
"portValue": 8081
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"tlsCertificates": [
{
"certificateChain": {
"filename": "api.cert.pem"
},
"privateKey": {
"filename": "api.key.pem"
}
}
],
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"dnsRefreshRate": "10s",
"dnsLookupFamily": "V4_ONLY",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
@ -173,38 +119,6 @@
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "LOGICAL_DNS",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "cache.mydomain",
"portValue": 8081
}
}
},
"healthStatus": "UNHEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
"dnsRefreshRate": "10s",
"dnsLookupFamily": "V4_ONLY",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",

View File

@ -1,60 +1,6 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "alt.api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "LOGICAL_DNS",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "alt.api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "api.altdomain",
"portValue": 8081
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"tlsCertificates": [
{
"certificateChain": {
"filename": "api.cert.pem"
},
"privateKey": {
"filename": "api.key.pem"
}
}
],
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"dnsRefreshRate": "10s",
"dnsLookupFamily": "V4_ONLY",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
@ -173,38 +119,6 @@
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "LOGICAL_DNS",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "cache.mydomain",
"portValue": 8081
}
}
},
"healthStatus": "UNHEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
"dnsRefreshRate": "10s",
"dnsLookupFamily": "V4_ONLY",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",

View File

@ -1,60 +1,6 @@
{
"versionInfo": "00000001",
"resources": [
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "alt.api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "LOGICAL_DNS",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "alt.api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "api.altdomain",
"portValue": 8081
}
}
},
"healthStatus": "HEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"tlsCertificates": [
{
"certificateChain": {
"filename": "api.cert.pem"
},
"privateKey": {
"filename": "api.key.pem"
}
}
],
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"dnsRefreshRate": "10s",
"dnsLookupFamily": "V4_ONLY",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
@ -173,38 +119,6 @@
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "LOGICAL_DNS",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "cache.mydomain",
"portValue": 8081
}
}
},
"healthStatus": "UNHEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
"dnsRefreshRate": "10s",
"dnsLookupFamily": "V4_ONLY",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",

View File

@ -119,62 +119,6 @@
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",

View File

@ -119,62 +119,6 @@
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",

View File

@ -119,62 +119,6 @@
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",

View File

@ -119,62 +119,6 @@
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",

View File

@ -119,94 +119,6 @@
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "LOGICAL_DNS",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "cache.mydomain",
"portValue": 8081
}
}
},
"healthStatus": "UNHEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
"dnsRefreshRate": "10s",
"dnsLookupFamily": "V4_ONLY",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",

View File

@ -119,94 +119,6 @@
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "LOGICAL_DNS",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "cache.mydomain",
"portValue": 8081
}
}
},
"healthStatus": "UNHEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
"dnsRefreshRate": "10s",
"dnsLookupFamily": "V4_ONLY",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",

View File

@ -119,94 +119,6 @@
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "LOGICAL_DNS",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "cache.mydomain",
"portValue": 8081
}
}
},
"healthStatus": "UNHEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
"dnsRefreshRate": "10s",
"dnsLookupFamily": "V4_ONLY",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",

View File

@ -119,94 +119,6 @@
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "LOGICAL_DNS",
"connectTimeout": "5s",
"loadAssignment": {
"clusterName": "prod.cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "cache.mydomain",
"portValue": 8081
}
}
},
"healthStatus": "UNHEALTHY",
"loadBalancingWeight": 1
}
]
}
]
},
"dnsRefreshRate": "10s",
"dnsLookupFamily": "V4_ONLY",
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
}
},
"connectTimeout": "5s",
"tlsContext": {
"commonTlsContext": {
"tlsParams": {
},
"validationContext": {
"trustedCa": {
"filename": "ca.cert.pem"
}
}
}
},
"outlierDetection": {
}
},
{
"@type": "type.googleapis.com/envoy.api.v2.Cluster",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",

View File

@ -3,10 +3,10 @@
"resources": [
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"virtualHosts": [
{
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"domains": [
"*"
],
@ -16,26 +16,7 @@
"prefix": "/"
},
"route": {
"cluster": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"hashPolicy": [
{
"cookie": {
"name": "chocolate-chip"
},
"terminal": true
},
{
"header": {
"headerName": "x-user-id"
}
},
{
"connectionProperties": {
"sourceIp": true
},
"terminal": true
}
]
"cluster": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
@ -45,10 +26,10 @@
},
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"virtualHosts": [
{
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"domains": [
"*"
],
@ -58,26 +39,7 @@
"prefix": "/"
},
"route": {
"cluster": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"hashPolicy": [
{
"cookie": {
"name": "chocolate-chip"
},
"terminal": true
},
{
"header": {
"headerName": "x-user-id"
}
},
{
"connectionProperties": {
"sourceIp": true
},
"terminal": true
}
]
"cluster": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
@ -87,10 +49,10 @@
},
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"virtualHosts": [
{
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"domains": [
"*"
],
@ -100,26 +62,7 @@
"prefix": "/"
},
"route": {
"cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"hashPolicy": [
{
"cookie": {
"name": "chocolate-chip"
},
"terminal": true
},
{
"header": {
"headerName": "x-user-id"
}
},
{
"connectionProperties": {
"sourceIp": true
},
"terminal": true
}
]
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]

View File

@ -3,10 +3,10 @@
"resources": [
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"virtualHosts": [
{
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"domains": [
"*"
],
@ -16,26 +16,7 @@
"prefix": "/"
},
"route": {
"cluster": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"hashPolicy": [
{
"cookie": {
"name": "chocolate-chip"
},
"terminal": true
},
{
"header": {
"headerName": "x-user-id"
}
},
{
"connectionProperties": {
"sourceIp": true
},
"terminal": true
}
]
"cluster": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
@ -45,10 +26,10 @@
},
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"virtualHosts": [
{
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"domains": [
"*"
],
@ -58,26 +39,7 @@
"prefix": "/"
},
"route": {
"cluster": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"hashPolicy": [
{
"cookie": {
"name": "chocolate-chip"
},
"terminal": true
},
{
"header": {
"headerName": "x-user-id"
}
},
{
"connectionProperties": {
"sourceIp": true
},
"terminal": true
}
]
"cluster": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
@ -87,10 +49,10 @@
},
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"virtualHosts": [
{
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"domains": [
"*"
],
@ -100,26 +62,7 @@
"prefix": "/"
},
"route": {
"cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"hashPolicy": [
{
"cookie": {
"name": "chocolate-chip"
},
"terminal": true
},
{
"header": {
"headerName": "x-user-id"
}
},
{
"connectionProperties": {
"sourceIp": true
},
"terminal": true
}
]
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]

View File

@ -3,10 +3,10 @@
"resources": [
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"virtualHosts": [
{
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"domains": [
"*"
],
@ -16,26 +16,7 @@
"prefix": "/"
},
"route": {
"cluster": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"hashPolicy": [
{
"cookie": {
"name": "chocolate-chip"
},
"terminal": true
},
{
"header": {
"headerName": "x-user-id"
}
},
{
"connectionProperties": {
"sourceIp": true
},
"terminal": true
}
]
"cluster": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
@ -45,10 +26,10 @@
},
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"virtualHosts": [
{
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"domains": [
"*"
],
@ -58,26 +39,7 @@
"prefix": "/"
},
"route": {
"cluster": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"hashPolicy": [
{
"cookie": {
"name": "chocolate-chip"
},
"terminal": true
},
{
"header": {
"headerName": "x-user-id"
}
},
{
"connectionProperties": {
"sourceIp": true
},
"terminal": true
}
]
"cluster": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
@ -87,10 +49,10 @@
},
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"virtualHosts": [
{
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"domains": [
"*"
],
@ -100,26 +62,7 @@
"prefix": "/"
},
"route": {
"cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"hashPolicy": [
{
"cookie": {
"name": "chocolate-chip"
},
"terminal": true
},
{
"header": {
"headerName": "x-user-id"
}
},
{
"connectionProperties": {
"sourceIp": true
},
"terminal": true
}
]
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]

View File

@ -3,10 +3,10 @@
"resources": [
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"virtualHosts": [
{
"name": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"domains": [
"*"
],
@ -16,26 +16,7 @@
"prefix": "/"
},
"route": {
"cluster": "v1.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"hashPolicy": [
{
"cookie": {
"name": "chocolate-chip"
},
"terminal": true
},
{
"header": {
"headerName": "x-user-id"
}
},
{
"connectionProperties": {
"sourceIp": true
},
"terminal": true
}
]
"cluster": "api.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
@ -45,10 +26,10 @@
},
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"virtualHosts": [
{
"name": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"domains": [
"*"
],
@ -58,26 +39,7 @@
"prefix": "/"
},
"route": {
"cluster": "v2.web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"hashPolicy": [
{
"cookie": {
"name": "chocolate-chip"
},
"terminal": true
},
{
"header": {
"headerName": "x-user-id"
}
},
{
"connectionProperties": {
"sourceIp": true
},
"terminal": true
}
]
"cluster": "cache.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]
@ -87,10 +49,10 @@
},
{
"@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"virtualHosts": [
{
"name": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"domains": [
"*"
],
@ -100,26 +62,7 @@
"prefix": "/"
},
"route": {
"cluster": "web.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"hashPolicy": [
{
"cookie": {
"name": "chocolate-chip"
},
"terminal": true
},
{
"header": {
"headerName": "x-user-id"
}
},
{
"connectionProperties": {
"sourceIp": true
},
"terminal": true
}
]
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
}
}
]

View File

@ -140,7 +140,7 @@ type ServiceResolverConfigEntry struct {
// LoadBalancer determines the load balancing policy and configuration for services
// issuing requests to this upstream service.
LoadBalancer LoadBalancer `json:",omitempty" alias:"load_balancer"`
LoadBalancer *LoadBalancer `json:",omitempty" alias:"load_balancer"`
CreateIndex uint64
ModifyIndex uint64
@ -209,14 +209,22 @@ type ServiceResolverFailover struct {
// LoadBalancer determines the load balancing policy and configuration for services
// issuing requests to this upstream service.
type LoadBalancer struct {
// EnvoyLBConfig contains Envoy-specific load balancing configuration for this upstream
EnvoyLBConfig *EnvoyLBConfig `json:",omitempty" alias:"envoy_lb_config"`
// OpaqueConfig contains load balancing configuration opaque to Consul for 3rd party proxies
OpaqueConfig string `json:",omitempty" alias:"opaque_config"`
}
type EnvoyLBConfig struct {
// Policy is the load balancing policy used to select a host
Policy string `json:",omitempty"`
// RingHashConfig contains configuration for the "ring_hash" policy type
RingHashConfig RingHashConfig `json:",omitempty" alias:"ring_hash_config"`
RingHashConfig *RingHashConfig `json:",omitempty" alias:"ring_hash_config"`
// LeastRequestConfig contains configuration for the "least_request" policy type
LeastRequestConfig LeastRequestConfig `json:",omitempty" alias:"least_request_config"`
LeastRequestConfig *LeastRequestConfig `json:",omitempty" alias:"least_request_config"`
// HashPolicies is a list of hash policies to use for hashing load balancing algorithms.
// Hash policies are evaluated individually and combined such that identical lists
@ -241,28 +249,37 @@ type LeastRequestConfig struct {
ChoiceCount uint32 `json:",omitempty" alias:"choice_count"`
}
// HashPolicy is a list of hash policies to use for hashing load balancing algorithms.
// Hash policies are evaluated individually and combined such that identical lists
// result in the same hash.
// If no hash policies are present, or none are successfully evaluated,
// then a random backend host will be selected.
// HashPolicy defines which attributes will be hashed by hash-based LB algorithms
type HashPolicy struct {
// Field is the attribute type to hash on.
// Must be one of "header","cookie", or "query_parameter".
// Cannot be specified along with SourceIP.
Field string `json:",omitempty"`
// FieldMatchValue is the value to hash.
// FieldValue is the value to hash.
// ie. header name, cookie name, URL query parameter name
// Cannot be specified along with SourceIP.
FieldMatchValue string `json:",omitempty" alias:"field_value"`
FieldValue string `json:",omitempty" alias:"field_value"`
// SourceAddress determines whether the hash should be of the source IP rather than of a field and field value.
// Cannot be specified along with Field and FieldMatchValue.
SourceAddress bool `json:",omitempty" alias:"source_address"`
// CookieConfig contains configuration for the "cookie" hash policy type.
CookieConfig *CookieConfig `json:",omitempty" alias:"cookie_config"`
// SourceIP determines whether the hash should be of the source IP rather than of a field and field value.
// Cannot be specified along with Field or FieldValue.
SourceIP bool `json:",omitempty" alias:"source_ip"`
// Terminal will short circuit the computation of the hash when multiple hash policies are present.
// If a hash is computed when a Terminal policy is evaluated,
// then that hash will be used and subsequent hash policies will be ignored.
Terminal bool `json:",omitempty"`
}
// CookieConfig contains configuration for the "cookie" hash policy type.
// This is specified to have Envoy generate a cookie for a client on its first request.
type CookieConfig struct {
// TTL for generated cookies
TTL time.Duration `json:",omitempty"`
// The path to set for the cookie
Path string `json:",omitempty"`
}

View File

@ -293,9 +293,11 @@ func TestAPI_ConfigEntry_ServiceResolver_LoadBalancer(t *testing.T) {
Kind: ServiceResolver,
Name: "test-least-req",
Namespace: defaultNamespace,
LoadBalancer: LoadBalancer{
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: "least_request",
LeastRequestConfig: LeastRequestConfig{ChoiceCount: 10},
LeastRequestConfig: &LeastRequestConfig{ChoiceCount: 10},
},
},
},
verify: verifyResolver,
@ -306,20 +308,30 @@ func TestAPI_ConfigEntry_ServiceResolver_LoadBalancer(t *testing.T) {
Kind: ServiceResolver,
Name: "test-ring-hash",
Namespace: defaultNamespace,
LoadBalancer: LoadBalancer{
LoadBalancer: &LoadBalancer{
EnvoyLBConfig: &EnvoyLBConfig{
Policy: "ring_hash",
RingHashConfig: RingHashConfig{
RingHashConfig: &RingHashConfig{
MinimumRingSize: 1024 * 2,
MaximumRingSize: 1024 * 4,
},
HashPolicies: []HashPolicy{
{
Field: "header",
FieldMatchValue: "my-session-header",
FieldValue: "my-session-header",
Terminal: true,
},
{
SourceAddress: true,
Field: "cookie",
FieldValue: "oreo",
CookieConfig: &CookieConfig{
Path: "/tray",
TTL: 20 * time.Millisecond,
},
},
{
SourceIP: true,
},
},
},
},

View File

@ -1,6 +1,7 @@
package write
import (
"github.com/hashicorp/consul/agent/structs"
"io"
"strings"
"testing"
@ -1165,6 +1166,235 @@ func TestParseConfigEntry(t *testing.T) {
Name: "main",
},
},
{
name: "service-resolver: envoy hash lb kitchen sink",
snake: `
kind = "service-resolver"
name = "main"
load_balancer = {
envoy_lb_config = {
policy = "ring_hash"
ring_hash_config = {
minimum_ring_size = 1
maximum_ring_size = 2
}
hash_policies = [
{
field = "cookie"
field_value = "good-cookie"
cookie_config = {
ttl = "1s"
path = "/oven"
}
terminal = true
},
{
field = "header"
field_value = "x-user-id"
},
{
source_ip = true
}
]
}
}
`,
camel: `
Kind = "service-resolver"
Name = "main"
LoadBalancer = {
EnvoyLBConfig = {
Policy = "ring_hash"
RingHashConfig = {
MinimumRingSize = 1
MaximumRingSize = 2
}
HashPolicies = [
{
Field = "cookie"
FieldValue = "good-cookie"
CookieConfig = {
TTL = "1s"
Path = "/oven"
}
Terminal = true
},
{
Field = "header"
FieldValue = "x-user-id"
},
{
SourceIP = true
}
]
}
}
`,
snakeJSON: `
{
"kind": "service-resolver",
"name": "main",
"load_balancer": {
"envoy_lb_config": {
"policy": "ring_hash",
"ring_hash_config": {
"minimum_ring_size": 1,
"maximum_ring_size": 2
},
"hash_policies": [
{
"field": "cookie",
"field_value": "good-cookie",
"cookie_config": {
"ttl": "1s",
"path": "/oven"
},
"terminal": true
},
{
"field": "header",
"field_value": "x-user-id"
},
{
"source_ip": true
}
]
}
}
}
`,
camelJSON: `
{
"Kind": "service-resolver",
"Name": "main",
"LoadBalancer": {
"EnvoyLBConfig": {
"Policy": "ring_hash",
"RingHashConfig": {
"MinimumRingSize": 1,
"MaximumRingSize": 2
},
"HashPolicies": [
{
"Field": "cookie",
"FieldValue": "good-cookie",
"CookieConfig": {
"TTL": "1s",
"Path": "/oven"
},
"Terminal": true
},
{
"Field": "header",
"FieldValue": "x-user-id"
},
{
"SourceIP": true
}
]
}
}
}
`,
expect: &api.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
LoadBalancer: &api.LoadBalancer{
EnvoyLBConfig: &api.EnvoyLBConfig{
Policy: structs.LBPolicyRingHash,
RingHashConfig: &api.RingHashConfig{
MinimumRingSize: 1,
MaximumRingSize: 2,
},
HashPolicies: []api.HashPolicy{
{
Field: structs.HashPolicyCookie,
FieldValue: "good-cookie",
CookieConfig: &api.CookieConfig{
TTL: 1 * time.Second,
Path: "/oven",
},
Terminal: true,
},
{
Field: structs.HashPolicyHeader,
FieldValue: "x-user-id",
},
{
SourceIP: true,
},
},
},
},
},
},
{
name: "service-resolver: envoy least request kitchen sink",
snake: `
kind = "service-resolver"
name = "main"
load_balancer = {
envoy_lb_config = {
policy = "least_request"
least_request_config = {
choice_count = 2
}
}
}
`,
camel: `
Kind = "service-resolver"
Name = "main"
LoadBalancer = {
EnvoyLBConfig = {
Policy = "least_request"
LeastRequestConfig = {
ChoiceCount = 2
}
}
}
`,
snakeJSON: `
{
"kind": "service-resolver",
"name": "main",
"load_balancer": {
"envoy_lb_config": {
"policy": "least_request",
"least_request_config": {
"choice_count": 2
}
}
}
}
`,
camelJSON: `
{
"Kind": "service-resolver",
"Name": "main",
"LoadBalancer": {
"EnvoyLBConfig": {
"Policy": "least_request",
"LeastRequestConfig": {
"ChoiceCount": 2
}
}
}
}
`,
expect: &api.ServiceResolverConfigEntry{
Kind: "service-resolver",
Name: "main",
LoadBalancer: &api.LoadBalancer{
EnvoyLBConfig: &api.EnvoyLBConfig{
Policy: structs.LBPolicyLeastRequest,
LeastRequestConfig: &api.LeastRequestConfig{
ChoiceCount: 2,
},
},
},
},
},
{
name: "expose paths: kitchen sink proxy defaults",
snake: `

View File

@ -83,7 +83,7 @@ LoadBalancer = {
HashPolicies = [
{
Field = "header"
FieldMatchValue = "x-user-id"
FieldValue = "x-user-id"
}
]
}
@ -171,7 +171,8 @@ LoadBalancer = {
configuration for services issuing requests to this upstream.
This option is available in Consul versions 1.8.4 and newer.
**Note:** The options below are specific to Envoy proxy.
- `EnvoyLBConfig` `(EnvoyLBConfig)` - Envoy proxy specific load balancing configuration
for this upstream.
- `Policy` `(string: "")` - The load balancing policy used to select a host.
One of: `random`, `round_robin`, `least_request`, `ring_hash`, `maglev`.
@ -201,13 +202,16 @@ LoadBalancer = {
Must be one of `header`,`cookie`, or `query_parameter`.
Cannot be specified along with `SourceAddress`.
- `FieldMatchValue` `(string: "")` - The value to hash.
- `FieldValue` `(string: "")` - The value to hash.
ie. header name, cookie name, URL query parameter name.
Cannot be specified along with `SourceAddress`.
- `SourceAddress` `(bool: false)` - Determines whether the hash should be of the source IP
- `CookieConfig` `(CookieConfig)` - Additional configuration for the "cookie" hash policy type.
This is specified to have Envoy generate a cookie for a client on its first request.
- `SourceIP` `(bool: false)` - Determines whether the hash should be of the source IP
address rather than of a field and field value.
Cannot be specified along with `Field` or `FieldMatchValue`.
Cannot be specified along with `Field` or `FieldValue`.
- `Terminal` `(bool: false)` - Will short circuit the computation of the hash
when multiple hash policies are present. If a hash is computed when a

View File

@ -185,6 +185,12 @@ A single node in the compiled discovery chain.
- `Targets` `(array<string>)` - List of targets found in
[`Targets`](#targets) to failover to in order of preference.
- `LoadBalancer` `(LoadBalancer: <optional>`) - Copy of the underlying `service-resolver`
[`LoadBalancer`](/docs/agent/config-entries/service-resolver#loadbalancer) field.
If a `service-splitter` splits between services with differing `LoadBalancer` configuration
the first hash-based load balancing policy is copied.
#### `DiscoveryTarget`
- `ID` `(string)` - The unique name of this target.