Merge pull request #1613 from containous/merge-v1.3.0-rc2-master
Merge v1.3.0-rc2 master
This commit is contained in:
commit
ff3481f06b
21 changed files with 465 additions and 81 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -1,5 +1,19 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## [v1.3.0-rc2](https://github.com/containous/traefik/tree/v1.3.0-rc2) (2017-05-16)
|
||||||
|
[Full Changelog](https://github.com/containous/traefik/compare/v1.3.0-rc1...v1.3.0-rc2)
|
||||||
|
|
||||||
|
**Merged pull requests:**
|
||||||
|
|
||||||
|
- doc: Traefik cluster in beta. [\#1610](https://github.com/containous/traefik/pull/1610) ([ldez](https://github.com/ldez))
|
||||||
|
- SemaphoreCI on 1.3 branch [\#1608](https://github.com/containous/traefik/pull/1608) ([ldez](https://github.com/ldez))
|
||||||
|
- Fix empty basic auth [\#1601](https://github.com/containous/traefik/pull/1601) ([emilevauge](https://github.com/emilevauge))
|
||||||
|
- Fix stats hijack [\#1598](https://github.com/containous/traefik/pull/1598) ([emilevauge](https://github.com/emilevauge))
|
||||||
|
- Fix exported fields providers [\#1588](https://github.com/containous/traefik/pull/1588) ([emilevauge](https://github.com/emilevauge))
|
||||||
|
- Maintain sticky flag on LB method validation failure. [\#1585](https://github.com/containous/traefik/pull/1585) ([timoreimann](https://github.com/timoreimann))
|
||||||
|
- \[Kubernetes\] Ignore missing pass host header annotation. [\#1581](https://github.com/containous/traefik/pull/1581) ([timoreimann](https://github.com/timoreimann))
|
||||||
|
- Fixed ReplacePath rule executing out of order, when combined with PathPrefixStrip [\#1577](https://github.com/containous/traefik/pull/1577) ([aantono](https://github.com/aantono))
|
||||||
|
|
||||||
## [v1.3.0-rc1](https://github.com/containous/traefik/tree/v1.3.0-rc1) (2017-05-04)
|
## [v1.3.0-rc1](https://github.com/containous/traefik/tree/v1.3.0-rc1) (2017-05-04)
|
||||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.2.3...v1.3.0-rc1)
|
[Full Changelog](https://github.com/containous/traefik/compare/v1.2.3...v1.3.0-rc1)
|
||||||
|
|
||||||
|
|
|
@ -191,6 +191,25 @@ backend = "backend2"
|
||||||
rule = "Path:/test1,/test2"
|
rule = "Path:/test1,/test2"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Rules Order
|
||||||
|
|
||||||
|
When combining `Modifier` rules with `Matcher` rules, it is important to remember that `Modifier` rules **ALWAYS** apply after the `Matcher` rules.
|
||||||
|
The following rules are both `Matchers` and `Modifiers`, so the `Matcher` portion of the rule will apply first, and the `Modifier` will apply later.
|
||||||
|
|
||||||
|
- `PathStrip`
|
||||||
|
- `PathStripRegex`
|
||||||
|
- `PathPrefixStrip`
|
||||||
|
- `PathPrefixStripRegex`
|
||||||
|
|
||||||
|
`Modifiers` will be applied in a pre-determined order regardless of their order in the `rule` configuration section.
|
||||||
|
|
||||||
|
1. `PathStrip`
|
||||||
|
2. `PathPrefixStrip`
|
||||||
|
3. `PathStripRegex`
|
||||||
|
4. `PathPrefixStripRegex`
|
||||||
|
5. `AddPrefix`
|
||||||
|
6. `ReplacePath`
|
||||||
|
|
||||||
### Priorities
|
### Priorities
|
||||||
|
|
||||||
By default, routes will be sorted (in descending order) using rules length (to avoid path overlap):
|
By default, routes will be sorted (in descending order) using rules length (to avoid path overlap):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Clustering / High Availability
|
# Clustering / High Availability (beta)
|
||||||
|
|
||||||
This guide explains how tu use Træfik in high availability mode.
|
This guide explains how tu use Træfik in high availability mode.
|
||||||
In order to deploy and configure multiple Træfik instances, without copying the same configuration file on each instance, we will use a distributed Key-Value store.
|
In order to deploy and configure multiple Træfik instances, without copying the same configuration file on each instance, we will use a distributed Key-Value store.
|
||||||
|
@ -15,5 +15,6 @@ Please refer to [this section](/user-guide/kv-config/#store-configuration-in-key
|
||||||
## Deploy a Træfik cluster
|
## Deploy a Træfik cluster
|
||||||
|
|
||||||
Once your Træfik configuration is uploaded on your KV store, you can start each Træfik instance.
|
Once your Træfik configuration is uploaded on your KV store, you can start each Træfik instance.
|
||||||
A Træfik cluster is based on a master/slave model. When starting, Træfik will elect a master. If this instance fails, another master will be automatically elected.
|
A Træfik cluster is based on a master/slave model.
|
||||||
|
When starting, Træfik will elect a master. If this instance fails, another master will be automatically elected.
|
||||||
|
|
33
middlewares/recover.go
Normal file
33
middlewares/recover.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/codegangsta/negroni"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RecoverHandler recovers from a panic in http handlers
|
||||||
|
func RecoverHandler(next http.Handler) http.Handler {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer recoverFunc(w)
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NegroniRecoverHandler recovers from a panic in negroni handlers
|
||||||
|
func NegroniRecoverHandler() negroni.Handler {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
defer recoverFunc(w)
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
return negroni.HandlerFunc(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func recoverFunc(w http.ResponseWriter) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Errorf("Recovered from panic in http handler: %+v", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
45
middlewares/recover_test.go
Normal file
45
middlewares/recover_test.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/codegangsta/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRecoverHandler(t *testing.T) {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
panic("I love panicing!")
|
||||||
|
}
|
||||||
|
recoverHandler := RecoverHandler(http.HandlerFunc(fn))
|
||||||
|
server := httptest.NewServer(recoverHandler)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resp, err := http.Get(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusInternalServerError {
|
||||||
|
t.Fatalf("Received non-%d response: %d\n", http.StatusInternalServerError, resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNegroniRecoverHandler(t *testing.T) {
|
||||||
|
n := negroni.New()
|
||||||
|
n.Use(NegroniRecoverHandler())
|
||||||
|
panicHandler := func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
panic("I love panicing!")
|
||||||
|
}
|
||||||
|
n.UseFunc(negroni.HandlerFunc(panicHandler))
|
||||||
|
server := httptest.NewServer(n)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
resp, err := http.Get(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusInternalServerError {
|
||||||
|
t.Fatalf("Received non-%d response: %d\n", http.StatusInternalServerError, resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,10 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ http.ResponseWriter = &ResponseRecorder{}
|
_ Stateful = &ResponseRecorder{}
|
||||||
_ http.Hijacker = &ResponseRecorder{}
|
|
||||||
_ http.Flusher = &ResponseRecorder{}
|
|
||||||
_ http.CloseNotifier = &ResponseRecorder{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Retry is a middleware that retries requests
|
// Retry is a middleware that retries requests
|
||||||
|
|
12
middlewares/stateful.go
Normal file
12
middlewares/stateful.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Stateful interface groups all http interfaces that must be
|
||||||
|
// implemented by a stateful middleware (ie: recorders)
|
||||||
|
type Stateful interface {
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}
|
|
@ -1,11 +1,17 @@
|
||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Stateful = &responseRecorder{}
|
||||||
|
)
|
||||||
|
|
||||||
// StatsRecorder is an optional middleware that records more details statistics
|
// StatsRecorder is an optional middleware that records more details statistics
|
||||||
// about requests and how they are processed. This currently consists of recent
|
// about requests and how they are processed. This currently consists of recent
|
||||||
// requests that have caused errors (4xx and 5xx status codes), making it easy
|
// requests that have caused errors (4xx and 5xx status codes), making it easy
|
||||||
|
@ -51,6 +57,23 @@ func (r *responseRecorder) WriteHeader(status int) {
|
||||||
r.statusCode = status
|
r.statusCode = status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hijack hijacks the connection
|
||||||
|
func (r *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
return r.ResponseWriter.(http.Hijacker).Hijack()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseNotify returns a channel that receives at most a
|
||||||
|
// single value (true) when the client connection has gone
|
||||||
|
// away.
|
||||||
|
func (r *responseRecorder) CloseNotify() <-chan bool {
|
||||||
|
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush sends any buffered data to the client.
|
||||||
|
func (r *responseRecorder) Flush() {
|
||||||
|
r.ResponseWriter.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// ServeHTTP silently extracts information from the request and response as it
|
// ServeHTTP silently extracts information from the request and response as it
|
||||||
// is processed. If the response is 4xx or 5xx, add it to the list of 10 most
|
// is processed. If the response is 4xx or 5xx, add it to the list of 10 most
|
||||||
// recent errors.
|
// recent errors.
|
||||||
|
|
|
@ -25,13 +25,13 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||||
}
|
}
|
||||||
p.Kvclient = store
|
p.SetKVClient(store)
|
||||||
return p.Provider.Provide(configurationChan, pool, constraints)
|
return p.Provider.Provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateStore creates the KV store
|
// CreateStore creates the KV store
|
||||||
func (p *Provider) CreateStore() (store.Store, error) {
|
func (p *Provider) CreateStore() (store.Store, error) {
|
||||||
p.StoreType = store.BOLTDB
|
p.SetStoreType(store.BOLTDB)
|
||||||
boltdb.Register()
|
boltdb.Register()
|
||||||
return p.Provider.CreateStore()
|
return p.Provider.CreateStore()
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,13 +25,13 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||||
}
|
}
|
||||||
p.Kvclient = store
|
p.SetKVClient(store)
|
||||||
return p.Provider.Provide(configurationChan, pool, constraints)
|
return p.Provider.Provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateStore creates the KV store
|
// CreateStore creates the KV store
|
||||||
func (p *Provider) CreateStore() (store.Store, error) {
|
func (p *Provider) CreateStore() (store.Store, error) {
|
||||||
p.StoreType = store.CONSUL
|
p.SetStoreType(store.CONSUL)
|
||||||
consul.Register()
|
consul.Register()
|
||||||
return p.Provider.CreateStore()
|
return p.Provider.CreateStore()
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,13 +25,13 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||||
}
|
}
|
||||||
p.Kvclient = store
|
p.SetKVClient(store)
|
||||||
return p.Provider.Provide(configurationChan, pool, constraints)
|
return p.Provider.Provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateStore creates the KV store
|
// CreateStore creates the KV store
|
||||||
func (p *Provider) CreateStore() (store.Store, error) {
|
func (p *Provider) CreateStore() (store.Store, error) {
|
||||||
p.StoreType = store.ETCD
|
p.SetStoreType(store.ETCD)
|
||||||
etcd.Register()
|
etcd.Register()
|
||||||
return p.Provider.CreateStore()
|
return p.Provider.CreateStore()
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,8 @@ import (
|
||||||
// Provider holds configuration of the Provider provider.
|
// Provider holds configuration of the Provider provider.
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
provider.BaseProvider `mapstructure:",squash"`
|
provider.BaseProvider `mapstructure:",squash"`
|
||||||
Endpoint string
|
Endpoint string `description:"Eureka server endpoint"`
|
||||||
Delay string
|
Delay string `description:"Override default configuration time between refresh"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide allows the eureka provider to provide configurations to traefik
|
// Provide allows the eureka provider to provide configurations to traefik
|
||||||
|
|
|
@ -26,8 +26,8 @@ type Provider struct {
|
||||||
TLS *provider.ClientTLS `description:"Enable TLS support"`
|
TLS *provider.ClientTLS `description:"Enable TLS support"`
|
||||||
Username string `description:"KV Username"`
|
Username string `description:"KV Username"`
|
||||||
Password string `description:"KV Password"`
|
Password string `description:"KV Password"`
|
||||||
StoreType store.Backend
|
storeType store.Backend
|
||||||
Kvclient store.Store
|
kvclient store.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateStore create the K/V store
|
// CreateStore create the K/V store
|
||||||
|
@ -47,15 +47,25 @@ func (p *Provider) CreateStore() (store.Store, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return libkv.NewStore(
|
return libkv.NewStore(
|
||||||
p.StoreType,
|
p.storeType,
|
||||||
strings.Split(p.Endpoint, ","),
|
strings.Split(p.Endpoint, ","),
|
||||||
storeConfig,
|
storeConfig,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetStoreType storeType setter
|
||||||
|
func (p *Provider) SetStoreType(storeType store.Backend) {
|
||||||
|
p.storeType = storeType
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetKVClient kvclient setter
|
||||||
|
func (p *Provider) SetKVClient(kvClient store.Store) {
|
||||||
|
p.kvclient = kvClient
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error {
|
func (p *Provider) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error {
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
events, err := p.Kvclient.WatchTree(p.Prefix, make(chan struct{}))
|
events, err := p.kvclient.WatchTree(p.Prefix, make(chan struct{}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to KV WatchTree: %v", err)
|
return fmt.Errorf("Failed to KV WatchTree: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -70,7 +80,7 @@ func (p *Provider) watchKv(configurationChan chan<- types.ConfigMessage, prefix
|
||||||
configuration := p.loadConfig()
|
configuration := p.loadConfig()
|
||||||
if configuration != nil {
|
if configuration != nil {
|
||||||
configurationChan <- types.ConfigMessage{
|
configurationChan <- types.ConfigMessage{
|
||||||
ProviderName: string(p.StoreType),
|
ProviderName: string(p.storeType),
|
||||||
Configuration: configuration,
|
Configuration: configuration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +102,7 @@ func (p *Provider) watchKv(configurationChan chan<- types.ConfigMessage, prefix
|
||||||
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||||
p.Constraints = append(p.Constraints, constraints...)
|
p.Constraints = append(p.Constraints, constraints...)
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
if _, err := p.Kvclient.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil {
|
if _, err := p.kvclient.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil {
|
||||||
return fmt.Errorf("Failed to test KV store connection: %v", err)
|
return fmt.Errorf("Failed to test KV store connection: %v", err)
|
||||||
}
|
}
|
||||||
if p.Watch {
|
if p.Watch {
|
||||||
|
@ -105,7 +115,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
}
|
}
|
||||||
configuration := p.loadConfig()
|
configuration := p.loadConfig()
|
||||||
configurationChan <- types.ConfigMessage{
|
configurationChan <- types.ConfigMessage{
|
||||||
ProviderName: string(p.StoreType),
|
ProviderName: string(p.storeType),
|
||||||
Configuration: configuration,
|
Configuration: configuration,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -152,7 +162,7 @@ func (p *Provider) loadConfig() *types.Configuration {
|
||||||
|
|
||||||
func (p *Provider) list(keys ...string) []string {
|
func (p *Provider) list(keys ...string) []string {
|
||||||
joinedKeys := strings.Join(keys, "")
|
joinedKeys := strings.Join(keys, "")
|
||||||
keysPairs, err := p.Kvclient.List(joinedKeys)
|
keysPairs, err := p.kvclient.List(joinedKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Cannot get keys %s %s ", joinedKeys, err)
|
log.Debugf("Cannot get keys %s %s ", joinedKeys, err)
|
||||||
return nil
|
return nil
|
||||||
|
@ -169,7 +179,7 @@ func (p *Provider) listServers(backend string) []string {
|
||||||
serverNames := p.list(backend, "/servers/")
|
serverNames := p.list(backend, "/servers/")
|
||||||
return fun.Filter(func(serverName string) bool {
|
return fun.Filter(func(serverName string) bool {
|
||||||
key := fmt.Sprint(serverName, "/url")
|
key := fmt.Sprint(serverName, "/url")
|
||||||
if _, err := p.Kvclient.Get(key); err != nil {
|
if _, err := p.kvclient.Get(key); err != nil {
|
||||||
if err != store.ErrKeyNotFound {
|
if err != store.ErrKeyNotFound {
|
||||||
log.Errorf("Failed to retrieve value for key %s: %s", key, err)
|
log.Errorf("Failed to retrieve value for key %s: %s", key, err)
|
||||||
}
|
}
|
||||||
|
@ -181,7 +191,7 @@ func (p *Provider) listServers(backend string) []string {
|
||||||
|
|
||||||
func (p *Provider) get(defaultValue string, keys ...string) string {
|
func (p *Provider) get(defaultValue string, keys ...string) string {
|
||||||
joinedKeys := strings.Join(keys, "")
|
joinedKeys := strings.Join(keys, "")
|
||||||
keyPair, err := p.Kvclient.Get(strings.TrimPrefix(joinedKeys, "/"))
|
keyPair, err := p.kvclient.Get(strings.TrimPrefix(joinedKeys, "/"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Cannot get key %s %s, setting default %s", joinedKeys, err, defaultValue)
|
log.Debugf("Cannot get key %s %s, setting default %s", joinedKeys, err, defaultValue)
|
||||||
return defaultValue
|
return defaultValue
|
||||||
|
@ -194,7 +204,7 @@ func (p *Provider) get(defaultValue string, keys ...string) string {
|
||||||
|
|
||||||
func (p *Provider) splitGet(keys ...string) []string {
|
func (p *Provider) splitGet(keys ...string) []string {
|
||||||
joinedKeys := strings.Join(keys, "")
|
joinedKeys := strings.Join(keys, "")
|
||||||
keyPair, err := p.Kvclient.Get(joinedKeys)
|
keyPair, err := p.kvclient.Get(joinedKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Cannot get key %s %s, setting default empty", joinedKeys, err)
|
log.Debugf("Cannot get key %s %s, setting default empty", joinedKeys, err)
|
||||||
return []string{}
|
return []string{}
|
||||||
|
@ -212,7 +222,7 @@ func (p *Provider) last(key string) string {
|
||||||
|
|
||||||
func (p *Provider) checkConstraints(keys ...string) bool {
|
func (p *Provider) checkConstraints(keys ...string) bool {
|
||||||
joinedKeys := strings.Join(keys, "")
|
joinedKeys := strings.Join(keys, "")
|
||||||
keyPair, err := p.Kvclient.Get(joinedKeys)
|
keyPair, err := p.kvclient.Get(joinedKeys)
|
||||||
|
|
||||||
value := ""
|
value := ""
|
||||||
if err == nil && keyPair != nil && keyPair.Value != nil {
|
if err == nil && keyPair != nil && keyPair.Value != nil {
|
||||||
|
|
|
@ -20,21 +20,21 @@ func TestKvList(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
provider: &Provider{
|
provider: &Provider{
|
||||||
Kvclient: &Mock{},
|
kvclient: &Mock{},
|
||||||
},
|
},
|
||||||
keys: []string{},
|
keys: []string{},
|
||||||
expected: []string{},
|
expected: []string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: &Provider{
|
provider: &Provider{
|
||||||
Kvclient: &Mock{},
|
kvclient: &Mock{},
|
||||||
},
|
},
|
||||||
keys: []string{"traefik"},
|
keys: []string{"traefik"},
|
||||||
expected: []string{},
|
expected: []string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: &Provider{
|
provider: &Provider{
|
||||||
Kvclient: &Mock{
|
kvclient: &Mock{
|
||||||
KVPairs: []*store.KVPair{
|
KVPairs: []*store.KVPair{
|
||||||
{
|
{
|
||||||
Key: "foo",
|
Key: "foo",
|
||||||
|
@ -48,7 +48,7 @@ func TestKvList(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: &Provider{
|
provider: &Provider{
|
||||||
Kvclient: &Mock{
|
kvclient: &Mock{
|
||||||
KVPairs: []*store.KVPair{
|
KVPairs: []*store.KVPair{
|
||||||
{
|
{
|
||||||
Key: "foo",
|
Key: "foo",
|
||||||
|
@ -62,7 +62,7 @@ func TestKvList(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: &Provider{
|
provider: &Provider{
|
||||||
Kvclient: &Mock{
|
kvclient: &Mock{
|
||||||
KVPairs: []*store.KVPair{
|
KVPairs: []*store.KVPair{
|
||||||
{
|
{
|
||||||
Key: "foo/baz/1",
|
Key: "foo/baz/1",
|
||||||
|
@ -95,7 +95,7 @@ func TestKvList(t *testing.T) {
|
||||||
|
|
||||||
// Error case
|
// Error case
|
||||||
provider := &Provider{
|
provider := &Provider{
|
||||||
Kvclient: &Mock{
|
kvclient: &Mock{
|
||||||
Error: KvError{
|
Error: KvError{
|
||||||
List: store.ErrKeyNotFound,
|
List: store.ErrKeyNotFound,
|
||||||
},
|
},
|
||||||
|
@ -115,21 +115,21 @@ func TestKvGet(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
provider: &Provider{
|
provider: &Provider{
|
||||||
Kvclient: &Mock{},
|
kvclient: &Mock{},
|
||||||
},
|
},
|
||||||
keys: []string{},
|
keys: []string{},
|
||||||
expected: "",
|
expected: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: &Provider{
|
provider: &Provider{
|
||||||
Kvclient: &Mock{},
|
kvclient: &Mock{},
|
||||||
},
|
},
|
||||||
keys: []string{"traefik"},
|
keys: []string{"traefik"},
|
||||||
expected: "",
|
expected: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: &Provider{
|
provider: &Provider{
|
||||||
Kvclient: &Mock{
|
kvclient: &Mock{
|
||||||
KVPairs: []*store.KVPair{
|
KVPairs: []*store.KVPair{
|
||||||
{
|
{
|
||||||
Key: "foo",
|
Key: "foo",
|
||||||
|
@ -143,7 +143,7 @@ func TestKvGet(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: &Provider{
|
provider: &Provider{
|
||||||
Kvclient: &Mock{
|
kvclient: &Mock{
|
||||||
KVPairs: []*store.KVPair{
|
KVPairs: []*store.KVPair{
|
||||||
{
|
{
|
||||||
Key: "foo",
|
Key: "foo",
|
||||||
|
@ -157,7 +157,7 @@ func TestKvGet(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provider: &Provider{
|
provider: &Provider{
|
||||||
Kvclient: &Mock{
|
kvclient: &Mock{
|
||||||
KVPairs: []*store.KVPair{
|
KVPairs: []*store.KVPair{
|
||||||
{
|
{
|
||||||
Key: "foo/baz/1",
|
Key: "foo/baz/1",
|
||||||
|
@ -188,7 +188,7 @@ func TestKvGet(t *testing.T) {
|
||||||
|
|
||||||
// Error case
|
// Error case
|
||||||
provider := &Provider{
|
provider := &Provider{
|
||||||
Kvclient: &Mock{
|
kvclient: &Mock{
|
||||||
Error: KvError{
|
Error: KvError{
|
||||||
Get: store.ErrKeyNotFound,
|
Get: store.ErrKeyNotFound,
|
||||||
},
|
},
|
||||||
|
@ -249,7 +249,7 @@ func TestKvWatchTree(t *testing.T) {
|
||||||
returnedChans := make(chan chan []*store.KVPair)
|
returnedChans := make(chan chan []*store.KVPair)
|
||||||
provider := &KvMock{
|
provider := &KvMock{
|
||||||
Provider{
|
Provider{
|
||||||
Kvclient: &Mock{
|
kvclient: &Mock{
|
||||||
WatchTreeMethod: func() <-chan []*store.KVPair {
|
WatchTreeMethod: func() <-chan []*store.KVPair {
|
||||||
c := make(chan []*store.KVPair, 10)
|
c := make(chan []*store.KVPair, 10)
|
||||||
returnedChans <- c
|
returnedChans <- c
|
||||||
|
@ -378,7 +378,7 @@ func (s *Mock) Close() {
|
||||||
func TestKVLoadConfig(t *testing.T) {
|
func TestKVLoadConfig(t *testing.T) {
|
||||||
provider := &Provider{
|
provider := &Provider{
|
||||||
Prefix: "traefik",
|
Prefix: "traefik",
|
||||||
Kvclient: &Mock{
|
kvclient: &Mock{
|
||||||
KVPairs: []*store.KVPair{
|
KVPairs: []*store.KVPair{
|
||||||
{
|
{
|
||||||
Key: "traefik/frontends/frontend.with.dot",
|
Key: "traefik/frontends/frontend.with.dot",
|
||||||
|
|
|
@ -45,14 +45,14 @@ type Provider struct {
|
||||||
DialerTimeout flaeg.Duration `description:"Set a non-default connection timeout for Marathon"`
|
DialerTimeout flaeg.Duration `description:"Set a non-default connection timeout for Marathon"`
|
||||||
KeepAlive flaeg.Duration `description:"Set a non-default TCP Keep Alive time in seconds"`
|
KeepAlive flaeg.Duration `description:"Set a non-default TCP Keep Alive time in seconds"`
|
||||||
ForceTaskHostname bool `description:"Force to use the task's hostname."`
|
ForceTaskHostname bool `description:"Force to use the task's hostname."`
|
||||||
Basic *Basic
|
Basic *Basic `description:"Enable basic authentication"`
|
||||||
marathonClient marathon.Marathon
|
marathonClient marathon.Marathon
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic holds basic authentication specific configurations
|
// Basic holds basic authentication specific configurations
|
||||||
type Basic struct {
|
type Basic struct {
|
||||||
HTTPBasicAuthUser string
|
HTTPBasicAuthUser string `description:"Basic authentication User"`
|
||||||
HTTPBasicPassword string
|
HTTPBasicPassword string `description:"Basic authentication Password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type lightMarathonClient interface {
|
type lightMarathonClient interface {
|
||||||
|
|
|
@ -25,13 +25,13 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||||
}
|
}
|
||||||
p.Kvclient = store
|
p.SetKVClient(store)
|
||||||
return p.Provider.Provide(configurationChan, pool, constraints)
|
return p.Provider.Provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateStore creates the KV store
|
// CreateStore creates the KV store
|
||||||
func (p *Provider) CreateStore() (store.Store, error) {
|
func (p *Provider) CreateStore() (store.Store, error) {
|
||||||
p.StoreType = store.ZK
|
p.SetStoreType(store.ZK)
|
||||||
zookeeper.Register()
|
zookeeper.Register()
|
||||||
return p.Provider.CreateStore()
|
return p.Provider.CreateStore()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ -n "$TRAVIS_TAG" ] && [ "$DOCKER_VERSION" = "1.10.3" ]; then
|
if [ -n "$TRAVIS_TAG" ]; then
|
||||||
echo "Deploying..."
|
echo "Deploying..."
|
||||||
else
|
else
|
||||||
echo "Skipping deploy"
|
echo "Skipping deploy"
|
||||||
|
|
|
@ -177,7 +177,7 @@ func (server *Server) startHTTPServers() {
|
||||||
server.serverEntryPoints = server.buildEntryPoints(server.globalConfiguration)
|
server.serverEntryPoints = server.buildEntryPoints(server.globalConfiguration)
|
||||||
|
|
||||||
for newServerEntryPointName, newServerEntryPoint := range server.serverEntryPoints {
|
for newServerEntryPointName, newServerEntryPoint := range server.serverEntryPoints {
|
||||||
serverMiddlewares := []negroni.Handler{server.accessLoggerMiddleware, server.loggerMiddleware, metrics}
|
serverMiddlewares := []negroni.Handler{middlewares.NegroniRecoverHandler(), server.accessLoggerMiddleware, server.loggerMiddleware, metrics}
|
||||||
if server.globalConfiguration.Web != nil && server.globalConfiguration.Web.Metrics != nil {
|
if server.globalConfiguration.Web != nil && server.globalConfiguration.Web.Metrics != nil {
|
||||||
if server.globalConfiguration.Web.Metrics.Prometheus != nil {
|
if server.globalConfiguration.Web.Metrics.Prometheus != nil {
|
||||||
metricsMiddleware := middlewares.NewMetricsWrapper(middlewares.NewPrometheus(newServerEntryPointName, server.globalConfiguration.Web.Metrics.Prometheus))
|
metricsMiddleware := middlewares.NewMetricsWrapper(middlewares.NewPrometheus(newServerEntryPointName, server.globalConfiguration.Web.Metrics.Prometheus))
|
||||||
|
@ -258,19 +258,8 @@ func (server *Server) defaultConfigurationValues(configuration *types.Configurat
|
||||||
if configuration == nil || configuration.Frontends == nil {
|
if configuration == nil || configuration.Frontends == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, frontend := range configuration.Frontends {
|
server.configureFrontends(configuration.Frontends)
|
||||||
// default endpoints if not defined in frontends
|
server.configureBackends(configuration.Backends)
|
||||||
if len(frontend.EntryPoints) == 0 {
|
|
||||||
frontend.EntryPoints = server.globalConfiguration.DefaultEntryPoints
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for backendName, backend := range configuration.Backends {
|
|
||||||
_, err := types.NewLoadBalancerMethod(backend.LoadBalancer)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("Load balancer method '%+v' for backend %s: %v. Using default wrr.", backend.LoadBalancer, backendName, err)
|
|
||||||
backend.LoadBalancer = &types.LoadBalancer{Method: "wrr"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) listenConfigurations(stop chan bool) {
|
func (server *Server) listenConfigurations(stop chan bool) {
|
||||||
|
@ -661,7 +650,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||||
log.Errorf("Skipping frontend %s...", frontendName)
|
log.Errorf("Skipping frontend %s...", frontendName)
|
||||||
continue frontend
|
continue frontend
|
||||||
}
|
}
|
||||||
hcOpts := parseHealthCheckOptions(rebalancer, frontend.Backend, configuration.Backends[frontend.Backend].HealthCheck, *globalConfiguration.HealthCheck)
|
hcOpts := parseHealthCheckOptions(rebalancer, frontend.Backend, configuration.Backends[frontend.Backend].HealthCheck, globalConfiguration.HealthCheck)
|
||||||
if hcOpts != nil {
|
if hcOpts != nil {
|
||||||
log.Debugf("Setting up backend health check %s", *hcOpts)
|
log.Debugf("Setting up backend health check %s", *hcOpts)
|
||||||
backendsHealthcheck[frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts)
|
backendsHealthcheck[frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts)
|
||||||
|
@ -689,7 +678,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||||
continue frontend
|
continue frontend
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hcOpts := parseHealthCheckOptions(rr, frontend.Backend, configuration.Backends[frontend.Backend].HealthCheck, *globalConfiguration.HealthCheck)
|
hcOpts := parseHealthCheckOptions(rr, frontend.Backend, configuration.Backends[frontend.Backend].HealthCheck, globalConfiguration.HealthCheck)
|
||||||
if hcOpts != nil {
|
if hcOpts != nil {
|
||||||
log.Debugf("Setting up backend health check %s", *hcOpts)
|
log.Debugf("Setting up backend health check %s", *hcOpts)
|
||||||
backendsHealthcheck[frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts)
|
backendsHealthcheck[frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts)
|
||||||
|
@ -739,10 +728,11 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||||
}
|
}
|
||||||
authMiddleware, err := middlewares.NewAuthenticator(auth)
|
authMiddleware, err := middlewares.NewAuthenticator(auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Error creating Auth: ", err)
|
log.Errorf("Error creating Auth: %s", err)
|
||||||
}
|
} else {
|
||||||
negroni.Use(authMiddleware)
|
negroni.Use(authMiddleware)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if configuration.Backends[frontend.Backend].CircuitBreaker != nil {
|
if configuration.Backends[frontend.Backend].CircuitBreaker != nil {
|
||||||
log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression)
|
log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression)
|
||||||
cbreaker, err := middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger))
|
cbreaker, err := middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger))
|
||||||
|
@ -781,7 +771,17 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) wireFrontendBackend(serverRoute *serverRoute, handler http.Handler) {
|
func (server *Server) wireFrontendBackend(serverRoute *serverRoute, handler http.Handler) {
|
||||||
// add prefix
|
// path replace - This needs to always be the very last on the handler chain (first in the order in this function)
|
||||||
|
// -- Replacing Path should happen at the very end of the Modifier chain, after all the Matcher+Modifiers ran
|
||||||
|
if len(serverRoute.replacePath) > 0 {
|
||||||
|
handler = &middlewares.ReplacePath{
|
||||||
|
Path: serverRoute.replacePath,
|
||||||
|
Handler: handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add prefix - This needs to always be right before ReplacePath on the chain (second in order in this function)
|
||||||
|
// -- Adding Path Prefix should happen after all *Strip Matcher+Modifiers ran, but before Replace (in case it's configured)
|
||||||
if len(serverRoute.addPrefix) > 0 {
|
if len(serverRoute.addPrefix) > 0 {
|
||||||
handler = &middlewares.AddPrefix{
|
handler = &middlewares.AddPrefix{
|
||||||
Prefix: serverRoute.addPrefix,
|
Prefix: serverRoute.addPrefix,
|
||||||
|
@ -802,14 +802,6 @@ func (server *Server) wireFrontendBackend(serverRoute *serverRoute, handler http
|
||||||
handler = middlewares.NewStripPrefixRegex(handler, serverRoute.stripPrefixesRegex)
|
handler = middlewares.NewStripPrefixRegex(handler, serverRoute.stripPrefixesRegex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// path replace
|
|
||||||
if len(serverRoute.replacePath) > 0 {
|
|
||||||
handler = &middlewares.ReplacePath{
|
|
||||||
Path: serverRoute.replacePath,
|
|
||||||
Handler: handler,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serverRoute.route.Handler(handler)
|
serverRoute.route.Handler(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -849,8 +841,8 @@ func (server *Server) buildDefaultHTTPRouter() *mux.Router {
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHealthCheckOptions(lb healthcheck.LoadBalancer, backend string, hc *types.HealthCheck, hcConfig HealthCheckConfig) *healthcheck.Options {
|
func parseHealthCheckOptions(lb healthcheck.LoadBalancer, backend string, hc *types.HealthCheck, hcConfig *HealthCheckConfig) *healthcheck.Options {
|
||||||
if hc == nil || hc.Path == "" {
|
if hc == nil || hc.Path == "" || hcConfig == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -893,3 +885,29 @@ func sortedFrontendNamesForConfig(configuration *types.Configuration) []string {
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (server *Server) configureFrontends(frontends map[string]*types.Frontend) {
|
||||||
|
for _, frontend := range frontends {
|
||||||
|
// default endpoints if not defined in frontends
|
||||||
|
if len(frontend.EntryPoints) == 0 {
|
||||||
|
frontend.EntryPoints = server.globalConfiguration.DefaultEntryPoints
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Server) configureBackends(backends map[string]*types.Backend) {
|
||||||
|
for backendName, backend := range backends {
|
||||||
|
_, err := types.NewLoadBalancerMethod(backend.LoadBalancer)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Validation of load balancer method for backend %s failed: %s. Using default method wrr.", backendName, err)
|
||||||
|
var sticky bool
|
||||||
|
if backend.LoadBalancer != nil {
|
||||||
|
sticky = backend.LoadBalancer.Sticky
|
||||||
|
}
|
||||||
|
backend.LoadBalancer = &types.LoadBalancer{
|
||||||
|
Method: "wrr",
|
||||||
|
Sticky: sticky,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,14 +2,18 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/flaeg"
|
"github.com/containous/flaeg"
|
||||||
|
"github.com/containous/mux"
|
||||||
"github.com/containous/traefik/healthcheck"
|
"github.com/containous/traefik/healthcheck"
|
||||||
|
"github.com/containous/traefik/testhelpers"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/vulcand/oxy/roundrobin"
|
"github.com/vulcand/oxy/roundrobin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,6 +31,85 @@ func (lb *testLoadBalancer) Servers() []*url.URL {
|
||||||
return []*url.URL{}
|
return []*url.URL{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServerMultipleFrontendRules(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
expression string
|
||||||
|
requestURL string
|
||||||
|
expectedURL string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
expression: "Host:foo.bar",
|
||||||
|
requestURL: "http://foo.bar",
|
||||||
|
expectedURL: "http://foo.bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expression: "PathPrefix:/management;ReplacePath:/health",
|
||||||
|
requestURL: "http://foo.bar/management",
|
||||||
|
expectedURL: "http://foo.bar/health",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expression: "Host:foo.bar;AddPrefix:/blah",
|
||||||
|
requestURL: "http://foo.bar/baz",
|
||||||
|
expectedURL: "http://foo.bar/blah/baz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expression: "PathPrefixStripRegex:/one/{two}/{three:[0-9]+}",
|
||||||
|
requestURL: "http://foo.bar/one/some/12345/four",
|
||||||
|
expectedURL: "http://foo.bar/four",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expression: "PathPrefixStripRegex:/one/{two}/{three:[0-9]+};AddPrefix:/zero",
|
||||||
|
requestURL: "http://foo.bar/one/some/12345/four",
|
||||||
|
expectedURL: "http://foo.bar/zero/four",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expression: "AddPrefix:/blah;ReplacePath:/baz",
|
||||||
|
requestURL: "http://foo.bar/hello",
|
||||||
|
expectedURL: "http://foo.bar/baz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expression: "PathPrefixStrip:/management;ReplacePath:/health",
|
||||||
|
requestURL: "http://foo.bar/management",
|
||||||
|
expectedURL: "http://foo.bar/health",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range cases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.expression, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
router := mux.NewRouter()
|
||||||
|
route := router.NewRoute()
|
||||||
|
serverRoute := &serverRoute{route: route}
|
||||||
|
rules := &Rules{route: serverRoute}
|
||||||
|
|
||||||
|
expression := test.expression
|
||||||
|
routeResult, err := rules.Parse(expression)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error while building route for %s: %+v", expression, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
request := testhelpers.MustNewRequest(http.MethodGet, test.requestURL, nil)
|
||||||
|
routeMatch := routeResult.Match(request, &mux.RouteMatch{Route: routeResult})
|
||||||
|
|
||||||
|
if !routeMatch {
|
||||||
|
t.Fatalf("Rule %s doesn't match", expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := new(Server)
|
||||||
|
|
||||||
|
server.wireFrontendBackend(serverRoute, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.String() != test.expectedURL {
|
||||||
|
t.Fatalf("got URL %s, expected %s", r.URL.String(), test.expectedURL)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
serverRoute.route.GetHandler().ServeHTTP(nil, request)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestServerLoadConfigHealthCheckOptions(t *testing.T) {
|
func TestServerLoadConfigHealthCheckOptions(t *testing.T) {
|
||||||
healthChecks := []*types.HealthCheck{
|
healthChecks := []*types.HealthCheck{
|
||||||
nil,
|
nil,
|
||||||
|
@ -151,10 +234,125 @@ func TestServerParseHealthCheckOptions(t *testing.T) {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
gotOpts := parseHealthCheckOptions(lb, "backend", test.hc, HealthCheckConfig{Interval: flaeg.Duration(globalInterval)})
|
gotOpts := parseHealthCheckOptions(lb, "backend", test.hc, &HealthCheckConfig{Interval: flaeg.Duration(globalInterval)})
|
||||||
if !reflect.DeepEqual(gotOpts, test.wantOpts) {
|
if !reflect.DeepEqual(gotOpts, test.wantOpts) {
|
||||||
t.Errorf("got health check options %+v, want %+v", gotOpts, test.wantOpts)
|
t.Errorf("got health check options %+v, want %+v", gotOpts, test.wantOpts)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServerLoadConfigEmptyBasicAuth(t *testing.T) {
|
||||||
|
globalConfig := GlobalConfiguration{
|
||||||
|
EntryPoints: EntryPoints{
|
||||||
|
"http": &EntryPoint{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicConfigs := configs{
|
||||||
|
"config": &types.Configuration{
|
||||||
|
Frontends: map[string]*types.Frontend{
|
||||||
|
"frontend": {
|
||||||
|
EntryPoints: []string{"http"},
|
||||||
|
Backend: "backend",
|
||||||
|
BasicAuth: []string{""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Backends: map[string]*types.Backend{
|
||||||
|
"backend": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"server": {
|
||||||
|
URL: "http://localhost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LoadBalancer: &types.LoadBalancer{
|
||||||
|
Method: "Wrr",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := NewServer(globalConfig)
|
||||||
|
if _, err := srv.loadConfig(dynamicConfigs, globalConfig); err != nil {
|
||||||
|
t.Fatalf("got error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigureBackends(t *testing.T) {
|
||||||
|
validMethod := "Drr"
|
||||||
|
defaultMethod := "wrr"
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
lb *types.LoadBalancer
|
||||||
|
wantMethod string
|
||||||
|
wantSticky bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "valid load balancer method with sticky enabled",
|
||||||
|
lb: &types.LoadBalancer{
|
||||||
|
Method: validMethod,
|
||||||
|
Sticky: true,
|
||||||
|
},
|
||||||
|
wantMethod: validMethod,
|
||||||
|
wantSticky: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "valid load balancer method with sticky disabled",
|
||||||
|
lb: &types.LoadBalancer{
|
||||||
|
Method: validMethod,
|
||||||
|
Sticky: false,
|
||||||
|
},
|
||||||
|
wantMethod: validMethod,
|
||||||
|
wantSticky: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid load balancer method with sticky enabled",
|
||||||
|
lb: &types.LoadBalancer{
|
||||||
|
Method: "Invalid",
|
||||||
|
Sticky: true,
|
||||||
|
},
|
||||||
|
wantMethod: defaultMethod,
|
||||||
|
wantSticky: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid load balancer method with sticky disabled",
|
||||||
|
lb: &types.LoadBalancer{
|
||||||
|
Method: "Invalid",
|
||||||
|
Sticky: false,
|
||||||
|
},
|
||||||
|
wantMethod: defaultMethod,
|
||||||
|
wantSticky: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing load balancer",
|
||||||
|
lb: nil,
|
||||||
|
wantMethod: defaultMethod,
|
||||||
|
wantSticky: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
backend := &types.Backend{
|
||||||
|
LoadBalancer: test.lb,
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := Server{}
|
||||||
|
srv.configureBackends(map[string]*types.Backend{
|
||||||
|
"backend": backend,
|
||||||
|
})
|
||||||
|
|
||||||
|
wantLB := types.LoadBalancer{
|
||||||
|
Method: test.wantMethod,
|
||||||
|
Sticky: test.wantSticky,
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,21 @@
|
||||||
package testhelpers
|
package testhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
// Intp returns a pointer to the given integer value.
|
// Intp returns a pointer to the given integer value.
|
||||||
func Intp(i int) *int {
|
func Intp(i int) *int {
|
||||||
return &i
|
return &i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MustNewRequest creates a new http get request or panics if it can't
|
||||||
|
func MustNewRequest(method, urlStr string, body io.Reader) *http.Request {
|
||||||
|
request, err := http.NewRequest(method, urlStr, body)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to create HTTP %s Request for '%s': %s", method, urlStr, err))
|
||||||
|
}
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
|
@ -81,19 +81,18 @@ var loadBalancerMethodNames = []string{
|
||||||
|
|
||||||
// NewLoadBalancerMethod create a new LoadBalancerMethod from a given LoadBalancer.
|
// NewLoadBalancerMethod create a new LoadBalancerMethod from a given LoadBalancer.
|
||||||
func NewLoadBalancerMethod(loadBalancer *LoadBalancer) (LoadBalancerMethod, error) {
|
func NewLoadBalancerMethod(loadBalancer *LoadBalancer) (LoadBalancerMethod, error) {
|
||||||
|
var method string
|
||||||
if loadBalancer != nil {
|
if loadBalancer != nil {
|
||||||
|
method = loadBalancer.Method
|
||||||
for i, name := range loadBalancerMethodNames {
|
for i, name := range loadBalancerMethodNames {
|
||||||
if strings.EqualFold(name, loadBalancer.Method) {
|
if strings.EqualFold(name, method) {
|
||||||
return LoadBalancerMethod(i), nil
|
return LoadBalancerMethod(i), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Wrr, ErrInvalidLoadBalancerMethod
|
return Wrr, fmt.Errorf("invalid load-balancing method '%s'", method)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrInvalidLoadBalancerMethod is thrown when the specified load balancing method is invalid.
|
|
||||||
var ErrInvalidLoadBalancerMethod = errors.New("Invalid method, using default")
|
|
||||||
|
|
||||||
// Configuration of a provider.
|
// Configuration of a provider.
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
Backends map[string]*Backend `json:"backends,omitempty"`
|
Backends map[string]*Backend `json:"backends,omitempty"`
|
||||||
|
|
Loading…
Reference in a new issue