Merge tag 'v1.4.0-rc5' into master

This commit is contained in:
Fernandez Ludovic 2017-10-10 17:17:44 +02:00
commit 9faae7387e
52 changed files with 930 additions and 416 deletions

57
server/cookie/cookie.go Normal file
View file

@ -0,0 +1,57 @@
package cookie
import (
"crypto/sha1"
"fmt"
"strings"
"github.com/containous/traefik/log"
)
const cookieNameLength = 6
// GetName of a cookie
func GetName(cookieName string, backendName string) string {
if len(cookieName) != 0 {
return sanitizeName(cookieName)
}
return GenerateName(backendName)
}
// GenerateName Generate a hashed name
func GenerateName(backendName string) string {
data := []byte("_TRAEFIK_BACKEND_" + backendName)
hash := sha1.New()
_, err := hash.Write(data)
if err != nil {
// Impossible case
log.Errorf("Fail to create cookie name: %v", err)
}
return fmt.Sprintf("_%x", hash.Sum(nil))[:cookieNameLength]
}
// sanitizeName According to [RFC 2616](https://www.ietf.org/rfc/rfc2616.txt) section 2.2
func sanitizeName(backend string) string {
sanitizer := func(r rune) rune {
switch r {
case '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '`', '|', '~':
return r
}
switch {
case 'a' <= r && r <= 'z':
fallthrough
case 'A' <= r && r <= 'Z':
fallthrough
case '0' <= r && r <= '9':
return r
default:
return '_'
}
}
return strings.Map(sanitizer, backend)
}

View file

@ -0,0 +1,83 @@
package cookie
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetName(t *testing.T) {
testCases := []struct {
desc string
cookieName string
backendName string
expectedCookieName string
}{
{
desc: "with backend name, without cookie name",
cookieName: "",
backendName: "/my/BACKEND-v1.0~rc1",
expectedCookieName: "_5f7bc",
},
{
desc: "without backend name, with cookie name",
cookieName: "/my/BACKEND-v1.0~rc1",
backendName: "",
expectedCookieName: "_my_BACKEND-v1.0~rc1",
},
{
desc: "with backend name, with cookie name",
cookieName: "containous",
backendName: "treafik",
expectedCookieName: "containous",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
cookieName := GetName(test.cookieName, test.backendName)
assert.Equal(t, test.expectedCookieName, cookieName)
})
}
}
func Test_sanitizeName(t *testing.T) {
testCases := []struct {
desc string
srcName string
expectedName string
}{
{
desc: "with /",
srcName: "/my/BACKEND-v1.0~rc1",
expectedName: "_my_BACKEND-v1.0~rc1",
},
{
desc: "some chars",
srcName: "!#$%&'()*+-./:<=>?@[]^_`{|}~",
expectedName: "!#$%&'__*+-._________^_`_|_~",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
cookieName := sanitizeName(test.srcName)
assert.Equal(t, test.expectedName, cookieName, "Cookie name")
})
}
}
func TestGenerateName(t *testing.T) {
cookieName := GenerateName("containous")
assert.Len(t, "_8a7bc", 6)
assert.Equal(t, "_8a7bc", cookieName)
}

View file

@ -6,6 +6,7 @@ import (
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
@ -30,7 +31,9 @@ import (
mauth "github.com/containous/traefik/middlewares/auth"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/server/cookie"
"github.com/containous/traefik/types"
"github.com/containous/traefik/whitelist"
"github.com/streamrail/concurrent-map"
thoas_stats "github.com/thoas/stats"
"github.com/urfave/negroni"
@ -154,8 +157,8 @@ func createHTTPTransport(globalConfiguration configuration.GlobalConfiguration)
transport.TLSClientConfig = &tls.Config{
RootCAs: createRootCACertPool(globalConfiguration.RootCAs),
}
http2.ConfigureTransport(transport)
}
http2.ConfigureTransport(transport)
return transport
}
@ -335,11 +338,11 @@ func (server *Server) listenProviders(stop chan bool) {
lastReceivedConfigurationValue := lastReceivedConfiguration.Get().(time.Time)
providersThrottleDuration := time.Duration(server.globalConfiguration.ProvidersThrottleDuration)
if time.Now().After(lastReceivedConfigurationValue.Add(providersThrottleDuration)) {
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration.String())
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
// last config received more than n s ago
server.configurationValidatedChan <- configMsg
} else {
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration.String())
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
safe.Go(func() {
<-time.After(providersThrottleDuration)
lastReceivedConfigurationValue := lastReceivedConfiguration.Get().(time.Time)
@ -652,8 +655,22 @@ func (server *Server) prepareServer(entryPointName string, entryPoint *configura
return nil, nil, err
}
if entryPoint.ProxyProtocol {
listener = &proxyproto.Listener{Listener: listener}
if entryPoint.ProxyProtocol != nil {
IPs, err := whitelist.NewIP(entryPoint.ProxyProtocol.TrustedIPs)
if err != nil {
return nil, nil, fmt.Errorf("Error creating whitelist: %s", err)
}
log.Infof("Enabling ProxyProtocol for trusted IPs %v", entryPoint.ProxyProtocol.TrustedIPs)
listener = &proxyproto.Listener{
Listener: listener,
SourceCheck: func(addr net.Addr) (bool, error) {
ip, ok := addr.(*net.TCPAddr)
if !ok {
return false, fmt.Errorf("Type error %v", addr)
}
return IPs.ContainsIP(ip.IP)
},
}
}
return &http.Server{
@ -826,11 +843,10 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf
continue frontend
}
stickySession := config.Backends[frontend.Backend].LoadBalancer.Sticky
cookieName := "_TRAEFIK_BACKEND_" + frontend.Backend
var sticky *roundrobin.StickySession
if stickySession {
var cookieName string
if stickiness := config.Backends[frontend.Backend].LoadBalancer.Stickiness; stickiness != nil {
cookieName = cookie.GetName(stickiness.CookieName, frontend.Backend)
sticky = roundrobin.NewStickySession(cookieName)
}
@ -839,7 +855,7 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf
case types.Drr:
log.Debugf("Creating load-balancer drr")
rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger))
if stickySession {
if sticky != nil {
log.Debugf("Sticky session with cookie %v", cookieName)
rebalancer, _ = roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger), roundrobin.RebalancerStickySession(sticky))
}
@ -856,7 +872,7 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf
lb = middlewares.NewEmptyBackendHandler(rebalancer, lb)
case types.Wrr:
log.Debugf("Creating load-balancer wrr")
if stickySession {
if sticky != nil {
log.Debugf("Sticky session with cookie %v", cookieName)
if server.accessLoggerMiddleware != nil {
rr, _ = roundrobin.New(saveFrontend, roundrobin.EnableStickySession(sticky))
@ -1159,16 +1175,31 @@ func (server *Server) configureFrontends(frontends map[string]*types.Frontend) {
func (*Server) configureBackends(backends map[string]*types.Backend) {
for backendName, backend := range backends {
if backend.LoadBalancer != nil && backend.LoadBalancer.Sticky {
log.Warn("Deprecated configuration found: %s. Please use %s.", "backend.LoadBalancer.Sticky", "backend.LoadBalancer.Stickiness")
}
_, err := types.NewLoadBalancerMethod(backend.LoadBalancer)
if err != nil {
if err == nil {
if backend.LoadBalancer != nil && backend.LoadBalancer.Stickiness == nil && backend.LoadBalancer.Sticky {
backend.LoadBalancer.Stickiness = &types.Stickiness{}
}
} else {
log.Debugf("Validation of load balancer method for backend %s failed: %s. Using default method wrr.", backendName, err)
var sticky bool
var stickiness *types.Stickiness
if backend.LoadBalancer != nil {
sticky = backend.LoadBalancer.Sticky
if backend.LoadBalancer.Stickiness != nil {
stickiness = backend.LoadBalancer.Stickiness
} else if backend.LoadBalancer.Sticky {
if backend.LoadBalancer.Stickiness == nil {
stickiness = &types.Stickiness{}
}
}
}
backend.LoadBalancer = &types.LoadBalancer{
Method: "wrr",
Sticky: sticky,
Method: "wrr",
Stickiness: stickiness,
}
}
}

View file

@ -355,7 +355,7 @@ func TestNewServerWithWhitelistSourceRange(t *testing.T) {
"foo",
},
middlewareConfigured: false,
errMessage: "parsing CIDR whitelist <nil>: invalid CIDR address: foo",
errMessage: "parsing CIDR whitelist [foo]: parsing CIDR whitelist <nil>: invalid CIDR address: foo",
},
}
@ -422,52 +422,49 @@ func TestConfigureBackends(t *testing.T) {
defaultMethod := "wrr"
tests := []struct {
desc string
lb *types.LoadBalancer
wantMethod string
wantSticky bool
desc string
lb *types.LoadBalancer
wantMethod string
wantStickiness *types.Stickiness
}{
{
desc: "valid load balancer method with sticky enabled",
lb: &types.LoadBalancer{
Method: validMethod,
Sticky: true,
Method: validMethod,
Stickiness: &types.Stickiness{},
},
wantMethod: validMethod,
wantSticky: true,
wantMethod: validMethod,
wantStickiness: &types.Stickiness{},
},
{
desc: "valid load balancer method with sticky disabled",
lb: &types.LoadBalancer{
Method: validMethod,
Sticky: false,
Method: validMethod,
Stickiness: nil,
},
wantMethod: validMethod,
wantSticky: false,
},
{
desc: "invalid load balancer method with sticky enabled",
lb: &types.LoadBalancer{
Method: "Invalid",
Sticky: true,
Method: "Invalid",
Stickiness: &types.Stickiness{},
},
wantMethod: defaultMethod,
wantSticky: true,
wantMethod: defaultMethod,
wantStickiness: &types.Stickiness{},
},
{
desc: "invalid load balancer method with sticky disabled",
lb: &types.LoadBalancer{
Method: "Invalid",
Sticky: false,
Method: "Invalid",
Stickiness: nil,
},
wantMethod: defaultMethod,
wantSticky: false,
},
{
desc: "missing load balancer",
lb: nil,
wantMethod: defaultMethod,
wantSticky: false,
},
}
@ -485,8 +482,8 @@ func TestConfigureBackends(t *testing.T) {
})
wantLB := types.LoadBalancer{
Method: test.wantMethod,
Sticky: test.wantSticky,
Method: test.wantMethod,
Stickiness: test.wantStickiness,
}
if !reflect.DeepEqual(*backend.LoadBalancer, wantLB) {
t.Errorf("got backend load-balancer\n%v\nwant\n%v\n", spew.Sdump(backend.LoadBalancer), spew.Sdump(wantLB))
@ -539,7 +536,7 @@ func TestServerEntrypointWhitelistConfig(t *testing.T) {
handler := srvEntryPoint.httpServer.Handler.(*negroni.Negroni)
found := false
for _, handler := range handler.Handlers() {
if reflect.TypeOf(handler) == reflect.TypeOf((*middlewares.IPWhitelister)(nil)) {
if reflect.TypeOf(handler) == reflect.TypeOf((*middlewares.IPWhiteLister)(nil)) {
found = true
}
}
@ -719,6 +716,10 @@ func withServer(name, url string) func(backend *types.Backend) {
func withLoadBalancer(method string, sticky bool) func(*types.Backend) {
return func(be *types.Backend) {
be.LoadBalancer = &types.LoadBalancer{Method: method, Sticky: sticky}
if sticky {
be.LoadBalancer = &types.LoadBalancer{Method: method, Stickiness: &types.Stickiness{CookieName: "test"}}
} else {
be.LoadBalancer = &types.LoadBalancer{Method: method}
}
}
}