forked from Ivasoft/traefik
Compare commits
29 Commits
v1.3.0-rc1
...
v1.3.0-rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f521e72f15 | ||
|
|
88ea0a037b | ||
|
|
c963cee3c8 | ||
|
|
0be353d435 | ||
|
|
6afff2d403 | ||
|
|
12fa144f2f | ||
|
|
ac0e48b48c | ||
|
|
64aa37858b | ||
|
|
5348d4dccd | ||
|
|
c3c599241f | ||
|
|
c19432f95c | ||
|
|
bdf4f48d78 | ||
|
|
21aa0ea2da | ||
|
|
f8e7b5595b | ||
|
|
f9839f7b1d | ||
|
|
2c45428c8a | ||
|
|
30aa5a82b3 | ||
|
|
3f68e382fd | ||
|
|
9e57a283d7 | ||
|
|
eaedc1b924 | ||
|
|
e3ab4e4d63 | ||
|
|
48a91d05b5 | ||
|
|
111251da05 | ||
|
|
71cec1580b | ||
|
|
ffe1104851 | ||
|
|
aa4ed088bb | ||
|
|
3a4ec19817 | ||
|
|
d2b204a075 | ||
|
|
fe6c35bc6b |
@@ -1,7 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
sudo -E apt-get -yq update
|
||||
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-engine=${DOCKER_VERSION}*
|
||||
docker version
|
||||
|
||||
pip install --user -r requirements.txt
|
||||
|
||||
make pull-images
|
||||
make validate
|
||||
ci_retry make validate
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
make test-unit && make test-integration
|
||||
make test-unit
|
||||
ci_retry make test-integration
|
||||
make -j${N_MAKE_JOBS} crossbinary-default-parallel
|
||||
|
||||
@@ -5,6 +5,8 @@ export secure='btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvs
|
||||
|
||||
export REPO='containous/traefik'
|
||||
|
||||
export DOCKER_VERSION=1.12.6
|
||||
|
||||
if VERSION=$(git describe --exact-match --abbrev=0 --tags);
|
||||
then
|
||||
export VERSION
|
||||
@@ -15,3 +17,25 @@ fi
|
||||
export CODENAME=raclette
|
||||
|
||||
export N_MAKE_JOBS=2
|
||||
|
||||
|
||||
function ci_retry {
|
||||
|
||||
local NRETRY=3
|
||||
local NSLEEP=5
|
||||
local n=0
|
||||
|
||||
until [ $n -ge $NRETRY ]
|
||||
do
|
||||
"$@" && break
|
||||
n=$[$n+1]
|
||||
echo "$@ failed, attempt ${n}/${NRETRY}"
|
||||
sleep $NSLEEP
|
||||
done
|
||||
|
||||
[ $n -lt $NRETRY ]
|
||||
|
||||
}
|
||||
|
||||
export -f ci_retry
|
||||
|
||||
|
||||
28
.travis.yml
28
.travis.yml
@@ -11,32 +11,20 @@ env:
|
||||
- VERSION: $TRAVIS_TAG
|
||||
- CODENAME: raclette
|
||||
- N_MAKE_JOBS: 2
|
||||
- DOCKER_VERSION: 1.12.6
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- env: DOCKER_VERSION=1.10.3
|
||||
- env: DOCKER_VERSION=1.12.6
|
||||
|
||||
before_install:
|
||||
- sudo -E apt-get -yq update
|
||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-engine=${DOCKER_VERSION}*
|
||||
install:
|
||||
- docker version
|
||||
- pip install --user -r requirements.txt
|
||||
- make pull-images
|
||||
before_script:
|
||||
- make validate
|
||||
script:
|
||||
- make test-unit && travis_retry make test-integration
|
||||
- make -j${N_MAKE_JOBS} crossbinary-default-parallel
|
||||
after_failure:
|
||||
- docker ps
|
||||
- echo "Skipping tests... (Tests are executed on SemaphoreCI)"
|
||||
|
||||
before_deploy:
|
||||
- >
|
||||
if ! [ "$BEFORE_DEPLOY_RUN" ]; then
|
||||
export BEFORE_DEPLOY_RUN=1;
|
||||
make -j${N_MAKE_JOBS} crossbinary-others-parallel;
|
||||
sudo -E apt-get -yq update;
|
||||
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-engine=${DOCKER_VERSION}*;
|
||||
docker version;
|
||||
pip install --user -r requirements.txt;
|
||||
make -j${N_MAKE_JOBS} crossbinary-parallel;
|
||||
make image;
|
||||
mkdocs build --clean;
|
||||
tar cfz dist/traefik-${VERSION}.src.tar.gz --exclude-vcs --exclude dist .;
|
||||
|
||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,5 +1,19 @@
|
||||
# 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)
|
||||
[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"
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
@@ -39,6 +39,6 @@ bmysql:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
|
||||
log_driver: none
|
||||
brabbitmq:
|
||||
image: rabbitmq:3
|
||||
image: rabbitmq:3-alpine
|
||||
environment:
|
||||
RABBITMQ_NODE_IP_ADDRESS: "0.0.0.0"
|
||||
|
||||
@@ -12,6 +12,6 @@ consul:
|
||||
- "8302"
|
||||
- "8302/udp"
|
||||
nginx:
|
||||
image: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "8881:80"
|
||||
|
||||
@@ -12,6 +12,6 @@ consul:
|
||||
- "8302"
|
||||
- "8302/udp"
|
||||
nginx:
|
||||
image: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "8881:80"
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
nginx1:
|
||||
image: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "8881:80"
|
||||
nginx2:
|
||||
image: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "8882:80"
|
||||
nginx3:
|
||||
image: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "8883:80"
|
||||
nginx4:
|
||||
image: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "8884:80"
|
||||
nginx5:
|
||||
image: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "8885:80"
|
||||
|
||||
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 (
|
||||
_ http.ResponseWriter = &ResponseRecorder{}
|
||||
_ http.Hijacker = &ResponseRecorder{}
|
||||
_ http.Flusher = &ResponseRecorder{}
|
||||
_ http.CloseNotifier = &ResponseRecorder{}
|
||||
_ Stateful = &ResponseRecorder{}
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Stateful = &responseRecorder{}
|
||||
)
|
||||
|
||||
// StatsRecorder is an optional middleware that records more details statistics
|
||||
// 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
|
||||
@@ -51,6 +57,23 @@ func (r *responseRecorder) WriteHeader(status int) {
|
||||
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
|
||||
// is processed. If the response is 4xx or 5xx, add it to the list of 10 most
|
||||
// recent errors.
|
||||
|
||||
@@ -25,13 +25,13 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
p.Kvclient = store
|
||||
p.SetKVClient(store)
|
||||
return p.Provider.Provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
||||
// CreateStore creates the KV store
|
||||
func (p *Provider) CreateStore() (store.Store, error) {
|
||||
p.StoreType = store.BOLTDB
|
||||
p.SetStoreType(store.BOLTDB)
|
||||
boltdb.Register()
|
||||
return p.Provider.CreateStore()
|
||||
}
|
||||
|
||||
@@ -25,13 +25,13 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
p.Kvclient = store
|
||||
p.SetKVClient(store)
|
||||
return p.Provider.Provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
||||
// CreateStore creates the KV store
|
||||
func (p *Provider) CreateStore() (store.Store, error) {
|
||||
p.StoreType = store.CONSUL
|
||||
p.SetStoreType(store.CONSUL)
|
||||
consul.Register()
|
||||
return p.Provider.CreateStore()
|
||||
}
|
||||
|
||||
@@ -25,13 +25,13 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
p.Kvclient = store
|
||||
p.SetKVClient(store)
|
||||
return p.Provider.Provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
||||
// CreateStore creates the KV store
|
||||
func (p *Provider) CreateStore() (store.Store, error) {
|
||||
p.StoreType = store.ETCD
|
||||
p.SetStoreType(store.ETCD)
|
||||
etcd.Register()
|
||||
return p.Provider.CreateStore()
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
// Provider holds configuration of the Provider provider.
|
||||
type Provider struct {
|
||||
provider.BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
Delay string
|
||||
Endpoint string `description:"Eureka server endpoint"`
|
||||
Delay string `description:"Override default configuration time between refresh"`
|
||||
}
|
||||
|
||||
// Provide allows the eureka provider to provide configurations to traefik
|
||||
|
||||
@@ -156,14 +156,16 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
||||
|
||||
PassHostHeader := p.getPassHostHeader()
|
||||
|
||||
passHostHeaderAnnotation := i.Annotations["traefik.frontend.passHostHeader"]
|
||||
switch passHostHeaderAnnotation {
|
||||
case "true":
|
||||
PassHostHeader = true
|
||||
case "false":
|
||||
passHostHeaderAnnotation, ok := i.Annotations["traefik.frontend.passHostHeader"]
|
||||
switch {
|
||||
case !ok:
|
||||
// No op.
|
||||
case passHostHeaderAnnotation == "false":
|
||||
PassHostHeader = false
|
||||
case passHostHeaderAnnotation == "true":
|
||||
PassHostHeader = true
|
||||
default:
|
||||
log.Warnf("Unknown value of %s for traefik.frontend.passHostHeader, falling back to %s", passHostHeaderAnnotation, PassHostHeader)
|
||||
log.Warnf("Unknown value '%s' for traefik.frontend.passHostHeader, falling back to %s", passHostHeaderAnnotation, PassHostHeader)
|
||||
}
|
||||
if realm := i.Annotations["ingress.kubernetes.io/auth-realm"]; realm != "" && realm != traefikDefaultRealm {
|
||||
return nil, errors.New("no realm customization supported")
|
||||
|
||||
@@ -26,8 +26,8 @@ type Provider struct {
|
||||
TLS *provider.ClientTLS `description:"Enable TLS support"`
|
||||
Username string `description:"KV Username"`
|
||||
Password string `description:"KV Password"`
|
||||
StoreType store.Backend
|
||||
Kvclient store.Store
|
||||
storeType store.Backend
|
||||
kvclient store.Store
|
||||
}
|
||||
|
||||
// CreateStore create the K/V store
|
||||
@@ -47,15 +47,25 @@ func (p *Provider) CreateStore() (store.Store, error) {
|
||||
}
|
||||
}
|
||||
return libkv.NewStore(
|
||||
p.StoreType,
|
||||
p.storeType,
|
||||
strings.Split(p.Endpoint, ","),
|
||||
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 {
|
||||
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 {
|
||||
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()
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: string(p.StoreType),
|
||||
ProviderName: string(p.storeType),
|
||||
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 {
|
||||
p.Constraints = append(p.Constraints, constraints...)
|
||||
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)
|
||||
}
|
||||
if p.Watch {
|
||||
@@ -105,7 +115,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||
}
|
||||
configuration := p.loadConfig()
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: string(p.StoreType),
|
||||
ProviderName: string(p.storeType),
|
||||
Configuration: configuration,
|
||||
}
|
||||
return nil
|
||||
@@ -152,7 +162,7 @@ func (p *Provider) loadConfig() *types.Configuration {
|
||||
|
||||
func (p *Provider) list(keys ...string) []string {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keysPairs, err := p.Kvclient.List(joinedKeys)
|
||||
keysPairs, err := p.kvclient.List(joinedKeys)
|
||||
if err != nil {
|
||||
log.Debugf("Cannot get keys %s %s ", joinedKeys, err)
|
||||
return nil
|
||||
@@ -169,7 +179,7 @@ func (p *Provider) listServers(backend string) []string {
|
||||
serverNames := p.list(backend, "/servers/")
|
||||
return fun.Filter(func(serverName string) bool {
|
||||
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 {
|
||||
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 {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keyPair, err := p.Kvclient.Get(strings.TrimPrefix(joinedKeys, "/"))
|
||||
keyPair, err := p.kvclient.Get(strings.TrimPrefix(joinedKeys, "/"))
|
||||
if err != nil {
|
||||
log.Debugf("Cannot get key %s %s, setting default %s", joinedKeys, err, defaultValue)
|
||||
return defaultValue
|
||||
@@ -194,7 +204,7 @@ func (p *Provider) get(defaultValue string, keys ...string) string {
|
||||
|
||||
func (p *Provider) splitGet(keys ...string) []string {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keyPair, err := p.Kvclient.Get(joinedKeys)
|
||||
keyPair, err := p.kvclient.Get(joinedKeys)
|
||||
if err != nil {
|
||||
log.Debugf("Cannot get key %s %s, setting default empty", joinedKeys, err)
|
||||
return []string{}
|
||||
@@ -212,7 +222,7 @@ func (p *Provider) last(key string) string {
|
||||
|
||||
func (p *Provider) checkConstraints(keys ...string) bool {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keyPair, err := p.Kvclient.Get(joinedKeys)
|
||||
keyPair, err := p.kvclient.Get(joinedKeys)
|
||||
|
||||
value := ""
|
||||
if err == nil && keyPair != nil && keyPair.Value != nil {
|
||||
|
||||
@@ -20,21 +20,21 @@ func TestKvList(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
provider: &Provider{
|
||||
Kvclient: &Mock{},
|
||||
kvclient: &Mock{},
|
||||
},
|
||||
keys: []string{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
provider: &Provider{
|
||||
Kvclient: &Mock{},
|
||||
kvclient: &Mock{},
|
||||
},
|
||||
keys: []string{"traefik"},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
provider: &Provider{
|
||||
Kvclient: &Mock{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
Key: "foo",
|
||||
@@ -48,7 +48,7 @@ func TestKvList(t *testing.T) {
|
||||
},
|
||||
{
|
||||
provider: &Provider{
|
||||
Kvclient: &Mock{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
Key: "foo",
|
||||
@@ -62,7 +62,7 @@ func TestKvList(t *testing.T) {
|
||||
},
|
||||
{
|
||||
provider: &Provider{
|
||||
Kvclient: &Mock{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
Key: "foo/baz/1",
|
||||
@@ -95,7 +95,7 @@ func TestKvList(t *testing.T) {
|
||||
|
||||
// Error case
|
||||
provider := &Provider{
|
||||
Kvclient: &Mock{
|
||||
kvclient: &Mock{
|
||||
Error: KvError{
|
||||
List: store.ErrKeyNotFound,
|
||||
},
|
||||
@@ -115,21 +115,21 @@ func TestKvGet(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
provider: &Provider{
|
||||
Kvclient: &Mock{},
|
||||
kvclient: &Mock{},
|
||||
},
|
||||
keys: []string{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
provider: &Provider{
|
||||
Kvclient: &Mock{},
|
||||
kvclient: &Mock{},
|
||||
},
|
||||
keys: []string{"traefik"},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
provider: &Provider{
|
||||
Kvclient: &Mock{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
Key: "foo",
|
||||
@@ -143,7 +143,7 @@ func TestKvGet(t *testing.T) {
|
||||
},
|
||||
{
|
||||
provider: &Provider{
|
||||
Kvclient: &Mock{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
Key: "foo",
|
||||
@@ -157,7 +157,7 @@ func TestKvGet(t *testing.T) {
|
||||
},
|
||||
{
|
||||
provider: &Provider{
|
||||
Kvclient: &Mock{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
Key: "foo/baz/1",
|
||||
@@ -188,7 +188,7 @@ func TestKvGet(t *testing.T) {
|
||||
|
||||
// Error case
|
||||
provider := &Provider{
|
||||
Kvclient: &Mock{
|
||||
kvclient: &Mock{
|
||||
Error: KvError{
|
||||
Get: store.ErrKeyNotFound,
|
||||
},
|
||||
@@ -249,7 +249,7 @@ func TestKvWatchTree(t *testing.T) {
|
||||
returnedChans := make(chan chan []*store.KVPair)
|
||||
provider := &KvMock{
|
||||
Provider{
|
||||
Kvclient: &Mock{
|
||||
kvclient: &Mock{
|
||||
WatchTreeMethod: func() <-chan []*store.KVPair {
|
||||
c := make(chan []*store.KVPair, 10)
|
||||
returnedChans <- c
|
||||
@@ -378,7 +378,7 @@ func (s *Mock) Close() {
|
||||
func TestKVLoadConfig(t *testing.T) {
|
||||
provider := &Provider{
|
||||
Prefix: "traefik",
|
||||
Kvclient: &Mock{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
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"`
|
||||
KeepAlive flaeg.Duration `description:"Set a non-default TCP Keep Alive time in seconds"`
|
||||
ForceTaskHostname bool `description:"Force to use the task's hostname."`
|
||||
Basic *Basic
|
||||
Basic *Basic `description:"Enable basic authentication"`
|
||||
marathonClient marathon.Marathon
|
||||
}
|
||||
|
||||
// Basic holds basic authentication specific configurations
|
||||
type Basic struct {
|
||||
HTTPBasicAuthUser string
|
||||
HTTPBasicPassword string
|
||||
HTTPBasicAuthUser string `description:"Basic authentication User"`
|
||||
HTTPBasicPassword string `description:"Basic authentication Password"`
|
||||
}
|
||||
|
||||
type lightMarathonClient interface {
|
||||
|
||||
@@ -25,13 +25,13 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
p.Kvclient = store
|
||||
p.SetKVClient(store)
|
||||
return p.Provider.Provide(configurationChan, pool, constraints)
|
||||
}
|
||||
|
||||
// CreateStore creates the KV store
|
||||
func (p *Provider) CreateStore() (store.Store, error) {
|
||||
p.StoreType = store.ZK
|
||||
p.SetStoreType(store.ZK)
|
||||
zookeeper.Register()
|
||||
return p.Provider.CreateStore()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [ -n "$TRAVIS_TAG" ] && [ "$DOCKER_VERSION" = "1.10.3" ]; then
|
||||
if [ -n "$TRAVIS_TAG" ]; then
|
||||
echo "Deploying..."
|
||||
else
|
||||
echo "Skipping deploy"
|
||||
|
||||
@@ -173,7 +173,7 @@ func (server *Server) startHTTPServers() {
|
||||
server.serverEntryPoints = server.buildEntryPoints(server.globalConfiguration)
|
||||
|
||||
for newServerEntryPointName, newServerEntryPoint := range server.serverEntryPoints {
|
||||
serverMiddlewares := []negroni.Handler{server.loggerMiddleware, metrics}
|
||||
serverMiddlewares := []negroni.Handler{middlewares.NegroniRecoverHandler(), server.loggerMiddleware, metrics}
|
||||
if server.globalConfiguration.Web != nil && server.globalConfiguration.Web.Metrics != nil {
|
||||
if server.globalConfiguration.Web.Metrics.Prometheus != nil {
|
||||
metricsMiddleware := middlewares.NewMetricsWrapper(middlewares.NewPrometheus(newServerEntryPointName, server.globalConfiguration.Web.Metrics.Prometheus))
|
||||
@@ -254,19 +254,8 @@ func (server *Server) defaultConfigurationValues(configuration *types.Configurat
|
||||
if configuration == nil || configuration.Frontends == nil {
|
||||
return
|
||||
}
|
||||
for _, frontend := range configuration.Frontends {
|
||||
// default endpoints if not defined in frontends
|
||||
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"}
|
||||
}
|
||||
}
|
||||
server.configureFrontends(configuration.Frontends)
|
||||
server.configureBackends(configuration.Backends)
|
||||
}
|
||||
|
||||
func (server *Server) listenConfigurations(stop chan bool) {
|
||||
@@ -655,7 +644,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
log.Errorf("Skipping frontend %s...", frontendName)
|
||||
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 {
|
||||
log.Debugf("Setting up backend health check %s", *hcOpts)
|
||||
backendsHealthcheck[frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts)
|
||||
@@ -683,7 +672,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
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 {
|
||||
log.Debugf("Setting up backend health check %s", *hcOpts)
|
||||
backendsHealthcheck[frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts)
|
||||
@@ -733,9 +722,10 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
}
|
||||
authMiddleware, err := middlewares.NewAuthenticator(auth)
|
||||
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 {
|
||||
log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression)
|
||||
@@ -775,7 +765,17 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||
}
|
||||
|
||||
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 {
|
||||
handler = &middlewares.AddPrefix{
|
||||
Prefix: serverRoute.addPrefix,
|
||||
@@ -796,14 +796,6 @@ func (server *Server) wireFrontendBackend(serverRoute *serverRoute, handler http
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -843,8 +835,8 @@ func (server *Server) buildDefaultHTTPRouter() *mux.Router {
|
||||
return router
|
||||
}
|
||||
|
||||
func parseHealthCheckOptions(lb healthcheck.LoadBalancer, backend string, hc *types.HealthCheck, hcConfig HealthCheckConfig) *healthcheck.Options {
|
||||
if hc == nil || hc.Path == "" {
|
||||
func parseHealthCheckOptions(lb healthcheck.LoadBalancer, backend string, hc *types.HealthCheck, hcConfig *HealthCheckConfig) *healthcheck.Options {
|
||||
if hc == nil || hc.Path == "" || hcConfig == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -887,3 +879,29 @@ func sortedFrontendNamesForConfig(configuration *types.Configuration) []string {
|
||||
sort.Strings(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 (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/mux"
|
||||
"github.com/containous/traefik/healthcheck"
|
||||
"github.com/containous/traefik/testhelpers"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
)
|
||||
|
||||
@@ -27,6 +31,85 @@ func (lb *testLoadBalancer) Servers() []*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) {
|
||||
healthChecks := []*types.HealthCheck{
|
||||
nil,
|
||||
@@ -151,10 +234,125 @@ func TestServerParseHealthCheckOptions(t *testing.T) {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
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) {
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Intp returns a pointer to the given integer value.
|
||||
func Intp(i int) *int {
|
||||
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.
|
||||
func NewLoadBalancerMethod(loadBalancer *LoadBalancer) (LoadBalancerMethod, error) {
|
||||
var method string
|
||||
if loadBalancer != nil {
|
||||
method = loadBalancer.Method
|
||||
for i, name := range loadBalancerMethodNames {
|
||||
if strings.EqualFold(name, loadBalancer.Method) {
|
||||
if strings.EqualFold(name, method) {
|
||||
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.
|
||||
type Configuration struct {
|
||||
Backends map[string]*Backend `json:"backends,omitempty"`
|
||||
|
||||
@@ -7,7 +7,8 @@ RUN apt-get -yq update \
|
||||
&& apt-get -yq --no-install-suggests --no-install-recommends --force-yes install apt-transport-https \
|
||||
&& curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
|
||||
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
|
||||
&& apt-get -yq update && apt-get -yq --no-install-suggests --no-install-recommends --force-yes install yarn
|
||||
&& apt-get -yq update && apt-get -yq --no-install-suggests --no-install-recommends --force-yes install yarn \
|
||||
&& rm -fr /var/lib/apt/lists/
|
||||
|
||||
COPY package.json $WEBUI_DIR/
|
||||
COPY yarn.lock $WEBUI_DIR/
|
||||
|
||||
Reference in New Issue
Block a user