2017-03-15 19:16:06 +01:00
package server
import (
2018-03-21 22:43:15 +01:00
"context"
2017-05-09 15:31:16 -05:00
"net/http"
2017-07-10 12:11:44 +02:00
"net/http/httptest"
2017-03-15 19:16:06 +01:00
"testing"
"time"
2017-03-24 09:36:33 +01:00
"github.com/containous/flaeg"
2017-05-09 15:31:16 -05:00
"github.com/containous/mux"
2017-08-25 16:10:03 +02:00
"github.com/containous/traefik/configuration"
2017-04-18 08:22:06 +02:00
"github.com/containous/traefik/middlewares"
2018-06-05 12:32:03 +02:00
th "github.com/containous/traefik/testhelpers"
2017-03-15 19:16:06 +01:00
"github.com/containous/traefik/types"
2017-04-30 11:22:07 +02:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2018-03-01 16:42:04 +01:00
"github.com/unrolled/secure"
2017-03-15 19:16:06 +01:00
)
2017-08-18 15:34:04 +02:00
func TestPrepareServerTimeouts ( t * testing . T ) {
2018-04-23 15:30:03 +02:00
testCases := [ ] struct {
desc string
globalConfig configuration . GlobalConfiguration
expectedIdleTimeout time . Duration
expectedReadTimeout time . Duration
expectedWriteTimeout time . Duration
2017-08-18 15:34:04 +02:00
} {
{
desc : "full configuration" ,
2017-08-25 16:10:03 +02:00
globalConfig : configuration . GlobalConfiguration {
RespondingTimeouts : & configuration . RespondingTimeouts {
2017-08-18 15:34:04 +02:00
IdleTimeout : flaeg . Duration ( 10 * time . Second ) ,
ReadTimeout : flaeg . Duration ( 12 * time . Second ) ,
WriteTimeout : flaeg . Duration ( 14 * time . Second ) ,
} ,
} ,
2018-04-23 15:30:03 +02:00
expectedIdleTimeout : time . Duration ( 10 * time . Second ) ,
expectedReadTimeout : time . Duration ( 12 * time . Second ) ,
expectedWriteTimeout : time . Duration ( 14 * time . Second ) ,
2017-08-18 15:34:04 +02:00
} ,
{
2018-04-23 15:30:03 +02:00
desc : "using defaults" ,
globalConfig : configuration . GlobalConfiguration { } ,
expectedIdleTimeout : time . Duration ( 180 * time . Second ) ,
expectedReadTimeout : time . Duration ( 0 * time . Second ) ,
expectedWriteTimeout : time . Duration ( 0 * time . Second ) ,
2017-08-18 15:34:04 +02:00
} ,
{
desc : "deprecated IdleTimeout configured" ,
2017-08-25 16:10:03 +02:00
globalConfig : configuration . GlobalConfiguration {
2017-08-18 15:34:04 +02:00
IdleTimeout : flaeg . Duration ( 45 * time . Second ) ,
} ,
2018-04-23 15:30:03 +02:00
expectedIdleTimeout : time . Duration ( 45 * time . Second ) ,
expectedReadTimeout : time . Duration ( 0 * time . Second ) ,
expectedWriteTimeout : time . Duration ( 0 * time . Second ) ,
2017-08-18 15:34:04 +02:00
} ,
{
desc : "deprecated and new IdleTimeout configured" ,
2017-08-25 16:10:03 +02:00
globalConfig : configuration . GlobalConfiguration {
2017-08-18 15:34:04 +02:00
IdleTimeout : flaeg . Duration ( 45 * time . Second ) ,
2017-08-25 16:10:03 +02:00
RespondingTimeouts : & configuration . RespondingTimeouts {
2017-08-18 15:34:04 +02:00
IdleTimeout : flaeg . Duration ( 80 * time . Second ) ,
} ,
} ,
2018-04-23 15:30:03 +02:00
expectedIdleTimeout : time . Duration ( 45 * time . Second ) ,
expectedReadTimeout : time . Duration ( 0 * time . Second ) ,
expectedWriteTimeout : time . Duration ( 0 * time . Second ) ,
2017-08-18 15:34:04 +02:00
} ,
}
2018-04-23 15:30:03 +02:00
for _ , test := range testCases {
2017-08-18 15:34:04 +02:00
test := test
t . Run ( test . desc , func ( t * testing . T ) {
t . Parallel ( )
entryPointName := "http"
2017-10-16 12:46:03 +02:00
entryPoint := & configuration . EntryPoint {
Address : "localhost:0" ,
ForwardedHeaders : & configuration . ForwardedHeaders { Insecure : true } ,
}
2017-08-18 15:34:04 +02:00
router := middlewares . NewHandlerSwitcher ( mux . NewRouter ( ) )
2018-04-23 15:30:03 +02:00
srv := NewServer ( test . globalConfig , nil , nil )
httpServer , _ , err := srv . prepareServer ( entryPointName , entryPoint , router , nil )
require . NoError ( t , err , "Unexpected error when preparing srv" )
2017-08-18 15:34:04 +02:00
2018-04-23 15:30:03 +02:00
assert . Equal ( t , test . expectedIdleTimeout , httpServer . IdleTimeout , "IdleTimeout" )
assert . Equal ( t , test . expectedReadTimeout , httpServer . ReadTimeout , "ReadTimeout" )
assert . Equal ( t , test . expectedWriteTimeout , httpServer . WriteTimeout , "WriteTimeout" )
2017-08-18 15:34:04 +02:00
} )
}
}
2017-11-17 10:26:03 +01:00
func TestListenProvidersSkipsEmptyConfigs ( t * testing . T ) {
server , stop , invokeStopChan := setupListenProvider ( 10 * time . Millisecond )
defer invokeStopChan ( )
go func ( ) {
for {
select {
case <- stop :
return
case <- server . configurationValidatedChan :
t . Error ( "An empty configuration was published but it should not" )
}
}
} ( )
server . configurationChan <- types . ConfigMessage { ProviderName : "kubernetes" }
// give some time so that the configuration can be processed
time . Sleep ( 100 * time . Millisecond )
}
func TestListenProvidersSkipsSameConfigurationForProvider ( t * testing . T ) {
server , stop , invokeStopChan := setupListenProvider ( 10 * time . Millisecond )
defer invokeStopChan ( )
publishedConfigCount := 0
go func ( ) {
for {
select {
case <- stop :
return
case config := <- server . configurationValidatedChan :
// set the current configuration
// this is usually done in the processing part of the published configuration
// so we have to emulate the behaviour here
currentConfigurations := server . currentConfigurations . Get ( ) . ( types . Configurations )
currentConfigurations [ config . ProviderName ] = config . Configuration
server . currentConfigurations . Set ( currentConfigurations )
publishedConfigCount ++
if publishedConfigCount > 1 {
t . Error ( "Same configuration should not be published multiple times" )
}
}
}
} ( )
2018-06-05 12:32:03 +02:00
config := th . BuildConfiguration (
th . WithFrontends ( th . WithFrontend ( "backend" ) ) ,
th . WithBackends ( th . WithBackendNew ( "backend" ) ) ,
2017-11-17 10:26:03 +01:00
)
// provide a configuration
server . configurationChan <- types . ConfigMessage { ProviderName : "kubernetes" , Configuration : config }
// give some time so that the configuration can be processed
time . Sleep ( 20 * time . Millisecond )
// provide the same configuration a second time
server . configurationChan <- types . ConfigMessage { ProviderName : "kubernetes" , Configuration : config }
// give some time so that the configuration can be processed
time . Sleep ( 100 * time . Millisecond )
}
func TestListenProvidersPublishesConfigForEachProvider ( t * testing . T ) {
server , stop , invokeStopChan := setupListenProvider ( 10 * time . Millisecond )
defer invokeStopChan ( )
publishedProviderConfigCount := map [ string ] int { }
publishedConfigCount := 0
consumePublishedConfigsDone := make ( chan bool )
go func ( ) {
for {
select {
case <- stop :
return
case newConfig := <- server . configurationValidatedChan :
publishedProviderConfigCount [ newConfig . ProviderName ] ++
publishedConfigCount ++
if publishedConfigCount == 2 {
consumePublishedConfigsDone <- true
return
}
}
}
} ( )
2018-06-05 12:32:03 +02:00
config := th . BuildConfiguration (
th . WithFrontends ( th . WithFrontend ( "backend" ) ) ,
th . WithBackends ( th . WithBackendNew ( "backend" ) ) ,
2017-11-17 10:26:03 +01:00
)
server . configurationChan <- types . ConfigMessage { ProviderName : "kubernetes" , Configuration : config }
server . configurationChan <- types . ConfigMessage { ProviderName : "marathon" , Configuration : config }
select {
case <- consumePublishedConfigsDone :
if val := publishedProviderConfigCount [ "kubernetes" ] ; val != 1 {
t . Errorf ( "Got %d configuration publication(s) for provider %q, want 1" , val , "kubernetes" )
}
if val := publishedProviderConfigCount [ "marathon" ] ; val != 1 {
t . Errorf ( "Got %d configuration publication(s) for provider %q, want 1" , val , "marathon" )
}
case <- time . After ( 100 * time . Millisecond ) :
t . Errorf ( "Published configurations were not consumed in time" )
}
}
// setupListenProvider configures the Server and starts listenProviders
func setupListenProvider ( throttleDuration time . Duration ) ( server * Server , stop chan bool , invokeStopChan func ( ) ) {
stop = make ( chan bool )
invokeStopChan = func ( ) {
stop <- true
}
globalConfig := configuration . GlobalConfiguration {
EntryPoints : configuration . EntryPoints {
"http" : & configuration . EntryPoint { } ,
} ,
ProvidersThrottleDuration : flaeg . Duration ( throttleDuration ) ,
}
2018-04-23 15:30:03 +02:00
server = NewServer ( globalConfig , nil , nil )
2017-11-17 10:26:03 +01:00
go server . listenProviders ( stop )
return server , stop , invokeStopChan
}
2017-07-10 12:11:44 +02:00
func TestServerResponseEmptyBackend ( t * testing . T ) {
const requestPath = "/path"
const routeRule = "Path:" + requestPath
testCases := [ ] struct {
2018-04-23 15:30:03 +02:00
desc string
2018-06-05 22:55:19 +02:00
config func ( testServerURL string ) * types . Configuration
2018-04-23 15:30:03 +02:00
expectedStatusCode int
2017-07-10 12:11:44 +02:00
} {
{
desc : "Ok" ,
2018-06-05 12:32:03 +02:00
config : func ( testServerURL string ) * types . Configuration {
return th . BuildConfiguration (
th . WithFrontends ( th . WithFrontend ( "backend" ,
th . WithEntryPoints ( "http" ) ,
th . WithRoutes ( th . WithRoute ( requestPath , routeRule ) ) ) ,
) ,
th . WithBackends ( th . WithBackendNew ( "backend" ,
th . WithLBMethod ( "wrr" ) ,
th . WithServersNew ( th . WithServerNew ( testServerURL ) ) ) ,
) ,
2017-07-10 12:11:44 +02:00
)
} ,
2018-04-23 15:30:03 +02:00
expectedStatusCode : http . StatusOK ,
2017-07-10 12:11:44 +02:00
} ,
{
desc : "No Frontend" ,
2018-06-05 12:32:03 +02:00
config : func ( testServerURL string ) * types . Configuration {
return th . BuildConfiguration ( )
2017-07-10 12:11:44 +02:00
} ,
2018-04-23 15:30:03 +02:00
expectedStatusCode : http . StatusNotFound ,
2017-07-10 12:11:44 +02:00
} ,
{
desc : "Empty Backend LB-Drr" ,
2018-06-05 12:32:03 +02:00
config : func ( testServerURL string ) * types . Configuration {
return th . BuildConfiguration (
th . WithFrontends ( th . WithFrontend ( "backend" ,
th . WithEntryPoints ( "http" ) ,
th . WithRoutes ( th . WithRoute ( requestPath , routeRule ) ) ) ,
) ,
th . WithBackends ( th . WithBackendNew ( "backend" ,
th . WithLBMethod ( "drr" ) ) ,
) ,
2017-07-10 12:11:44 +02:00
)
} ,
2018-04-23 15:30:03 +02:00
expectedStatusCode : http . StatusServiceUnavailable ,
2017-07-10 12:11:44 +02:00
} ,
{
desc : "Empty Backend LB-Drr Sticky" ,
2018-06-05 12:32:03 +02:00
config : func ( testServerURL string ) * types . Configuration {
return th . BuildConfiguration (
th . WithFrontends ( th . WithFrontend ( "backend" ,
th . WithEntryPoints ( "http" ) ,
th . WithRoutes ( th . WithRoute ( requestPath , routeRule ) ) ) ,
) ,
th . WithBackends ( th . WithBackendNew ( "backend" ,
th . WithLBMethod ( "drr" ) , th . WithLBSticky ( "test" ) ) ,
) ,
2017-07-10 12:11:44 +02:00
)
} ,
2018-04-23 15:30:03 +02:00
expectedStatusCode : http . StatusServiceUnavailable ,
2017-07-10 12:11:44 +02:00
} ,
{
desc : "Empty Backend LB-Wrr" ,
2018-06-05 12:32:03 +02:00
config : func ( testServerURL string ) * types . Configuration {
return th . BuildConfiguration (
th . WithFrontends ( th . WithFrontend ( "backend" ,
th . WithEntryPoints ( "http" ) ,
th . WithRoutes ( th . WithRoute ( requestPath , routeRule ) ) ) ,
) ,
th . WithBackends ( th . WithBackendNew ( "backend" ,
th . WithLBMethod ( "wrr" ) ) ,
) ,
2017-07-10 12:11:44 +02:00
)
} ,
2018-04-23 15:30:03 +02:00
expectedStatusCode : http . StatusServiceUnavailable ,
2017-07-10 12:11:44 +02:00
} ,
{
desc : "Empty Backend LB-Wrr Sticky" ,
2018-06-05 12:32:03 +02:00
config : func ( testServerURL string ) * types . Configuration {
return th . BuildConfiguration (
th . WithFrontends ( th . WithFrontend ( "backend" ,
th . WithEntryPoints ( "http" ) ,
th . WithRoutes ( th . WithRoute ( requestPath , routeRule ) ) ) ,
) ,
th . WithBackends ( th . WithBackendNew ( "backend" ,
th . WithLBMethod ( "wrr" ) , th . WithLBSticky ( "test" ) ) ,
) ,
2017-07-10 12:11:44 +02:00
)
} ,
2018-04-23 15:30:03 +02:00
expectedStatusCode : http . StatusServiceUnavailable ,
2017-07-10 12:11:44 +02:00
} ,
}
for _ , test := range testCases {
test := test
t . Run ( test . desc , func ( t * testing . T ) {
t . Parallel ( )
testServer := httptest . NewServer ( http . HandlerFunc ( func ( rw http . ResponseWriter , req * http . Request ) {
rw . WriteHeader ( http . StatusOK )
} ) )
defer testServer . Close ( )
2018-04-23 15:30:03 +02:00
globalConfig := configuration . GlobalConfiguration { }
entryPointsConfig := map [ string ] EntryPoint {
"http" : { Configuration : & configuration . EntryPoint { ForwardedHeaders : & configuration . ForwardedHeaders { Insecure : true } } } ,
2017-07-10 12:11:44 +02:00
}
2018-06-05 12:32:03 +02:00
dynamicConfigs := types . Configurations { "config" : test . config ( testServer . URL ) }
2017-07-10 12:11:44 +02:00
2018-04-23 15:30:03 +02:00
srv := NewServer ( globalConfig , nil , entryPointsConfig )
2018-10-29 16:48:06 +01:00
entryPoints := srv . loadConfig ( dynamicConfigs , globalConfig )
2017-07-10 12:11:44 +02:00
responseRecorder := & httptest . ResponseRecorder { }
request := httptest . NewRequest ( http . MethodGet , testServer . URL + requestPath , nil )
entryPoints [ "http" ] . httpRouter . ServeHTTP ( responseRecorder , request )
2018-04-23 15:30:03 +02:00
assert . Equal ( t , test . expectedStatusCode , responseRecorder . Result ( ) . StatusCode , "status code" )
2017-07-10 12:11:44 +02:00
} )
}
}
2018-03-01 16:42:04 +01:00
type mockContext struct {
headers http . Header
}
func ( c mockContext ) Deadline ( ) ( deadline time . Time , ok bool ) {
return deadline , ok
}
func ( c mockContext ) Done ( ) <- chan struct { } {
ch := make ( chan struct { } )
close ( ch )
return ch
}
func ( c mockContext ) Err ( ) error {
return context . DeadlineExceeded
}
func ( c mockContext ) Value ( key interface { } ) interface { } {
return c . headers
}
func TestNewServerWithResponseModifiers ( t * testing . T ) {
2018-04-23 15:30:03 +02:00
testCases := [ ] struct {
2018-03-01 16:42:04 +01:00
desc string
headerMiddleware * middlewares . HeaderStruct
secureMiddleware * secure . Secure
ctx context . Context
expected map [ string ] string
} {
{
desc : "header and secure nil" ,
headerMiddleware : nil ,
secureMiddleware : nil ,
ctx : mockContext { } ,
expected : map [ string ] string {
"X-Default" : "powpow" ,
"Referrer-Policy" : "same-origin" ,
} ,
} ,
{
desc : "header middleware not nil" ,
headerMiddleware : middlewares . NewHeaderFromStruct ( & types . Headers {
CustomResponseHeaders : map [ string ] string {
"X-Default" : "powpow" ,
} ,
} ) ,
secureMiddleware : nil ,
ctx : mockContext { } ,
expected : map [ string ] string {
"X-Default" : "powpow" ,
"Referrer-Policy" : "same-origin" ,
} ,
} ,
{
desc : "secure middleware not nil" ,
headerMiddleware : nil ,
secureMiddleware : middlewares . NewSecure ( & types . Headers {
ReferrerPolicy : "no-referrer" ,
} ) ,
ctx : mockContext {
headers : http . Header { "Referrer-Policy" : [ ] string { "no-referrer" } } ,
} ,
expected : map [ string ] string {
"X-Default" : "powpow" ,
"Referrer-Policy" : "no-referrer" ,
} ,
} ,
{
desc : "header and secure middleware not nil" ,
headerMiddleware : middlewares . NewHeaderFromStruct ( & types . Headers {
CustomResponseHeaders : map [ string ] string {
"Referrer-Policy" : "powpow" ,
} ,
} ) ,
secureMiddleware : middlewares . NewSecure ( & types . Headers {
ReferrerPolicy : "no-referrer" ,
} ) ,
ctx : mockContext {
headers : http . Header { "Referrer-Policy" : [ ] string { "no-referrer" } } ,
} ,
expected : map [ string ] string {
"X-Default" : "powpow" ,
"Referrer-Policy" : "powpow" ,
} ,
} ,
}
2018-04-23 15:30:03 +02:00
for _ , test := range testCases {
2018-03-01 16:42:04 +01:00
test := test
t . Run ( test . desc , func ( t * testing . T ) {
t . Parallel ( )
headers := make ( http . Header )
headers . Add ( "X-Default" , "powpow" )
headers . Add ( "Referrer-Policy" , "same-origin" )
req := httptest . NewRequest ( http . MethodGet , "http://127.0.0.1" , nil )
res := & http . Response {
Request : req . WithContext ( test . ctx ) ,
Header : headers ,
}
responseModifier := buildModifyResponse ( test . secureMiddleware , test . headerMiddleware )
err := responseModifier ( res )
assert . NoError ( t , err )
assert . Equal ( t , len ( test . expected ) , len ( res . Header ) )
for k , v := range test . expected {
assert . Equal ( t , v , res . Header . Get ( k ) )
}
} )
}
}