Add a mechanism to format the sticky cookie value

Co-authored-by: Jean-Baptiste Doumenjou <925513+jbdoumenjou@users.noreply.github.com>
This commit is contained in:
Tom Moulard 2021-04-29 17:56:03 +02:00 committed by GitHub
parent 70a02158e5
commit dc8d5ef744
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 49 additions and 6 deletions

View file

@ -167,8 +167,8 @@ For now, only round robin load balancing is supported:
#### Sticky sessions #### Sticky sessions
When sticky sessions are enabled, a cookie is set on the initial request and response to let the client know which server handles the first response. When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response.
On subsequent requests, to keep the session alive with the same server, the client should resend the same cookie. On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set.
!!! info "Stickiness on multiple levels" !!! info "Stickiness on multiple levels"

2
go.mod
View file

@ -77,7 +77,7 @@ require (
github.com/unrolled/render v1.0.2 github.com/unrolled/render v1.0.2
github.com/unrolled/secure v1.0.7 github.com/unrolled/secure v1.0.7
github.com/vdemeester/shakers v0.1.0 github.com/vdemeester/shakers v0.1.0
github.com/vulcand/oxy v1.2.0 github.com/vulcand/oxy v1.3.0
github.com/vulcand/predicate v1.1.0 github.com/vulcand/predicate v1.1.0
go.elastic.co/apm v1.7.0 go.elastic.co/apm v1.7.0
go.elastic.co/apm/module/apmot v1.7.0 go.elastic.co/apm/module/apmot v1.7.0

6
go.sum
View file

@ -912,6 +912,8 @@ github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKP
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM=
github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
@ -1039,8 +1041,8 @@ github.com/vdemeester/shakers v0.1.0/go.mod h1:IZ1HHynUOQt32iQ3rvAeVddXLd19h/6LW
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/vulcand/oxy v1.2.0 h1:Y2Wt1EgQddA/qMnp1+YXjehPsyw2Gy8CAfavkFF+fQk= github.com/vulcand/oxy v1.3.0 h1:358BVHmJNLjhOrhbjq2EVJX5NQ3HxrP0d5OyHLRliX0=
github.com/vulcand/oxy v1.2.0/go.mod h1:nGeNTWfyYQj3ghi3W8ok7vLSkw7Gkvr0x+G/v8Wk7vM= github.com/vulcand/oxy v1.3.0/go.mod h1:hN/gw/jg+GH4A+bqvznsW26Izd4jNGV6h1z3s7drRzs=
github.com/vulcand/predicate v1.1.0 h1:Gq/uWopa4rx/tnZu2opOSBqHK63Yqlou/SzrbwdJiNg= github.com/vulcand/predicate v1.1.0 h1:Gq/uWopa4rx/tnZu2opOSBqHK63Yqlou/SzrbwdJiNg=
github.com/vulcand/predicate v1.1.0/go.mod h1:mlccC5IRBoc2cIFmCB8ZM62I3VDb6p2GXESMHa3CnZg= github.com/vulcand/predicate v1.1.0/go.mod h1:mlccC5IRBoc2cIFmCB8ZM62I3VDb6p2GXESMHa3CnZg=
github.com/vultr/govultr/v2 v2.4.0 h1:6ySGGAsoOann0lmVNkS8grLvbAT2iYWnO4R1RVYFg0A= github.com/vultr/govultr/v2 v2.4.0 h1:6ySGGAsoOann0lmVNkS8grLvbAT2iYWnO4R1RVYFg0A=

View file

@ -26,6 +26,7 @@ import (
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror" "github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror"
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/wrr" "github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/wrr"
"github.com/vulcand/oxy/roundrobin" "github.com/vulcand/oxy/roundrobin"
"github.com/vulcand/oxy/roundrobin/stickycookie"
) )
const ( const (
@ -310,7 +311,13 @@ func (m *Manager) getLoadBalancer(ctx context.Context, serviceName string, servi
SameSite: convertSameSite(service.Sticky.Cookie.SameSite), SameSite: convertSameSite(service.Sticky.Cookie.SameSite),
} }
options = append(options, roundrobin.EnableStickySession(roundrobin.NewStickySessionWithOptions(cookieName, opts))) // Sticky Cookie Value
cv, err := stickycookie.NewFallbackValue(&stickycookie.RawValue{}, &stickycookie.HashValue{})
if err != nil {
return nil, err
}
options = append(options, roundrobin.EnableStickySession(roundrobin.NewStickySessionWithOptions(cookieName, opts).SetCookieValue(cv)))
logger.Debugf("Sticky session cookie name: %v", cookieName) logger.Debugf("Sticky session cookie name: %v", cookieName)
} }

View file

@ -120,6 +120,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
serviceName string serviceName string
service *dynamic.ServersLoadBalancer service *dynamic.ServersLoadBalancer
responseModifier func(*http.Response) error responseModifier func(*http.Response) error
cookieRawValue string
expected []ExpectedResult expected []ExpectedResult
}{ }{
@ -258,6 +259,34 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
}, },
}, },
}, },
{
desc: "Cookie value is backward compatible",
serviceName: "test",
service: &dynamic.ServersLoadBalancer{
Sticky: &dynamic.Sticky{
Cookie: &dynamic.Cookie{},
},
Servers: []dynamic.Server{
{
URL: server1.URL,
},
{
URL: server2.URL,
},
},
},
cookieRawValue: "_6f743=" + server1.URL,
expected: []ExpectedResult{
{
StatusCode: http.StatusOK,
XFrom: "first",
},
{
StatusCode: http.StatusOK,
XFrom: "first",
},
},
},
} }
for _, test := range testCases { for _, test := range testCases {
@ -269,6 +298,10 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
assert.NotNil(t, handler) assert.NotNil(t, handler)
req := testhelpers.MustNewRequest(http.MethodGet, "http://callme", nil) req := testhelpers.MustNewRequest(http.MethodGet, "http://callme", nil)
if test.cookieRawValue != "" {
req.Header.Set("Cookie", test.cookieRawValue)
}
for _, expected := range test.expected { for _, expected := range test.expected {
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
@ -282,6 +315,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
req.Header.Set("Cookie", cookieHeader) req.Header.Set("Cookie", cookieHeader)
assert.Equal(t, expected.SecureCookie, strings.Contains(cookieHeader, "Secure")) assert.Equal(t, expected.SecureCookie, strings.Contains(cookieHeader, "Secure"))
assert.Equal(t, expected.HTTPOnlyCookie, strings.Contains(cookieHeader, "HttpOnly")) assert.Equal(t, expected.HTTPOnlyCookie, strings.Contains(cookieHeader, "HttpOnly"))
assert.NotContains(t, cookieHeader, "://")
} }
} }
}) })