refactor(kv): split provide and configuration.
This commit is contained in:
parent
e3d1201b46
commit
be0dd71bb4
9 changed files with 944 additions and 546 deletions
|
@ -19,14 +19,14 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Services IP addresses fixed in the configuration
|
// Services IP addresses fixed in the configuration
|
||||||
ipEtcd string = "172.18.0.2"
|
ipEtcd = "172.18.0.2"
|
||||||
ipWhoami01 string = "172.18.0.3"
|
ipWhoami01 = "172.18.0.3"
|
||||||
ipWhoami02 string = "172.18.0.4"
|
ipWhoami02 = "172.18.0.4"
|
||||||
ipWhoami03 string = "172.18.0.5"
|
ipWhoami03 = "172.18.0.5"
|
||||||
ipWhoami04 string = "172.18.0.6"
|
ipWhoami04 = "172.18.0.6"
|
||||||
|
|
||||||
traefikEtcdURL string = "http://127.0.0.1:8000/"
|
traefikEtcdURL = "http://127.0.0.1:8000/"
|
||||||
traefikWebEtcdURL string = "http://127.0.0.1:8081/"
|
traefikWebEtcdURL = "http://127.0.0.1:8081/"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Etcd test suites (using libcompose)
|
// Etcd test suites (using libcompose)
|
||||||
|
@ -289,7 +289,7 @@ func (s *Etcd3Suite) TestGlobalConfiguration(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Etcd3Suite) TestCertificatesContentstWithSNIConfigHandshake(c *check.C) {
|
func (s *Etcd3Suite) TestCertificatesContentWithSNIConfigHandshake(c *check.C) {
|
||||||
// start traefik
|
// start traefik
|
||||||
cmd, display := s.traefikCmd(
|
cmd, display := s.traefikCmd(
|
||||||
withConfigFile("fixtures/simple_web.toml"),
|
withConfigFile("fixtures/simple_web.toml"),
|
||||||
|
|
|
@ -291,7 +291,7 @@ func (s *EtcdSuite) TestGlobalConfiguration(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *EtcdSuite) TestCertificatesContentstWithSNIConfigHandshake(c *check.C) {
|
func (s *EtcdSuite) TestCertificatesContentWithSNIConfigHandshake(c *check.C) {
|
||||||
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
|
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
|
||||||
// start Træfik
|
// start Træfik
|
||||||
cmd, display := s.traefikCmd(
|
cmd, display := s.traefikCmd(
|
||||||
|
|
121
provider/kv/filler_test.go
Normal file
121
provider/kv/filler_test.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package kv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/libkv/store"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ByKey []*store.KVPair
|
||||||
|
|
||||||
|
func (a ByKey) Len() int { return len(a) }
|
||||||
|
func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a ByKey) Less(i, j int) bool { return a[i].Key < a[j].Key }
|
||||||
|
|
||||||
|
func filler(prefix string, opts ...func(string, map[string]*store.KVPair)) []*store.KVPair {
|
||||||
|
buf := make(map[string]*store.KVPair)
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(prefix, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result ByKey
|
||||||
|
for _, value := range buf {
|
||||||
|
result = append(result, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func backend(name string, opts ...func(map[string]string)) func(string, map[string]*store.KVPair) {
|
||||||
|
return entry(pathBackends+name, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func frontend(name string, opts ...func(map[string]string)) func(string, map[string]*store.KVPair) {
|
||||||
|
return entry(pathFrontends+name, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func entry(root string, opts ...func(map[string]string)) func(string, map[string]*store.KVPair) {
|
||||||
|
return func(prefix string, pairs map[string]*store.KVPair) {
|
||||||
|
prefixedRoot := prefix + pathSeparator + strings.TrimPrefix(root, pathSeparator)
|
||||||
|
pairs[prefixedRoot] = &store.KVPair{Key: prefixedRoot, Value: []byte("")}
|
||||||
|
|
||||||
|
transit := make(map[string]string)
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(transit)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range transit {
|
||||||
|
fill(pairs, prefixedRoot, key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fill(pairs map[string]*store.KVPair, previous string, current string, value string) {
|
||||||
|
clean := strings.TrimPrefix(current, pathSeparator)
|
||||||
|
|
||||||
|
i := strings.IndexRune(clean, '/')
|
||||||
|
if i > 0 {
|
||||||
|
key := previous + pathSeparator + clean[:i]
|
||||||
|
|
||||||
|
if _, ok := pairs[key]; !ok || len(pairs[key].Value) == 0 {
|
||||||
|
pairs[key] = &store.KVPair{Key: key, Value: []byte("")}
|
||||||
|
}
|
||||||
|
|
||||||
|
fill(pairs, key, clean[i:], value)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := previous + pathSeparator + clean
|
||||||
|
pairs[key] = &store.KVPair{Key: key, Value: []byte(value)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withPair(key string, value string) func(map[string]string) {
|
||||||
|
return func(pairs map[string]string) {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pairs[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFiller(t *testing.T) {
|
||||||
|
expected := []*store.KVPair{
|
||||||
|
{Key: "traefik/backends/backend.with.dot.too", Value: []byte("")},
|
||||||
|
{Key: "traefik/backends/backend.with.dot.too/servers", Value: []byte("")},
|
||||||
|
{Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot", Value: []byte("")},
|
||||||
|
{Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot.without.url", Value: []byte("")},
|
||||||
|
{Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot.without.url/weight", Value: []byte("0")},
|
||||||
|
{Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot/url", Value: []byte("http://172.17.0.2:80")},
|
||||||
|
{Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot/weight", Value: []byte("0")},
|
||||||
|
{Key: "traefik/frontends/frontend.with.dot", Value: []byte("")},
|
||||||
|
{Key: "traefik/frontends/frontend.with.dot/backend", Value: []byte("backend.with.dot.too")},
|
||||||
|
{Key: "traefik/frontends/frontend.with.dot/routes", Value: []byte("")},
|
||||||
|
{Key: "traefik/frontends/frontend.with.dot/routes/route.with.dot", Value: []byte("")},
|
||||||
|
{Key: "traefik/frontends/frontend.with.dot/routes/route.with.dot/rule", Value: []byte("Host:test.localhost")},
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs1 := filler("traefik",
|
||||||
|
frontend("frontend.with.dot",
|
||||||
|
withPair("backend", "backend.with.dot.too"),
|
||||||
|
withPair("routes/route.with.dot/rule", "Host:test.localhost")),
|
||||||
|
backend("backend.with.dot.too",
|
||||||
|
withPair("servers/server.with.dot/url", "http://172.17.0.2:80"),
|
||||||
|
withPair("servers/server.with.dot/weight", "0"),
|
||||||
|
withPair("servers/server.with.dot.without.url/weight", "0")),
|
||||||
|
)
|
||||||
|
assert.EqualValues(t, expected, pairs1)
|
||||||
|
|
||||||
|
pairs2 := filler("traefik",
|
||||||
|
entry("frontends/frontend.with.dot",
|
||||||
|
withPair("backend", "backend.with.dot.too"),
|
||||||
|
withPair("routes/route.with.dot/rule", "Host:test.localhost")),
|
||||||
|
entry("backends/backend.with.dot.too",
|
||||||
|
withPair("servers/server.with.dot/url", "http://172.17.0.2:80"),
|
||||||
|
withPair("servers/server.with.dot/weight", "0"),
|
||||||
|
withPair("servers/server.with.dot.without.url/weight", "0")),
|
||||||
|
)
|
||||||
|
assert.EqualValues(t, expected, pairs2)
|
||||||
|
}
|
24
provider/kv/keynames.go
Normal file
24
provider/kv/keynames.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package kv
|
||||||
|
|
||||||
|
const (
|
||||||
|
pathBackends = "/backends/"
|
||||||
|
pathBackendCircuitBreakerExpression = "/circuitbreaker/expression"
|
||||||
|
pathBackendHealthCheckPath = "/healthcheck/path"
|
||||||
|
pathBackendHealthCheckInterval = "/healthcheck/interval"
|
||||||
|
pathBackendLoadBalancerMethod = "/loadbalancer/method"
|
||||||
|
pathBackendLoadBalancerSticky = "/loadbalancer/sticky"
|
||||||
|
pathBackendLoadBalancerStickiness = "/loadbalancer/stickiness"
|
||||||
|
pathBackendLoadBalancerStickinessCookieName = "/loadbalancer/stickiness/cookiename"
|
||||||
|
pathBackendServers = "/servers/"
|
||||||
|
pathBackendServerURL = "/url"
|
||||||
|
|
||||||
|
pathFrontends = "/frontends/"
|
||||||
|
pathFrontendBackend = "/backend"
|
||||||
|
pathFrontendPriority = "/priority"
|
||||||
|
pathFrontendPassHostHeader = "/passHostHeader"
|
||||||
|
pathFrontendEntryPoints = "/entrypoints"
|
||||||
|
|
||||||
|
pathTags = "/tags"
|
||||||
|
pathAlias = "/alias"
|
||||||
|
pathSeparator = "/"
|
||||||
|
)
|
|
@ -4,10 +4,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
|
||||||
"github.com/cenk/backoff"
|
"github.com/cenk/backoff"
|
||||||
"github.com/containous/traefik/job"
|
"github.com/containous/traefik/job"
|
||||||
"github.com/containous/traefik/log"
|
"github.com/containous/traefik/log"
|
||||||
|
@ -27,7 +25,7 @@ type Provider struct {
|
||||||
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
|
||||||
|
@ -58,16 +56,16 @@ func (p *Provider) SetStoreType(storeType store.Backend) {
|
||||||
p.storeType = storeType
|
p.storeType = storeType
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetKVClient kvclient setter
|
// SetKVClient kvClient setter
|
||||||
func (p *Provider) SetKVClient(kvClient store.Store) {
|
func (p *Provider) SetKVClient(kvClient store.Store) {
|
||||||
p.kvclient = kvClient
|
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{}), nil)
|
events, err := p.kvClient.WatchTree(p.Prefix, make(chan struct{}), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to KV WatchTree: %v", err)
|
return fmt.Errorf("failed to KV WatchTree: %v", err)
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -77,7 +75,7 @@ func (p *Provider) watchKv(configurationChan chan<- types.ConfigMessage, prefix
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New("watchtree channel closed")
|
return errors.New("watchtree channel closed")
|
||||||
}
|
}
|
||||||
configuration := p.loadConfig()
|
configuration := p.buildConfiguration()
|
||||||
if configuration != nil {
|
if configuration != nil {
|
||||||
configurationChan <- types.ConfigMessage{
|
configurationChan <- types.ConfigMessage{
|
||||||
ProviderName: string(p.storeType),
|
ProviderName: string(p.storeType),
|
||||||
|
@ -93,7 +91,7 @@ func (p *Provider) watchKv(configurationChan chan<- types.ConfigMessage, prefix
|
||||||
}
|
}
|
||||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Cannot connect to KV server: %v", err)
|
return fmt.Errorf("cannot connect to KV server: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -102,8 +100,8 @@ 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(p.Prefix+"/qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj", nil); err != nil {
|
if _, err := p.kvClient.Exists(p.Prefix+"/qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj", nil); 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 {
|
||||||
pool.Go(func(stop chan bool) {
|
pool.Go(func(stop chan bool) {
|
||||||
|
@ -113,7 +111,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
configuration := p.loadConfig()
|
configuration := p.buildConfiguration()
|
||||||
configurationChan <- types.ConfigMessage{
|
configurationChan <- types.ConfigMessage{
|
||||||
ProviderName: string(p.storeType),
|
ProviderName: string(p.storeType),
|
||||||
Configuration: configuration,
|
Configuration: configuration,
|
||||||
|
@ -125,142 +123,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
|
||||||
}
|
}
|
||||||
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Cannot connect to KV server: %v", err)
|
return fmt.Errorf("cannot connect to KV server: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) loadConfig() *types.Configuration {
|
|
||||||
templateObjects := struct {
|
|
||||||
Prefix string
|
|
||||||
}{
|
|
||||||
// Allow `/traefik/alias` to supersede `p.Prefix`
|
|
||||||
strings.TrimSuffix(p.get(p.Prefix, p.Prefix+"/alias"), "/"),
|
|
||||||
}
|
|
||||||
|
|
||||||
var KvFuncMap = template.FuncMap{
|
|
||||||
"List": p.list,
|
|
||||||
"ListServers": p.listServers,
|
|
||||||
"Get": p.get,
|
|
||||||
"SplitGet": p.splitGet,
|
|
||||||
"Last": p.last,
|
|
||||||
"getSticky": p.getSticky,
|
|
||||||
"hasStickinessLabel": p.hasStickinessLabel,
|
|
||||||
"getStickinessCookieName": p.getStickinessCookieName,
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration, err := p.GetConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, frontend := range configuration.Frontends {
|
|
||||||
if _, ok := configuration.Backends[frontend.Backend]; !ok {
|
|
||||||
delete(configuration.Frontends, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return configuration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) list(keys ...string) []string {
|
|
||||||
joinedKeys := strings.Join(keys, "")
|
|
||||||
keysPairs, err := p.kvclient.List(joinedKeys, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("Cannot get keys %s %s ", joinedKeys, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
directoryKeys := make(map[string]string)
|
|
||||||
for _, key := range keysPairs {
|
|
||||||
directory := strings.Split(strings.TrimPrefix(key.Key, joinedKeys), "/")[0]
|
|
||||||
directoryKeys[directory] = joinedKeys + directory
|
|
||||||
}
|
|
||||||
return fun.Values(directoryKeys).([]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
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, nil); err != nil {
|
|
||||||
if err != store.ErrKeyNotFound {
|
|
||||||
log.Errorf("Failed to retrieve value for key %s: %s", key, err)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return p.checkConstraints(serverName, "/tags")
|
|
||||||
}, serverNames).([]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) get(defaultValue string, keys ...string) string {
|
|
||||||
joinedKeys := strings.Join(keys, "")
|
|
||||||
if p.storeType == store.ETCD {
|
|
||||||
joinedKeys = strings.TrimPrefix(joinedKeys, "/")
|
|
||||||
}
|
|
||||||
keyPair, err := p.kvclient.Get(joinedKeys, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("Cannot get key %s %s, setting default %s", joinedKeys, err, defaultValue)
|
|
||||||
return defaultValue
|
|
||||||
} else if keyPair == nil {
|
|
||||||
log.Debugf("Cannot get key %s, setting default %s", joinedKeys, defaultValue)
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return string(keyPair.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) splitGet(keys ...string) []string {
|
|
||||||
joinedKeys := strings.Join(keys, "")
|
|
||||||
keyPair, err := p.kvclient.Get(joinedKeys, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("Cannot get key %s %s, setting default empty", joinedKeys, err)
|
|
||||||
return []string{}
|
|
||||||
} else if keyPair == nil {
|
|
||||||
log.Debugf("Cannot get key %s, setting default %empty", joinedKeys)
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
return strings.Split(string(keyPair.Value), ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) last(key string) string {
|
|
||||||
splittedKey := strings.Split(key, "/")
|
|
||||||
return splittedKey[len(splittedKey)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) checkConstraints(keys ...string) bool {
|
|
||||||
joinedKeys := strings.Join(keys, "")
|
|
||||||
keyPair, err := p.kvclient.Get(joinedKeys, nil)
|
|
||||||
|
|
||||||
value := ""
|
|
||||||
if err == nil && keyPair != nil && keyPair.Value != nil {
|
|
||||||
value = string(keyPair.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
constraintTags := strings.Split(value, ",")
|
|
||||||
ok, failingConstraint := p.MatchConstraints(constraintTags)
|
|
||||||
if !ok {
|
|
||||||
if failingConstraint != nil {
|
|
||||||
log.Debugf("Constraint %v not matching with following tags: %v", failingConstraint.String(), value)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) getSticky(rootPath string) string {
|
|
||||||
stickyValue := p.get("", rootPath, "/loadbalancer", "/sticky")
|
|
||||||
if len(stickyValue) > 0 {
|
|
||||||
log.Warnf("Deprecated configuration found: %s. Please use %s.", "loadbalancer/sticky", "loadbalancer/stickiness")
|
|
||||||
} else {
|
|
||||||
stickyValue = "false"
|
|
||||||
}
|
|
||||||
return stickyValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) hasStickinessLabel(rootPath string) bool {
|
|
||||||
stickinessValue := p.get("false", rootPath, "/loadbalancer", "/stickiness")
|
|
||||||
return len(stickinessValue) > 0 && strings.EqualFold(strings.TrimSpace(stickinessValue), "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) getStickinessCookieName(rootPath string) string {
|
|
||||||
return p.get("", rootPath, "/loadbalancer", "/stickiness", "/cookiename")
|
|
||||||
}
|
|
||||||
|
|
178
provider/kv/kv_config.go
Normal file
178
provider/kv/kv_config.go
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
package kv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/ty/fun"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/provider/label"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/docker/libkv/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Provider) buildConfiguration() *types.Configuration {
|
||||||
|
templateObjects := struct {
|
||||||
|
Prefix string
|
||||||
|
}{
|
||||||
|
// Allow `/traefik/alias` to supersede `p.Prefix`
|
||||||
|
Prefix: strings.TrimSuffix(p.get(p.Prefix, p.Prefix+pathAlias), pathSeparator),
|
||||||
|
}
|
||||||
|
|
||||||
|
var KvFuncMap = template.FuncMap{
|
||||||
|
"List": p.list,
|
||||||
|
"ListServers": p.listServers,
|
||||||
|
"Get": p.get,
|
||||||
|
"SplitGet": p.splitGet,
|
||||||
|
"Last": p.last,
|
||||||
|
|
||||||
|
// Backend functions
|
||||||
|
"getSticky": p.getSticky,
|
||||||
|
"hasStickinessLabel": p.hasStickinessLabel,
|
||||||
|
"getStickinessCookieName": p.getStickinessCookieName,
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration, err := p.GetConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, frontend := range configuration.Frontends {
|
||||||
|
if _, ok := configuration.Backends[frontend.Backend]; !ok {
|
||||||
|
delete(configuration.Frontends, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) getSticky(rootPath string) bool {
|
||||||
|
stickyValue := p.get("", rootPath, pathBackendLoadBalancerSticky)
|
||||||
|
if len(stickyValue) > 0 {
|
||||||
|
log.Warnf("Deprecated configuration found: %s. Please use %s.", pathBackendLoadBalancerSticky, pathBackendLoadBalancerStickiness)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sticky, err := strconv.ParseBool(stickyValue)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Invalid %s value: %s.", pathBackendLoadBalancerSticky, stickyValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sticky
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) hasStickinessLabel(rootPath string) bool {
|
||||||
|
return p.getBool(false, rootPath, pathBackendLoadBalancerStickiness)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) getStickinessCookieName(rootPath string) string {
|
||||||
|
return p.get("", rootPath, pathBackendLoadBalancerStickinessCookieName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) listServers(backend string) []string {
|
||||||
|
serverNames := p.list(backend, pathBackendServers)
|
||||||
|
return fun.Filter(p.serverFilter, serverNames).([]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) serverFilter(serverName string) bool {
|
||||||
|
key := fmt.Sprint(serverName, pathBackendServerURL)
|
||||||
|
if _, err := p.kvClient.Get(key, nil); err != nil {
|
||||||
|
if err != store.ErrKeyNotFound {
|
||||||
|
log.Errorf("Failed to retrieve value for key %s: %s", key, err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return p.checkConstraints(serverName, pathTags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) checkConstraints(keys ...string) bool {
|
||||||
|
joinedKeys := strings.Join(keys, "")
|
||||||
|
keyPair, err := p.kvClient.Get(joinedKeys, nil)
|
||||||
|
|
||||||
|
value := ""
|
||||||
|
if err == nil && keyPair != nil && keyPair.Value != nil {
|
||||||
|
value = string(keyPair.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
constraintTags := label.SplitAndTrimString(value, ",")
|
||||||
|
ok, failingConstraint := p.MatchConstraints(constraintTags)
|
||||||
|
if !ok {
|
||||||
|
if failingConstraint != nil {
|
||||||
|
log.Debugf("Constraint %v not matching with following tags: %v", failingConstraint.String(), value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) get(defaultValue string, keyParts ...string) string {
|
||||||
|
key := strings.Join(keyParts, "")
|
||||||
|
|
||||||
|
if p.storeType == store.ETCD {
|
||||||
|
key = strings.TrimPrefix(key, pathSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPair, err := p.kvClient.Get(key, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Cannot get key %s %s, setting default %s", key, err, defaultValue)
|
||||||
|
return defaultValue
|
||||||
|
} else if keyPair == nil {
|
||||||
|
log.Debugf("Cannot get key %s, setting default %s", key, defaultValue)
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(keyPair.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) getBool(defaultValue bool, keyParts ...string) bool {
|
||||||
|
rawValue := p.get(strconv.FormatBool(defaultValue), keyParts...)
|
||||||
|
|
||||||
|
if len(rawValue) == 0 {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := strconv.ParseBool(rawValue)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Invalid value for %v: %s", keyParts, rawValue)
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) list(keyParts ...string) []string {
|
||||||
|
rootKey := strings.Join(keyParts, "")
|
||||||
|
|
||||||
|
keysPairs, err := p.kvClient.List(rootKey, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Cannot list keys under %q: %v", rootKey, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
directoryKeys := make(map[string]string)
|
||||||
|
for _, key := range keysPairs {
|
||||||
|
directory := strings.Split(strings.TrimPrefix(key.Key, rootKey), pathSeparator)[0]
|
||||||
|
directoryKeys[directory] = rootKey + directory
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := fun.Values(directoryKeys).([]string)
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) splitGet(keyParts ...string) []string {
|
||||||
|
value := p.get("", keyParts...)
|
||||||
|
|
||||||
|
if len(value) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return label.SplitAndTrimString(value, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) last(key string) string {
|
||||||
|
index := strings.LastIndex(key, pathSeparator)
|
||||||
|
return key[index+1:]
|
||||||
|
}
|
579
provider/kv/kv_config_test.go
Normal file
579
provider/kv/kv_config_test.go
Normal file
|
@ -0,0 +1,579 @@
|
||||||
|
package kv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/provider/label"
|
||||||
|
"github.com/containous/traefik/tls"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/docker/libkv/store"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func aKVPair(key string, value string) *store.KVPair {
|
||||||
|
return &store.KVPair{Key: key, Value: []byte(value)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderBuildConfiguration(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
kvPairs []*store.KVPair
|
||||||
|
expected *types.Configuration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "name with dot",
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
frontend("frontend.with.dot",
|
||||||
|
withPair("backend", "backend.with.dot.too"),
|
||||||
|
withPair("routes/route.with.dot/rule", "Host:test.localhost")),
|
||||||
|
backend("backend.with.dot.too",
|
||||||
|
withPair("servers/server.with.dot/url", "http://172.17.0.2:80"),
|
||||||
|
withPair("servers/server.with.dot/weight", "0"),
|
||||||
|
withPair("servers/server.with.dot.without.url/weight", "0")),
|
||||||
|
),
|
||||||
|
expected: &types.Configuration{
|
||||||
|
Backends: map[string]*types.Backend{
|
||||||
|
"backend.with.dot.too": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"server.with.dot": {
|
||||||
|
URL: "http://172.17.0.2:80",
|
||||||
|
Weight: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frontends: map[string]*types.Frontend{
|
||||||
|
"frontend.with.dot": {
|
||||||
|
Backend: "backend.with.dot.too",
|
||||||
|
PassHostHeader: true,
|
||||||
|
EntryPoints: []string{},
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"route.with.dot": {
|
||||||
|
Rule: "Host:test.localhost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all parameters",
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
backend("backend1",
|
||||||
|
withPair("healthcheck/path", "/health"),
|
||||||
|
withPair("healthcheck/port", "80"),
|
||||||
|
withPair("healthcheck/interval", "30s"),
|
||||||
|
withPair("maxconn/amount", "5"),
|
||||||
|
withPair("maxconn/extractorfunc", "client.ip"),
|
||||||
|
withPair(pathBackendCircuitBreakerExpression, label.DefaultCircuitBreakerExpression),
|
||||||
|
withPair(pathBackendLoadBalancerMethod, "drr"),
|
||||||
|
withPair(pathBackendLoadBalancerSticky, "true"),
|
||||||
|
withPair(pathBackendLoadBalancerStickiness, "true"),
|
||||||
|
withPair(pathBackendLoadBalancerStickinessCookieName, "tomate"),
|
||||||
|
withPair("servers/server1/url", "http://172.17.0.2:80"),
|
||||||
|
withPair("servers/server1/weight", "0"),
|
||||||
|
withPair("servers/server2/weight", "0")),
|
||||||
|
frontend("frontend1",
|
||||||
|
withPair(pathFrontendBackend, "backend1"),
|
||||||
|
withPair(pathFrontendPriority, "6"),
|
||||||
|
withPair(pathFrontendPassHostHeader, "false"),
|
||||||
|
withPair(pathFrontendEntryPoints, "http,https"),
|
||||||
|
withPair("routes/route1/rule", "Host:test.localhost"),
|
||||||
|
withPair("routes/route2/rule", "Path:/foo")),
|
||||||
|
entry("tlsconfiguration/foo",
|
||||||
|
withPair("entrypoints", "http,https"),
|
||||||
|
withPair("certificate/certfile", "certfile1"),
|
||||||
|
withPair("certificate/keyfile", "keyfile1")),
|
||||||
|
entry("tlsconfiguration/bar",
|
||||||
|
withPair("entrypoints", "http,https"),
|
||||||
|
withPair("certificate/certfile", "certfile2"),
|
||||||
|
withPair("certificate/keyfile", "keyfile2")),
|
||||||
|
),
|
||||||
|
expected: &types.Configuration{
|
||||||
|
Backends: map[string]*types.Backend{
|
||||||
|
"backend1": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"server1": {
|
||||||
|
URL: "http://172.17.0.2:80",
|
||||||
|
Weight: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CircuitBreaker: &types.CircuitBreaker{
|
||||||
|
Expression: "NetworkErrorRatio() > 1",
|
||||||
|
},
|
||||||
|
LoadBalancer: &types.LoadBalancer{
|
||||||
|
Method: "drr",
|
||||||
|
Sticky: true,
|
||||||
|
Stickiness: &types.Stickiness{
|
||||||
|
CookieName: "tomate",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxConn: &types.MaxConn{
|
||||||
|
Amount: 5,
|
||||||
|
ExtractorFunc: "client.ip",
|
||||||
|
},
|
||||||
|
HealthCheck: &types.HealthCheck{
|
||||||
|
Path: "/health",
|
||||||
|
Port: 0,
|
||||||
|
Interval: "30s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frontends: map[string]*types.Frontend{
|
||||||
|
"frontend1": {
|
||||||
|
Priority: 6,
|
||||||
|
EntryPoints: []string{"http", "https"},
|
||||||
|
Backend: "backend1",
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"route1": {
|
||||||
|
Rule: "Host:test.localhost",
|
||||||
|
},
|
||||||
|
"route2": {
|
||||||
|
Rule: "Path:/foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLSConfiguration: []*tls.Configuration{
|
||||||
|
{
|
||||||
|
EntryPoints: []string{"http", "https"},
|
||||||
|
Certificate: &tls.Certificate{
|
||||||
|
CertFile: "certfile2",
|
||||||
|
KeyFile: "keyfile2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EntryPoints: []string{"http", "https"},
|
||||||
|
Certificate: &tls.Certificate{
|
||||||
|
CertFile: "certfile1",
|
||||||
|
KeyFile: "keyfile1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
p := &Provider{
|
||||||
|
Prefix: "traefik",
|
||||||
|
kvClient: &Mock{
|
||||||
|
KVPairs: test.kvPairs,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := p.buildConfiguration()
|
||||||
|
assert.NotNil(t, actual)
|
||||||
|
|
||||||
|
assert.EqualValues(t, test.expected.Backends, actual.Backends)
|
||||||
|
assert.EqualValues(t, test.expected.Frontends, actual.Frontends)
|
||||||
|
assert.EqualValues(t, test.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderList(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
kvPairs []*store.KVPair
|
||||||
|
kvError error
|
||||||
|
keyParts []string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "empty key parts and empty store",
|
||||||
|
keyParts: []string{},
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when non existing key and empty store",
|
||||||
|
keyParts: []string{"traefik"},
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when non existing key",
|
||||||
|
kvPairs: []*store.KVPair{
|
||||||
|
aKVPair("foo", "bar"),
|
||||||
|
},
|
||||||
|
keyParts: []string{"bar"},
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when one key",
|
||||||
|
kvPairs: []*store.KVPair{
|
||||||
|
aKVPair("foo", "bar"),
|
||||||
|
},
|
||||||
|
keyParts: []string{"foo"},
|
||||||
|
expected: []string{"foo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when multiple sub keys and nested sub key",
|
||||||
|
kvPairs: []*store.KVPair{
|
||||||
|
aKVPair("foo/baz/1", "bar"),
|
||||||
|
aKVPair("foo/baz/2", "bar"),
|
||||||
|
aKVPair("foo/baz/biz/1", "bar"),
|
||||||
|
},
|
||||||
|
keyParts: []string{"foo", "/baz/"},
|
||||||
|
expected: []string{"foo/baz/1", "foo/baz/2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when KV error",
|
||||||
|
kvError: store.ErrNotReachable,
|
||||||
|
kvPairs: []*store.KVPair{
|
||||||
|
aKVPair("foo/baz/1", "bar1"),
|
||||||
|
aKVPair("foo/baz/2", "bar2"),
|
||||||
|
aKVPair("foo/baz/biz/1", "bar3"),
|
||||||
|
},
|
||||||
|
keyParts: []string{"foo/baz/1"},
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
p := &Provider{
|
||||||
|
kvClient: newKvClientMock(test.kvPairs, test.kvError),
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := p.list(test.keyParts...)
|
||||||
|
|
||||||
|
sort.Strings(test.expected)
|
||||||
|
assert.Equal(t, test.expected, actual, "key: %v", test.keyParts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderGet(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
kvPairs []*store.KVPair
|
||||||
|
storeType store.Backend
|
||||||
|
keyParts []string
|
||||||
|
defaultValue string
|
||||||
|
kvError error
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "when empty key parts, empty store",
|
||||||
|
defaultValue: "circle",
|
||||||
|
keyParts: []string{},
|
||||||
|
expected: "circle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when non existing key",
|
||||||
|
defaultValue: "circle",
|
||||||
|
kvPairs: []*store.KVPair{
|
||||||
|
aKVPair("foo", "bar"),
|
||||||
|
},
|
||||||
|
keyParts: []string{"bar"},
|
||||||
|
expected: "circle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when one part key",
|
||||||
|
kvPairs: []*store.KVPair{
|
||||||
|
aKVPair("foo", "bar"),
|
||||||
|
},
|
||||||
|
keyParts: []string{"foo"},
|
||||||
|
expected: "bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when several parts key",
|
||||||
|
kvPairs: []*store.KVPair{
|
||||||
|
aKVPair("foo/baz/1", "bar1"),
|
||||||
|
aKVPair("foo/baz/2", "bar2"),
|
||||||
|
aKVPair("foo/baz/biz/1", "bar3"),
|
||||||
|
},
|
||||||
|
keyParts: []string{"foo", "/baz/", "2"},
|
||||||
|
expected: "bar2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when several parts key, starts with /",
|
||||||
|
defaultValue: "circle",
|
||||||
|
kvPairs: []*store.KVPair{
|
||||||
|
aKVPair("foo/baz/1", "bar1"),
|
||||||
|
aKVPair("foo/baz/2", "bar2"),
|
||||||
|
aKVPair("foo/baz/biz/1", "bar3"),
|
||||||
|
},
|
||||||
|
keyParts: []string{"/foo", "/baz/", "2"},
|
||||||
|
expected: "circle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when several parts key starts with /, ETCD v2",
|
||||||
|
storeType: store.ETCD,
|
||||||
|
kvPairs: []*store.KVPair{
|
||||||
|
aKVPair("foo/baz/1", "bar1"),
|
||||||
|
aKVPair("foo/baz/2", "bar2"),
|
||||||
|
aKVPair("foo/baz/biz/1", "bar3"),
|
||||||
|
},
|
||||||
|
keyParts: []string{"/foo", "/baz/", "2"},
|
||||||
|
expected: "bar2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when KV error",
|
||||||
|
kvError: store.ErrNotReachable,
|
||||||
|
kvPairs: []*store.KVPair{
|
||||||
|
aKVPair("foo/baz/1", "bar1"),
|
||||||
|
aKVPair("foo/baz/2", "bar2"),
|
||||||
|
aKVPair("foo/baz/biz/1", "bar3"),
|
||||||
|
},
|
||||||
|
keyParts: []string{"foo/baz/1"},
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
p := &Provider{
|
||||||
|
kvClient: newKvClientMock(test.kvPairs, test.kvError),
|
||||||
|
storeType: test.storeType,
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := p.get(test.defaultValue, test.keyParts...)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, actual, "key %v", test.keyParts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderLast(t *testing.T) {
|
||||||
|
p := &Provider{}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
key string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
key: "",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "foo",
|
||||||
|
expected: "foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "foo/bar",
|
||||||
|
expected: "bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "foo/bar/baz",
|
||||||
|
expected: "baz",
|
||||||
|
},
|
||||||
|
// FIXME is this wanted ?
|
||||||
|
{
|
||||||
|
key: "foo/bar/",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
actual := p.last(test.key)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderSplitGet(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
kvPairs []*store.KVPair
|
||||||
|
kvError error
|
||||||
|
keyParts []string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "when has value",
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
frontend("foo",
|
||||||
|
withPair("bar", "courgette, carotte, tomate, aubergine"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyParts: []string{"traefik/frontends/foo/bar"},
|
||||||
|
expected: []string{"courgette", "carotte", "tomate", "aubergine"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when empty value",
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
frontend("foo",
|
||||||
|
withPair("bar", ""),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyParts: []string{"traefik/frontends/foo/bar"},
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when not existing key",
|
||||||
|
kvPairs: nil,
|
||||||
|
keyParts: []string{"traefik/frontends/foo/bar"},
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when KV error",
|
||||||
|
kvError: store.ErrNotReachable,
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
frontend("foo",
|
||||||
|
withPair("bar", ""),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyParts: []string{"traefik/frontends/foo/bar"},
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
p := &Provider{
|
||||||
|
kvClient: newKvClientMock(test.kvPairs, test.kvError),
|
||||||
|
}
|
||||||
|
|
||||||
|
values := p.splitGet(test.keyParts...)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, values, "key: %v", test.keyParts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderGetBool(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
kvPairs []*store.KVPair
|
||||||
|
kvError error
|
||||||
|
keyParts []string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "when value is 'true",
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
frontend("foo",
|
||||||
|
withPair("bar", "true"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyParts: []string{"traefik/frontends/foo/bar"},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when value is 'false",
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
frontend("foo",
|
||||||
|
withPair("bar", "false"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyParts: []string{"traefik/frontends/foo/bar"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when empty value",
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
frontend("foo",
|
||||||
|
withPair("bar", ""),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyParts: []string{"traefik/frontends/foo/bar"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when not existing key",
|
||||||
|
kvPairs: nil,
|
||||||
|
keyParts: []string{"traefik/frontends/foo/bar"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "when KV error",
|
||||||
|
kvError: store.ErrNotReachable,
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
frontend("foo",
|
||||||
|
withPair("bar", "true"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyParts: []string{"traefik/frontends/foo/bar"},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
p := &Provider{
|
||||||
|
kvClient: newKvClientMock(test.kvPairs, test.kvError),
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := p.getBool(false, test.keyParts...)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, actual, "key: %v", test.keyParts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderHasStickinessLabel(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
kvPairs []*store.KVPair
|
||||||
|
rootPath string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "without option",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "with cookie name without stickiness=true",
|
||||||
|
rootPath: "traefik/backends/foo",
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
backend("foo",
|
||||||
|
withPair(pathBackendLoadBalancerStickinessCookieName, "aubergine"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "stickiness=true",
|
||||||
|
rootPath: "traefik/backends/foo",
|
||||||
|
kvPairs: filler("traefik",
|
||||||
|
backend("foo",
|
||||||
|
withPair(pathBackendLoadBalancerStickiness, "true"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
p := &Provider{
|
||||||
|
kvClient: &Mock{
|
||||||
|
KVPairs: test.kvPairs,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := p.hasStickinessLabel(test.rootPath)
|
||||||
|
|
||||||
|
if actual != test.expected {
|
||||||
|
t.Fatalf("expected %v, got %v", test.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,18 +4,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containous/traefik/types"
|
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
type KvMock struct {
|
|
||||||
Provider
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *KvMock) loadConfig() *types.Configuration {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override Get/List to return a error
|
// Override Get/List to return a error
|
||||||
type KvError struct {
|
type KvError struct {
|
||||||
Get error
|
Get error
|
||||||
|
@ -29,6 +20,20 @@ type Mock struct {
|
||||||
WatchTreeMethod func() <-chan []*store.KVPair
|
WatchTreeMethod func() <-chan []*store.KVPair
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newKvClientMock(kvPairs []*store.KVPair, err error) *Mock {
|
||||||
|
mock := &Mock{
|
||||||
|
KVPairs: kvPairs,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
mock.Error = KvError{
|
||||||
|
Get: err,
|
||||||
|
List: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Mock) Put(key string, value []byte, opts *store.WriteOptions) error {
|
func (s *Mock) Put(key string, value []byte, opts *store.WriteOptions) error {
|
||||||
return errors.New("Put not supported")
|
return errors.New("Put not supported")
|
||||||
}
|
}
|
||||||
|
@ -82,9 +87,9 @@ func (s *Mock) List(prefix string, options *store.ReadOptions) ([]*store.KVPair,
|
||||||
if err := s.Error.List; err != nil {
|
if err := s.Error.List; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
kv := []*store.KVPair{}
|
var kv []*store.KVPair
|
||||||
for _, kvPair := range s.KVPairs {
|
for _, kvPair := range s.KVPairs {
|
||||||
if strings.HasPrefix(kvPair.Key, prefix) && !strings.ContainsAny(strings.TrimPrefix(kvPair.Key, prefix), "/") {
|
if strings.HasPrefix(kvPair.Key, prefix) && !strings.ContainsAny(strings.TrimPrefix(kvPair.Key, prefix), pathSeparator) {
|
||||||
kv = append(kv, kvPair)
|
kv = append(kv, kvPair)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package kv
|
package kv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -10,243 +8,16 @@ import (
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestKvList(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
provider *Provider
|
|
||||||
keys []string
|
|
||||||
expected []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
provider: &Provider{
|
|
||||||
kvclient: &Mock{},
|
|
||||||
},
|
|
||||||
keys: []string{},
|
|
||||||
expected: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provider: &Provider{
|
|
||||||
kvclient: &Mock{},
|
|
||||||
},
|
|
||||||
keys: []string{"traefik"},
|
|
||||||
expected: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provider: &Provider{
|
|
||||||
kvclient: &Mock{
|
|
||||||
KVPairs: []*store.KVPair{
|
|
||||||
{
|
|
||||||
Key: "foo",
|
|
||||||
Value: []byte("bar"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
keys: []string{"bar"},
|
|
||||||
expected: []string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provider: &Provider{
|
|
||||||
kvclient: &Mock{
|
|
||||||
KVPairs: []*store.KVPair{
|
|
||||||
{
|
|
||||||
Key: "foo",
|
|
||||||
Value: []byte("bar"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
keys: []string{"foo"},
|
|
||||||
expected: []string{"foo"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provider: &Provider{
|
|
||||||
kvclient: &Mock{
|
|
||||||
KVPairs: []*store.KVPair{
|
|
||||||
{
|
|
||||||
Key: "foo/baz/1",
|
|
||||||
Value: []byte("bar"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "foo/baz/2",
|
|
||||||
Value: []byte("bar"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "foo/baz/biz/1",
|
|
||||||
Value: []byte("bar"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
keys: []string{"foo", "/baz/"},
|
|
||||||
expected: []string{"foo/baz/1", "foo/baz/2"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
actual := c.provider.list(c.keys...)
|
|
||||||
sort.Strings(actual)
|
|
||||||
sort.Strings(c.expected)
|
|
||||||
if !reflect.DeepEqual(actual, c.expected) {
|
|
||||||
t.Fatalf("expected %v, got %v for %v and %v", c.expected, actual, c.keys, c.provider)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error case
|
|
||||||
provider := &Provider{
|
|
||||||
kvclient: &Mock{
|
|
||||||
Error: KvError{
|
|
||||||
List: store.ErrKeyNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
actual := provider.list("anything")
|
|
||||||
if actual != nil {
|
|
||||||
t.Fatalf("Should have return nil, got %v", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKvGet(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
provider *Provider
|
|
||||||
keys []string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
provider: &Provider{
|
|
||||||
kvclient: &Mock{},
|
|
||||||
},
|
|
||||||
keys: []string{},
|
|
||||||
expected: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provider: &Provider{
|
|
||||||
kvclient: &Mock{},
|
|
||||||
},
|
|
||||||
keys: []string{"traefik"},
|
|
||||||
expected: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provider: &Provider{
|
|
||||||
kvclient: &Mock{
|
|
||||||
KVPairs: []*store.KVPair{
|
|
||||||
{
|
|
||||||
Key: "foo",
|
|
||||||
Value: []byte("bar"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
keys: []string{"bar"},
|
|
||||||
expected: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provider: &Provider{
|
|
||||||
kvclient: &Mock{
|
|
||||||
KVPairs: []*store.KVPair{
|
|
||||||
{
|
|
||||||
Key: "foo",
|
|
||||||
Value: []byte("bar"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
keys: []string{"foo"},
|
|
||||||
expected: "bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provider: &Provider{
|
|
||||||
kvclient: &Mock{
|
|
||||||
KVPairs: []*store.KVPair{
|
|
||||||
{
|
|
||||||
Key: "foo/baz/1",
|
|
||||||
Value: []byte("bar1"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "foo/baz/2",
|
|
||||||
Value: []byte("bar2"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "foo/baz/biz/1",
|
|
||||||
Value: []byte("bar3"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
keys: []string{"foo", "/baz/", "2"},
|
|
||||||
expected: "bar2",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
actual := c.provider.get("", c.keys...)
|
|
||||||
if actual != c.expected {
|
|
||||||
t.Fatalf("expected %v, got %v for %v and %v", c.expected, actual, c.keys, c.provider)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error case
|
|
||||||
provider := &Provider{
|
|
||||||
kvclient: &Mock{
|
|
||||||
Error: KvError{
|
|
||||||
Get: store.ErrKeyNotFound,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
actual := provider.get("", "anything")
|
|
||||||
if actual != "" {
|
|
||||||
t.Fatalf("Should have return nil, got %v", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKvLast(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
key string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
key: "",
|
|
||||||
expected: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "foo",
|
|
||||||
expected: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "foo/bar",
|
|
||||||
expected: "bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "foo/bar/baz",
|
|
||||||
expected: "baz",
|
|
||||||
},
|
|
||||||
// FIXME is this wanted ?
|
|
||||||
{
|
|
||||||
key: "foo/bar/",
|
|
||||||
expected: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
provider := &Provider{}
|
|
||||||
for _, c := range cases {
|
|
||||||
actual := provider.last(c.key)
|
|
||||||
if actual != c.expected {
|
|
||||||
t.Fatalf("expected %s, got %s", c.expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKvWatchTree(t *testing.T) {
|
func TestKvWatchTree(t *testing.T) {
|
||||||
returnedChans := make(chan chan []*store.KVPair)
|
returnedChans := make(chan chan []*store.KVPair)
|
||||||
provider := &KvMock{
|
provider := 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
|
||||||
return c
|
return c
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configChan := make(chan types.ConfigMessage)
|
configChan := make(chan types.ConfigMessage)
|
||||||
|
@ -277,146 +48,3 @@ func TestKvWatchTree(t *testing.T) {
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKVLoadConfig(t *testing.T) {
|
|
||||||
provider := &Provider{
|
|
||||||
Prefix: "traefik",
|
|
||||||
kvclient: &Mock{
|
|
||||||
KVPairs: []*store.KVPair{
|
|
||||||
{
|
|
||||||
Key: "traefik/frontends/frontend.with.dot",
|
|
||||||
Value: []byte(""),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "traefik/frontends/frontend.with.dot/backend",
|
|
||||||
Value: []byte("backend.with.dot.too"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "traefik/frontends/frontend.with.dot/routes",
|
|
||||||
Value: []byte(""),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "traefik/frontends/frontend.with.dot/routes/route.with.dot",
|
|
||||||
Value: []byte(""),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "traefik/frontends/frontend.with.dot/routes/route.with.dot/rule",
|
|
||||||
Value: []byte("Host:test.localhost"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "traefik/backends/backend.with.dot.too",
|
|
||||||
Value: []byte(""),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "traefik/backends/backend.with.dot.too/servers",
|
|
||||||
Value: []byte(""),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot",
|
|
||||||
Value: []byte(""),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot/url",
|
|
||||||
Value: []byte("http://172.17.0.2:80"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot/weight",
|
|
||||||
Value: []byte("0"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot.without.url",
|
|
||||||
Value: []byte(""),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot.without.url/weight",
|
|
||||||
Value: []byte("0"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
actual := provider.loadConfig()
|
|
||||||
expected := &types.Configuration{
|
|
||||||
Backends: map[string]*types.Backend{
|
|
||||||
"backend.with.dot.too": {
|
|
||||||
Servers: map[string]types.Server{
|
|
||||||
"server.with.dot": {
|
|
||||||
URL: "http://172.17.0.2:80",
|
|
||||||
Weight: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CircuitBreaker: nil,
|
|
||||||
LoadBalancer: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Frontends: map[string]*types.Frontend{
|
|
||||||
"frontend.with.dot": {
|
|
||||||
Backend: "backend.with.dot.too",
|
|
||||||
PassHostHeader: true,
|
|
||||||
EntryPoints: []string{},
|
|
||||||
Routes: map[string]types.Route{
|
|
||||||
"route.with.dot": {
|
|
||||||
Rule: "Host:test.localhost",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(actual.Backends, expected.Backends) {
|
|
||||||
t.Fatalf("expected %+v, got %+v", expected.Backends, actual.Backends)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(actual.Frontends, expected.Frontends) {
|
|
||||||
t.Fatalf("expected %+v, got %+v", expected.Frontends, actual.Frontends)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKVHasStickinessLabel(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
KVPairs []*store.KVPair
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "without option",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "with cookie name without stickiness=true",
|
|
||||||
KVPairs: []*store.KVPair{
|
|
||||||
{
|
|
||||||
Key: "/loadbalancer/stickiness/cookiename",
|
|
||||||
Value: []byte("foo"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "stickiness=true",
|
|
||||||
KVPairs: []*store.KVPair{
|
|
||||||
{
|
|
||||||
Key: "/loadbalancer/stickiness",
|
|
||||||
Value: []byte("true"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range testCases {
|
|
||||||
test := test
|
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
p := &Provider{
|
|
||||||
kvclient: &Mock{
|
|
||||||
KVPairs: test.KVPairs,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
actual := p.hasStickinessLabel("")
|
|
||||||
|
|
||||||
if actual != test.expected {
|
|
||||||
t.Fatalf("expected %v, got %v", test.expected, actual)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue