Consistent hashing to a subset of nodes. It works like consistent hash,

but instead of mapping to a single node, we map to a subset of nodes.
This commit is contained in:
Diego Woitasen 2018-11-10 20:30:06 -03:00
parent 29118750be
commit 60b983503b
17 changed files with 434 additions and 17 deletions

View file

@ -92,7 +92,7 @@ type Ingress struct {
SessionAffinity sessionaffinity.Config
SSLPassthrough bool
UsePortInRedirects bool
UpstreamHashBy string
UpstreamHashBy upstreamhashby.Config
LoadBalancing string
UpstreamVhost string
Whitelist ipwhitelist.SourceRange

View file

@ -186,7 +186,7 @@ func TestUpstreamHashBy(t *testing.T) {
for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations)
r := ec.Extract(ing).UpstreamHashBy
r := ec.Extract(ing).UpstreamHashBy.UpstreamHashBy
if r != foo.er {
t.Errorf("Returned %v but expected %v", r, foo.er)
}

View file

@ -27,14 +27,27 @@ type upstreamhashby struct {
r resolver.Resolver
}
// NewParser creates a new CORS annotation parser
// Config contains the Consistent hash configuration to be used in the Ingress
type Config struct {
UpstreamHashBy string `json:"upstream-hash-by,omitempty"`
UpstreamHashBySubset bool `json:"upstream-hash-by-subset,omitempty"`
UpstreamHashBySubsetSize int `json:"upstream-hash-by-subset-size,omitempty"`
}
// NewParser creates a new UpstreamHashBy annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return upstreamhashby{r}
}
// Parse parses the annotations contained in the ingress rule
// used to indicate if the location/s contains a fragment of
// configuration to be included inside the paths of the rules
func (a upstreamhashby) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetStringAnnotation("upstream-hash-by", ing)
upstreamHashBy, _ := parser.GetStringAnnotation("upstream-hash-by", ing)
upstreamHashBySubset, _ := parser.GetBoolAnnotation("upstream-hash-by-subset", ing)
upstreamHashbySubsetSize, _ := parser.GetIntAnnotation("upstream-hash-by-subset-size", ing)
if upstreamHashbySubsetSize == 0 {
upstreamHashbySubsetSize = 3
}
return &Config{upstreamHashBy, upstreamHashBySubset, upstreamHashbySubsetSize}, nil
}

View file

@ -55,7 +55,12 @@ func TestParse(t *testing.T) {
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
if result != testCase.expected {
uc, ok := result.(*Config)
if !ok {
t.Fatalf("expected a Config type")
}
if uc.UpstreamHashBy != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}

View file

@ -660,9 +660,13 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
if upstreams[defBackend].SecureCACert.Secret == "" {
upstreams[defBackend].SecureCACert = anns.SecureUpstream.CACert
}
if upstreams[defBackend].UpstreamHashBy == "" {
upstreams[defBackend].UpstreamHashBy = anns.UpstreamHashBy
if upstreams[defBackend].UpstreamHashBy.UpstreamHashBy == "" {
upstreams[defBackend].UpstreamHashBy.UpstreamHashBy = anns.UpstreamHashBy.UpstreamHashBy
upstreams[defBackend].UpstreamHashBy.UpstreamHashBySubset = anns.UpstreamHashBy.UpstreamHashBySubset
upstreams[defBackend].UpstreamHashBy.UpstreamHashBySubsetSize = anns.UpstreamHashBy.UpstreamHashBySubsetSize
}
if upstreams[defBackend].LoadBalancing == "" {
upstreams[defBackend].LoadBalancing = anns.LoadBalancing
}
@ -724,8 +728,10 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
upstreams[name].SecureCACert = anns.SecureUpstream.CACert
}
if upstreams[name].UpstreamHashBy == "" {
upstreams[name].UpstreamHashBy = anns.UpstreamHashBy
if upstreams[name].UpstreamHashBy.UpstreamHashBy == "" {
upstreams[name].UpstreamHashBy.UpstreamHashBy = anns.UpstreamHashBy.UpstreamHashBy
upstreams[name].UpstreamHashBy.UpstreamHashBySubset = anns.UpstreamHashBy.UpstreamHashBySubset
upstreams[name].UpstreamHashBy.UpstreamHashBySubsetSize = anns.UpstreamHashBy.UpstreamHashBySubsetSize
}
if upstreams[name].LoadBalancing == "" {

View file

@ -113,6 +113,14 @@ type Backend struct {
// http://nginx.org/en/docs/http/ngx_http_upstream_module.html#hash
UpstreamHashBy string `json:"upstream-hash-by"`
// Consistent hashing subset flag.
// Default: false
UpstreamHashBySubset bool `json:"upstream-hash-by-subset"`
// Subset consistent hashing, subset size.
// Default 3
UpstreamHashBySubsetSize int `json:"upstream-hash-by-subset-size"`
// Let's us choose a load balancing algorithm per ingress
LoadBalancing string `json:"load-balance"`

View file

@ -93,7 +93,7 @@ type Backend struct {
// StickySessionAffinitySession contains the StickyConfig object with stickyness configuration
SessionAffinity SessionAffinityConfig `json:"sessionAffinityConfig"`
// Consistent hashing by NGINX variable
UpstreamHashBy string `json:"upstream-hash-by,omitempty"`
UpstreamHashBy UpstreamHashByConfig `json:"upstreamHashByConfig,omitempty"`
// LB algorithm configuration per ingress
LoadBalancing string `json:"load-balance,omitempty"`
// Denotes if a backend has no server. The backend instead shares a server with another backend and acts as an
@ -150,6 +150,13 @@ type CookieSessionAffinity struct {
Path string `json:"path,omitempty"`
}
// UpstreamHashByConfig described setting from the upstream-hash-by* annotations.
type UpstreamHashByConfig struct {
UpstreamHashBy string `json:"upstream-hash-by,omitempty"`
UpstreamHashBySubset bool `json:"upstream-hash-by-subset,omitempty"`
UpstreamHashBySubsetSize int `json:"upstream-hash-by-subset-size,omitempty"`
}
// Endpoint describes a kubernetes endpoint in a backend
// +k8s:deepcopy-gen=true
type Endpoint struct {

View file

@ -234,6 +234,27 @@ func (csa1 *CookieSessionAffinity) Equal(csa2 *CookieSessionAffinity) bool {
return true
}
//Equal checks the equality between UpstreamByConfig types
func (u1 *UpstreamHashByConfig) Equal(u2 *UpstreamHashByConfig) bool {
if u1 == u2 {
return true
}
if u1 == nil || u2 == nil {
return false
}
if u1.UpstreamHashBy != u2.UpstreamHashBy {
return false
}
if u1.UpstreamHashBySubset != u2.UpstreamHashBySubset {
return false
}
if u1.UpstreamHashBySubsetSize != u2.UpstreamHashBySubsetSize {
return false
}
return true
}
// Equal checks the equality against an Endpoint
func (e1 *Endpoint) Equal(e2 *Endpoint) bool {
if e1 == e2 {