emile 2015-10-01 12:04:25 +02:00
parent cdcd5a2b68
commit 93b5410987
20 changed files with 346 additions and 248 deletions

21
Godeps/Godeps.json generated
View file

@ -26,6 +26,10 @@
"ImportPath": "github.com/alecthomas/units", "ImportPath": "github.com/alecthomas/units",
"Rev": "6b4e7dc5e3143b85ea77909c72caf89416fc2915" "Rev": "6b4e7dc5e3143b85ea77909c72caf89416fc2915"
}, },
{
"ImportPath": "github.com/boltdb/bolt",
"Rev": "51f99c862475898df9773747d3accd05a7ca33c1"
},
{ {
"ImportPath": "github.com/cenkalti/backoff", "ImportPath": "github.com/cenkalti/backoff",
"Rev": "4dc77674aceaabba2c7e3da25d4c823edfb73f99" "Rev": "4dc77674aceaabba2c7e3da25d4c823edfb73f99"
@ -39,6 +43,19 @@
"Comment": "v0.1-70-gc7477ad", "Comment": "v0.1-70-gc7477ad",
"Rev": "c7477ad8e330bef55bf1ebe300cf8aa67c492d1b" "Rev": "c7477ad8e330bef55bf1ebe300cf8aa67c492d1b"
}, },
{
"ImportPath": "github.com/coreos/go-etcd/etcd",
"Comment": "v2.0.0-11-gcc90c7b",
"Rev": "cc90c7b091275e606ad0ca7102a23fb2072f3f5e"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "2df174808ee097f90d259e432cc04442cf60be21"
},
{
"ImportPath": "github.com/docker/libkv",
"Rev": "3732f7ff1b56057c3158f10bceb1e79133025373"
},
{ {
"ImportPath": "github.com/docker/distribution", "ImportPath": "github.com/docker/distribution",
"Comment": "v2.0.0-467-g9038e48", "Comment": "v2.0.0-467-g9038e48",
@ -313,6 +330,10 @@
"ImportPath": "github.com/mailgun/timetools", "ImportPath": "github.com/mailgun/timetools",
"Rev": "fd192d755b00c968d312d23f521eb0cdc6f66bd0" "Rev": "fd192d755b00c968d312d23f521eb0cdc6f66bd0"
}, },
{
"ImportPath": "github.com/samuel/go-zookeeper/zk",
"Rev": "fa6674abf3f4580b946a01bf7a1ce4ba8766205b"
},
{ {
"ImportPath": "github.com/opencontainers/runc/libcontainer/user", "ImportPath": "github.com/opencontainers/runc/libcontainer/user",
"Comment": "v0.0.4-21-g4ab1324", "Comment": "v0.0.4-21-g4ab1324",

View file

@ -47,6 +47,9 @@ validate-govet: build
build: dist build: dist
docker build -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile . docker build -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
build-no-cache: dist
docker build --no-cache -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
shell: build shell: build
$(DOCKER_RUN_TRAEFIK) /bin/bash $(DOCKER_RUN_TRAEFIK) /bin/bash

14
boltdb.go Normal file
View file

@ -0,0 +1,14 @@
package main
type BoltDbProvider struct {
Watch bool
Endpoint string
Prefix string
Filename string
KvProvider *KvProvider
}
func (provider *BoltDbProvider) Provide(configurationChan chan<- configMessage) error {
provider.KvProvider = NewBoltDbProvider(provider)
return provider.KvProvider.provide(configurationChan)
}

View file

@ -10,7 +10,6 @@ type GlobalConfiguration struct {
GraceTimeOut int64 GraceTimeOut int64
AccessLogsFile string AccessLogsFile string
TraefikLogsFile string TraefikLogsFile string
TraefikLogsStdout bool
CertFile, KeyFile string CertFile, KeyFile string
LogLevel string LogLevel string
Docker *DockerProvider Docker *DockerProvider
@ -18,6 +17,9 @@ type GlobalConfiguration struct {
Web *WebProvider Web *WebProvider
Marathon *MarathonProvider Marathon *MarathonProvider
Consul *ConsulProvider Consul *ConsulProvider
Etcd *EtcdProvider
Zookeeper *ZookepperProvider
Boltdb *BoltDbProvider
} }
func NewGlobalConfiguration() *GlobalConfiguration { func NewGlobalConfiguration() *GlobalConfiguration {
@ -26,7 +28,6 @@ func NewGlobalConfiguration() *GlobalConfiguration {
globalConfiguration.Port = ":80" globalConfiguration.Port = ":80"
globalConfiguration.GraceTimeOut = 10 globalConfiguration.GraceTimeOut = 10
globalConfiguration.LogLevel = "ERROR" globalConfiguration.LogLevel = "ERROR"
globalConfiguration.TraefikLogsStdout = true
return globalConfiguration return globalConfiguration
} }

167
consul.go
View file

@ -1,165 +1,14 @@
package main package main
import (
"bytes"
"net/http"
"strings"
"text/template"
"github.com/BurntSushi/toml"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/hashicorp/consul/api"
)
type Key struct {
Value string
}
type ConsulProvider struct { type ConsulProvider struct {
Watch bool Watch bool
Endpoint string Endpoint string
Prefix string Prefix string
Filename string Filename string
consulClient *api.Client KvProvider *KvProvider
} }
var kvClient *api.KV func (provider *ConsulProvider) Provide(configurationChan chan<- configMessage) error {
provider.KvProvider = NewConsulProvider(provider)
var ConsulFuncMap = template.FuncMap{ return provider.KvProvider.provide(configurationChan)
"List": func(keys ...string) []string {
joinedKeys := strings.Join(keys, "")
keysPairs, _, err := kvClient.Keys(joinedKeys, "/", nil)
if err != nil {
log.Error("Error getting keys ", joinedKeys, err)
return nil
}
keysPairs = fun.Filter(func(key string) bool {
if key == joinedKeys {
return false
}
return true
}, keysPairs).([]string)
return keysPairs
},
"Get": func(keys ...string) string {
joinedKeys := strings.Join(keys, "")
keyPair, _, err := kvClient.Get(joinedKeys, nil)
if err != nil {
log.Error("Error getting key ", joinedKeys, err)
return ""
} else if keyPair == nil {
return ""
}
return string(keyPair.Value)
},
"Last": func(key string) string {
splittedKey := strings.Split(key, "/")
return splittedKey[len(splittedKey)-2]
},
}
func NewConsulProvider() *ConsulProvider {
consulProvider := new(ConsulProvider)
// default values
consulProvider.Watch = true
consulProvider.Prefix = "traefik"
return consulProvider
}
func (provider *ConsulProvider) Provide(configurationChan chan<- configMessage) {
config := &api.Config{
Address: provider.Endpoint,
Scheme: "http",
HttpClient: http.DefaultClient,
}
consulClient, _ := api.NewClient(config)
provider.consulClient = consulClient
if provider.Watch {
keypairs, meta, err := consulClient.KV().Keys("", "", nil)
if keypairs == nil {
log.Error("Key was not found")
} else if err != nil {
log.Error("Error connecting to consul %s", err)
} else {
var waitIndex uint64
waitIndex = meta.LastIndex
go func() {
for {
opts := api.QueryOptions{
WaitIndex: waitIndex,
}
keypairs, meta, err := consulClient.KV().Keys("", "", &opts)
if keypairs == nil {
log.Error("Key was not found")
} else if err != nil {
log.Error("Error connecting to consul %s", err)
} else {
waitIndex = meta.LastIndex
configuration := provider.loadConsulConfig()
if configuration != nil {
configurationChan <- configMessage{"consul", configuration}
}
}
}
}()
}
}
configuration := provider.loadConsulConfig()
configurationChan <- configMessage{"consul", configuration}
}
func (provider *ConsulProvider) loadConsulConfig() *Configuration {
configuration := new(Configuration)
services := []*api.CatalogService{}
kvClient = provider.consulClient.KV()
servicesName, _, _ := provider.consulClient.Catalog().Services(nil)
for serviceName := range servicesName {
catalogServices, _, _ := provider.consulClient.Catalog().Service(serviceName, "", nil)
for _, catalogService := range catalogServices {
services = append(services, catalogService)
}
}
templateObjects := struct {
Services []*api.CatalogService
}{
services,
}
tmpl := template.New(provider.Filename).Funcs(ConsulFuncMap)
if len(provider.Filename) > 0 {
_, err := tmpl.ParseFiles(provider.Filename)
if err != nil {
log.Error("Error reading file", err)
return nil
}
} else {
buf, err := Asset("providerTemplates/consul.tmpl")
if err != nil {
log.Error("Error reading file", err)
}
_, err = tmpl.Parse(string(buf))
if err != nil {
log.Error("Error reading file", err)
return nil
}
}
var buffer bytes.Buffer
err := tmpl.Execute(&buffer, templateObjects)
if err != nil {
log.Error("Error with consul template:", err)
return nil
}
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
log.Error("Error creating consul configuration:", err)
return nil
}
return configuration
} }

View file

@ -22,15 +22,6 @@ type DockerProvider struct {
Domain string Domain string
} }
func NewDockerProvider() *DockerProvider {
dockerProvider := new(DockerProvider)
// default
dockerProvider.Watch = true
dockerProvider.Domain = "traefik"
return dockerProvider
}
var DockerFuncMap = template.FuncMap{ var DockerFuncMap = template.FuncMap{
"getBackend": func(container docker.Container) string { "getBackend": func(container docker.Container) string {
for key, value := range container.Config.Labels { for key, value := range container.Config.Labels {
@ -65,13 +56,15 @@ var DockerFuncMap = template.FuncMap{
"getHost": getHost, "getHost": getHost,
} }
func (provider *DockerProvider) Provide(configurationChan chan<- configMessage) { func (provider *DockerProvider) Provide(configurationChan chan<- configMessage) error {
if dockerClient, err := docker.NewClient(provider.Endpoint); err != nil { if dockerClient, err := docker.NewClient(provider.Endpoint); err != nil {
log.Fatalf("Failed to create a client for docker, error: %s", err) log.Errorf("Failed to create a client for docker, error: %s", err)
return err
} else { } else {
err := dockerClient.Ping() err := dockerClient.Ping()
if err != nil { if err != nil {
log.Fatalf("Docker connection error %+v", err) log.Errorf("Docker connection error %+v", err)
return err
} }
log.Debug("Docker connection established") log.Debug("Docker connection established")
if provider.Watch { if provider.Watch {
@ -108,6 +101,7 @@ func (provider *DockerProvider) Provide(configurationChan chan<- configMessage)
configuration := provider.loadDockerConfig(dockerClient) configuration := provider.loadDockerConfig(dockerClient)
configurationChan <- configMessage{"docker", configuration} configurationChan <- configMessage{"docker", configuration}
} }
return nil
} }
func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *Configuration { func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *Configuration {

14
etcd.go Normal file
View file

@ -0,0 +1,14 @@
package main
type EtcdProvider struct {
Watch bool
Endpoint string
Prefix string
Filename string
KvProvider *KvProvider
}
func (provider *EtcdProvider) Provide(configurationChan chan<- configMessage) error {
provider.KvProvider = NewEtcdProvider(provider)
return provider.KvProvider.provide(configurationChan)
}

19
file.go
View file

@ -15,30 +15,21 @@ type FileProvider struct {
Filename string Filename string
} }
func NewFileProvider() *FileProvider { func (provider *FileProvider) Provide(configurationChan chan<- configMessage) error {
fileProvider := new(FileProvider)
// default values
fileProvider.Watch = true
return fileProvider
}
func (provider *FileProvider) Provide(configurationChan chan<- configMessage) {
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
log.Error("Error creating file watcher", err) log.Error("Error creating file watcher", err)
return return err
} }
defer watcher.Close() defer watcher.Close()
file, err := os.Open(provider.Filename) file, err := os.Open(provider.Filename)
if err != nil { if err != nil {
log.Error("Error opening file", err) log.Error("Error opening file", err)
return return err
} }
defer file.Close() defer file.Close()
done := make(chan bool)
// Process events // Process events
go func() { go func() {
for { for {
@ -63,12 +54,12 @@ func (provider *FileProvider) Provide(configurationChan chan<- configMessage) {
if err != nil { if err != nil {
log.Error("Error adding file watcher", err) log.Error("Error adding file watcher", err)
return return err
} }
configuration := provider.LoadFileConfig(file.Name()) configuration := provider.LoadFileConfig(file.Name())
configurationChan <- configMessage{"file", configuration} configurationChan <- configMessage{"file", configuration}
<-done return nil
} }
func (provider *FileProvider) LoadFileConfig(filename string) *Configuration { func (provider *FileProvider) LoadFileConfig(filename string) *Configuration {

View file

@ -15,14 +15,14 @@ func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) {
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
c.Assert(err, checker.NotNil) c.Assert(err, checker.NotNil)
c.Assert(string(output), checker.Contains, "Error reading file open traefik.toml: no such file or directory") c.Assert(string(output), checker.Contains, "Error reading file: open traefik.toml: no such file or directory")
nonExistentFile := "non/existent/file.toml" nonExistentFile := "non/existent/file.toml"
cmd = exec.Command(traefikBinary, nonExistentFile) cmd = exec.Command(traefikBinary, nonExistentFile)
output, err = cmd.CombinedOutput() output, err = cmd.CombinedOutput()
c.Assert(err, checker.NotNil) c.Assert(err, checker.NotNil)
c.Assert(string(output), checker.Contains, fmt.Sprintf("Error reading file open %s: no such file or directory", nonExistentFile)) c.Assert(string(output), checker.Contains, fmt.Sprintf("Error reading file: open %s: no such file or directory", nonExistentFile))
} }
func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) { func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
@ -30,7 +30,7 @@ func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
c.Assert(err, checker.NotNil) c.Assert(err, checker.NotNil)
c.Assert(string(output), checker.Contains, "Error reading file Near line 1") c.Assert(string(output), checker.Contains, "Error reading file: Near line 1")
} }
func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) { func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {

View file

@ -10,12 +10,4 @@ consul:
- "8301" - "8301"
- "8301/udp" - "8301/udp"
- "8302" - "8302"
- "8302/udp" - "8302/udp"
registrator:
image: gliderlabs/registrator:master
command: -internal consulkv://consul:8500/traefik
volumes:
- /var/run/docker.sock:/tmp/docker.sock
links:
- consul

195
kv.go Normal file
View file

@ -0,0 +1,195 @@
/*
Copyright
*/
package main
import (
"bytes"
"github.com/docker/libkv"
"github.com/docker/libkv/store/boltdb"
"github.com/docker/libkv/store/consul"
"github.com/docker/libkv/store/etcd"
"github.com/docker/libkv/store/zookeeper"
"strings"
"text/template"
"errors"
"github.com/BurntSushi/toml"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/docker/libkv/store"
"time"
)
type KvProvider struct {
Watch bool
Endpoint string
Prefix string
Filename string
StoreType store.Backend
kvclient store.Store
}
func NewConsulProvider(provider *ConsulProvider) *KvProvider {
kvProvider := new(KvProvider)
kvProvider.Watch = provider.Watch
kvProvider.Endpoint = provider.Endpoint
kvProvider.Prefix = provider.Prefix
kvProvider.Filename = provider.Filename
kvProvider.StoreType = store.CONSUL
return kvProvider
}
func NewEtcdProvider(provider *EtcdProvider) *KvProvider {
kvProvider := new(KvProvider)
kvProvider.Watch = provider.Watch
kvProvider.Endpoint = provider.Endpoint
kvProvider.Prefix = provider.Prefix
kvProvider.Filename = provider.Filename
kvProvider.StoreType = store.ETCD
return kvProvider
}
func NewZkProvider(provider *ZookepperProvider) *KvProvider {
kvProvider := new(KvProvider)
kvProvider.Watch = provider.Watch
kvProvider.Endpoint = provider.Endpoint
kvProvider.Prefix = provider.Prefix
kvProvider.Filename = provider.Filename
kvProvider.StoreType = store.ZK
return kvProvider
}
func NewBoltDbProvider(provider *BoltDbProvider) *KvProvider {
kvProvider := new(KvProvider)
kvProvider.Watch = provider.Watch
kvProvider.Endpoint = provider.Endpoint
kvProvider.Prefix = provider.Prefix
kvProvider.Filename = provider.Filename
kvProvider.StoreType = store.BOLTDB
return kvProvider
}
func (provider *KvProvider) provide(configurationChan chan<- configMessage) error {
switch provider.StoreType {
case store.CONSUL:
consul.Register()
case store.ETCD:
etcd.Register()
case store.ZK:
zookeeper.Register()
case store.BOLTDB:
boltdb.Register()
default:
return errors.New("Invalid kv store: " + string(provider.StoreType))
}
kv, err := libkv.NewStore(
provider.StoreType,
[]string{provider.Endpoint},
&store.Config{
ConnectionTimeout: 30 * time.Second,
},
)
if err != nil {
return err
}
if _, err := kv.List(""); err != nil {
return err
}
provider.kvclient = kv
if provider.Watch {
stopCh := make(chan struct{})
chanKeys, err := kv.WatchTree(provider.Prefix, stopCh)
if err != nil {
return err
}
go func() {
for {
<-chanKeys
configuration := provider.loadConfig()
if configuration != nil {
configurationChan <- configMessage{string(provider.StoreType), configuration}
}
defer close(stopCh)
}
}()
}
configuration := provider.loadConfig()
configurationChan <- configMessage{string(provider.StoreType), configuration}
return nil
}
func (provider *KvProvider) loadConfig() *Configuration {
configuration := new(Configuration)
templateObjects := struct {
Prefix string
}{
provider.Prefix,
}
var KvFuncMap = template.FuncMap{
"List": func(keys ...string) []string {
joinedKeys := strings.Join(keys, "")
keysPairs, err := provider.kvclient.List(joinedKeys)
if err != nil {
log.Error("Error getting keys: ", joinedKeys, err)
return nil
}
directoryKeys := make(map[string]string)
for _, key := range keysPairs {
directory := strings.Split(strings.TrimPrefix(key.Key, strings.TrimPrefix(joinedKeys, "/")), "/")[0]
directoryKeys[directory] = joinedKeys + directory
}
return fun.Values(directoryKeys).([]string)
},
"Get": func(keys ...string) string {
joinedKeys := strings.Join(keys, "")
keyPair, err := provider.kvclient.Get(joinedKeys)
if err != nil {
log.Debug("Error getting key: ", joinedKeys, err)
return ""
} else if keyPair == nil {
return ""
}
return string(keyPair.Value)
},
"Last": func(key string) string {
splittedKey := strings.Split(key, "/")
return splittedKey[len(splittedKey)-1]
},
}
tmpl := template.New(provider.Filename).Funcs(KvFuncMap)
if len(provider.Filename) > 0 {
_, err := tmpl.ParseFiles(provider.Filename)
if err != nil {
log.Error("Error reading file", err)
return nil
}
} else {
buf, err := Asset("providerTemplates/kv.tmpl")
if err != nil {
log.Error("Error reading file", err)
}
_, err = tmpl.Parse(string(buf))
if err != nil {
log.Error("Error reading file", err)
return nil
}
}
var buffer bytes.Buffer
err := tmpl.Execute(&buffer, templateObjects)
if err != nil {
log.Error("Error with kv template:", err)
return nil
}
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
log.Error("Error creating kv configuration:", err)
log.Error(buffer.String())
return nil
}
return configuration
}

View file

@ -21,16 +21,6 @@ type MarathonProvider struct {
NetworkInterface string NetworkInterface string
} }
func NewMarathonProvider() *MarathonProvider {
marathonProvider := new(MarathonProvider)
// default values
marathonProvider.Watch = true
marathonProvider.Domain = "traefik"
marathonProvider.NetworkInterface = "eth0"
return marathonProvider
}
var MarathonFuncMap = template.FuncMap{ var MarathonFuncMap = template.FuncMap{
"getPort": func(task marathon.Task) string { "getPort": func(task marathon.Task) string {
for _, port := range task.Ports { for _, port := range task.Ports {
@ -67,14 +57,14 @@ var MarathonFuncMap = template.FuncMap{
}, },
} }
func (provider *MarathonProvider) Provide(configurationChan chan<- configMessage) { func (provider *MarathonProvider) Provide(configurationChan chan<- configMessage) error {
config := marathon.NewDefaultConfig() config := marathon.NewDefaultConfig()
config.URL = provider.Endpoint config.URL = provider.Endpoint
config.EventsInterface = provider.NetworkInterface config.EventsInterface = provider.NetworkInterface
client, err := marathon.NewClient(config) client, err := marathon.NewClient(config)
if err != nil { if err != nil {
log.Errorf("Failed to create a client for marathon, error: %s", err) log.Errorf("Failed to create a client for marathon, error: %s", err)
return return err
} }
provider.marathonClient = client provider.marathonClient = client
update := make(marathon.EventsChannel, 5) update := make(marathon.EventsChannel, 5)
@ -97,6 +87,7 @@ func (provider *MarathonProvider) Provide(configurationChan chan<- configMessage
configuration := provider.loadMarathonConfig() configuration := provider.loadMarathonConfig()
configurationChan <- configMessage{"marathon", configuration} configurationChan <- configMessage{"marathon", configuration}
return nil
} }
func (provider *MarathonProvider) loadMarathonConfig() *Configuration { func (provider *MarathonProvider) loadMarathonConfig() *Configuration {

View file

@ -1,5 +1,5 @@
package main package main
type Provider interface { type Provider interface {
Provide(configurationChan chan<- configMessage) Provide(configurationChan chan<- configMessage) error
} }

View file

@ -1,17 +1,17 @@
{{$frontends := "frontends/" | List }} {{$frontends := List .Prefix "/frontends/" }}
{{$backends := "backends/" | List }} {{$backends := List .Prefix "/backends/"}}
{{range $backends}} {{range $backends}}
{{$backend := .}} {{$backend := .}}
{{$servers := "servers/" | List $backend }} {{$servers := List $backend "/servers/" }}
{{$circuitBreaker := Get . "circuitbreaker/" "expression"}} {{$circuitBreaker := Get . "/circuitbreaker/" "expression"}}
{{with $circuitBreaker}} {{with $circuitBreaker}}
[backends.{{Last $backend}}.circuitBreaker] [backends.{{Last $backend}}.circuitBreaker]
expression = "{{$circuitBreaker}}" expression = "{{$circuitBreaker}}"
{{end}} {{end}}
{{$loadBalancer := Get . "loadbalancer/" "method"}} {{$loadBalancer := Get . "/loadbalancer/" "method"}}
{{with $loadBalancer}} {{with $loadBalancer}}
[backends.{{Last $backend}}.loadBalancer] [backends.{{Last $backend}}.loadBalancer]
method = "{{$loadBalancer}}" method = "{{$loadBalancer}}"
@ -28,7 +28,7 @@
{{$frontend := Last .}} {{$frontend := Last .}}
[frontends.{{$frontend}}] [frontends.{{$frontend}}]
backend = "{{Get . "/backend"}}" backend = "{{Get . "/backend"}}"
{{$routes := "routes/" | List .}} {{$routes := List . "/routes/"}}
{{range $routes}} {{range $routes}}
[frontends.{{$frontend}}.routes.{{Last .}}] [frontends.{{$frontend}}.routes.{{Last .}}]
rule = "{{Get . "/rule"}}" rule = "{{Get . "/rule"}}"

View file

@ -61,7 +61,7 @@
{{range $keyProviders, $valueProviders := .Configurations}} {{range $keyProviders, $valueProviders := .Configurations}}
{{range $keyBackends, $valueBackends := $valueProviders.Backends}} {{range $keyBackends, $valueBackends := $valueProviders.Backends}}
<div class="panel panel-primary" id="{{$keyBackends}}"> <div class="panel panel-primary" id="{{$keyBackends}}">
<div class="panel-heading">{{$keyBackends}}({{$keyProviders}})</div> <div class="panel-heading">{{$keyBackends}} - ({{$keyProviders}})</div>
<div class="panel-body"> <div class="panel-body">
{{with $valueBackends.LoadBalancer}} {{with $valueBackends.LoadBalancer}}
<a class="btn btn-info" role="button"> <a class="btn btn-info" role="button">

View file

@ -1,25 +1,25 @@
#!/bin/sh #!/bin/sh
# backend 1 # backend 1
curl -i -H "Accept: application/json" -X PUT -d "NetworkErrorRatio() > 0.5" http://localhost:8500/v1/kv/backends/backend1/circuitbreaker/expression curl -i -H "Accept: application/json" -X PUT -d "NetworkErrorRatio() > 0.5" http://localhost:8500/v1/kv/traefik/backends/backend1/circuitbreaker/expression
curl -i -H "Accept: application/json" -X PUT -d "http://172.17.0.2:80" http://localhost:8500/v1/kv/backends/backend1/servers/server1/url curl -i -H "Accept: application/json" -X PUT -d "http://172.17.0.2:80" http://localhost:8500/v1/kv/traefik/backends/backend1/servers/server1/url
curl -i -H "Accept: application/json" -X PUT -d "10" http://localhost:8500/v1/kv/backends/backend1/servers/server1/weight curl -i -H "Accept: application/json" -X PUT -d "10" http://localhost:8500/v1/kv/traefik/backends/backend1/servers/server1/weight
curl -i -H "Accept: application/json" -X PUT -d "http://172.17.0.3:80" http://localhost:8500/v1/kv/backends/backend1/servers/server2/url curl -i -H "Accept: application/json" -X PUT -d "http://172.17.0.3:80" http://localhost:8500/v1/kv/traefik/backends/backend1/servers/server2/url
curl -i -H "Accept: application/json" -X PUT -d "1" http://localhost:8500/v1/kv/backends/backend1/servers/server2/weight curl -i -H "Accept: application/json" -X PUT -d "1" http://localhost:8500/v1/kv/traefik/backends/backend1/servers/server2/weight
# backend 2 # backend 2
curl -i -H "Accept: application/json" -X PUT -d "drr" http://localhost:8500/v1/kv/backends/backend2/loadbalancer/method curl -i -H "Accept: application/json" -X PUT -d "drr" http://localhost:8500/v1/kv/traefik/backends/backend2/loadbalancer/method
curl -i -H "Accept: application/json" -X PUT -d "http://172.17.0.4:80" http://localhost:8500/v1/kv/backends/backend2/servers/server1/url curl -i -H "Accept: application/json" -X PUT -d "http://172.17.0.4:80" http://localhost:8500/v1/kv/traefik/backends/backend2/servers/server1/url
curl -i -H "Accept: application/json" -X PUT -d "1" http://localhost:8500/v1/kv/backends/backend2/servers/server1/weight curl -i -H "Accept: application/json" -X PUT -d "1" http://localhost:8500/v1/kv/traefik/backends/backend2/servers/server1/weight
curl -i -H "Accept: application/json" -X PUT -d "http://172.17.0.5:80" http://localhost:8500/v1/kv/backends/backend2/servers/server2/url curl -i -H "Accept: application/json" -X PUT -d "http://172.17.0.5:80" http://localhost:8500/v1/kv/traefik/backends/backend2/servers/server2/url
curl -i -H "Accept: application/json" -X PUT -d "2" http://localhost:8500/v1/kv/backends/backend2/servers/server2/weight curl -i -H "Accept: application/json" -X PUT -d "2" http://localhost:8500/v1/kv/traefik/backends/backend2/servers/server2/weight
# frontend 1 # frontend 1
curl -i -H "Accept: application/json" -X PUT -d "backend2" http://localhost:8500/v1/kv/frontends/frontend1/backend curl -i -H "Accept: application/json" -X PUT -d "backend2" http://localhost:8500/v1/kv/traefik/frontends/frontend1/backend
curl -i -H "Accept: application/json" -X PUT -d "Host" http://localhost:8500/v1/kv/frontends/frontend1/routes/test_1/rule curl -i -H "Accept: application/json" -X PUT -d "Host" http://localhost:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/rule
curl -i -H "Accept: application/json" -X PUT -d "test.localhost" http://localhost:8500/v1/kv/frontends/frontend1/routes/test_1/value curl -i -H "Accept: application/json" -X PUT -d "test.localhost" http://localhost:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/value
# frontend 2 # frontend 2
curl -i -H "Accept: application/json" -X PUT -d "backend1" http://localhost:8500/v1/kv/frontends/frontend2/backend curl -i -H "Accept: application/json" -X PUT -d "backend1" http://localhost:8500/v1/kv/traefik/frontends/frontend2/backend
curl -i -H "Accept: application/json" -X PUT -d "Path" http://localhost:8500/v1/kv/frontends/frontend2/routes/test_2/rule curl -i -H "Accept: application/json" -X PUT -d "Path" http://localhost:8500/v1/kv/traefik/frontends/frontend2/routes/test_2/rule
curl -i -H "Accept: application/json" -X PUT -d "/test" http://localhost:8500/v1/kv/frontends/frontend2/routes/test_2/value curl -i -H "Accept: application/json" -X PUT -d "/test" http://localhost:8500/v1/kv/traefik/frontends/frontend2/routes/test_2/value

View file

@ -12,9 +12,11 @@ import (
"syscall" "syscall"
"time" "time"
"errors"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/codegangsta/negroni" "github.com/codegangsta/negroni"
"github.com/davecgh/go-spew/spew"
"github.com/emilevauge/traefik/middlewares" "github.com/emilevauge/traefik/middlewares"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mailgun/manners" "github.com/mailgun/manners"
@ -86,7 +88,7 @@ func main() {
} else { } else {
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true}) log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true})
} }
log.Debugf("Global configuration loaded %s", spew.Sdump(globalConfiguration))
configurationRouter = LoadDefaultConfig(globalConfiguration) configurationRouter = LoadDefaultConfig(globalConfiguration)
// listen new configurations from providers // listen new configurations from providers
@ -94,7 +96,8 @@ func main() {
for { for {
configMsg := <-configurationChan configMsg := <-configurationChan
log.Infof("Configuration receveived from provider %v: %+v", configMsg.providerName, configMsg.configuration) log.Infof("Configuration receveived from provider %s: %#v", configMsg.providerName, configMsg.configuration)
log.Debugf("Configuration %s", spew.Sdump(configMsg.configuration))
if configMsg.configuration == nil { if configMsg.configuration == nil {
log.Info("Skipping empty configuration") log.Info("Skipping empty configuration")
} else if reflect.DeepEqual(currentConfigurations[configMsg.providerName], configMsg.configuration) { } else if reflect.DeepEqual(currentConfigurations[configMsg.providerName], configMsg.configuration) {
@ -147,13 +150,25 @@ func main() {
if globalConfiguration.Consul != nil { if globalConfiguration.Consul != nil {
providers = append(providers, globalConfiguration.Consul) providers = append(providers, globalConfiguration.Consul)
} }
if globalConfiguration.Etcd != nil {
providers = append(providers, globalConfiguration.Etcd)
}
if globalConfiguration.Zookeeper != nil {
providers = append(providers, globalConfiguration.Zookeeper)
}
if globalConfiguration.Boltdb != nil {
providers = append(providers, globalConfiguration.Boltdb)
}
// start providers // start providers
for _, provider := range providers { for _, provider := range providers {
log.Infof("Starting provider %v %+v", reflect.TypeOf(provider), provider) log.Infof("Starting provider %v %+v", reflect.TypeOf(provider), provider)
currentProvider := provider currentProvider := provider
go func() { go func() {
currentProvider.Provide(configurationChan) err := currentProvider.Provide(configurationChan)
if err != nil {
log.Errorf("Error starting provider %s", err)
}
}() }()
} }
@ -176,6 +191,7 @@ func main() {
func startServer(srv *manners.GracefulServer, globalConfiguration *GlobalConfiguration) { func startServer(srv *manners.GracefulServer, globalConfiguration *GlobalConfiguration) {
log.Info("Starting server") log.Info("Starting server")
log.Debugf("Server %s", spew.Sdump(srv))
if len(globalConfiguration.CertFile) > 0 && len(globalConfiguration.KeyFile) > 0 { if len(globalConfiguration.CertFile) > 0 && len(globalConfiguration.KeyFile) > 0 {
err := srv.ListenAndServeTLS(globalConfiguration.CertFile, globalConfiguration.KeyFile) err := srv.ListenAndServeTLS(globalConfiguration.CertFile, globalConfiguration.KeyFile)
if err != nil { if err != nil {
@ -243,13 +259,16 @@ func LoadConfig(configurations configs, globalConfiguration *GlobalConfiguration
log.Debugf("Creating backend %s", frontend.Backend) log.Debugf("Creating backend %s", frontend.Backend)
var lb http.Handler var lb http.Handler
rr, _ := roundrobin.New(fwd) rr, _ := roundrobin.New(fwd)
if configuration.Backends[frontend.Backend] == nil {
return nil, errors.New("Backend not found: " + frontend.Backend)
}
lbMethod, err := NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer) lbMethod, err := NewLoadBalancerMethod(configuration.Backends[frontend.Backend].LoadBalancer)
if err != nil { if err != nil {
configuration.Backends[frontend.Backend].LoadBalancer = &LoadBalancer{Method: "wrr"} configuration.Backends[frontend.Backend].LoadBalancer = &LoadBalancer{Method: "wrr"}
} }
switch lbMethod { switch lbMethod {
case drr: case drr:
log.Debugf("Creating load-balancer drr") log.Infof("Creating load-balancer drr")
rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger)) rebalancer, _ := roundrobin.NewRebalancer(rr, roundrobin.RebalancerLogger(oxyLogger))
lb = rebalancer lb = rebalancer
for serverName, server := range configuration.Backends[frontend.Backend].Servers { for serverName, server := range configuration.Backends[frontend.Backend].Servers {
@ -257,31 +276,31 @@ func LoadConfig(configurations configs, globalConfiguration *GlobalConfiguration
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debugf("Creating server %s %s", serverName, url.String()) log.Infof("Creating server %s %s", serverName, url.String())
rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight)) rebalancer.UpsertServer(url, roundrobin.Weight(server.Weight))
} }
case wrr: case wrr:
log.Debugf("Creating load-balancer wrr") log.Infof("Creating load-balancer wrr")
lb = rr lb = rr
for serverName, server := range configuration.Backends[frontend.Backend].Servers { for serverName, server := range configuration.Backends[frontend.Backend].Servers {
url, err := url.Parse(server.URL) url, err := url.Parse(server.URL)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debugf("Creating server %s %s", serverName, url.String()) log.Infof("Creating server %s %s", serverName, url.String())
rr.UpsertServer(url, roundrobin.Weight(server.Weight)) rr.UpsertServer(url, roundrobin.Weight(server.Weight))
} }
} }
var negroni = negroni.New() var negroni = negroni.New()
if configuration.Backends[frontend.Backend].CircuitBreaker != nil { if configuration.Backends[frontend.Backend].CircuitBreaker != nil {
log.Debugf("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression) log.Infof("Creating circuit breaker %s", configuration.Backends[frontend.Backend].CircuitBreaker.Expression)
negroni.Use(middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger))) negroni.Use(middlewares.NewCircuitBreaker(lb, configuration.Backends[frontend.Backend].CircuitBreaker.Expression, cbreaker.Logger(oxyLogger)))
} else { } else {
negroni.UseHandler(lb) negroni.UseHandler(lb)
} }
backends[frontend.Backend] = negroni backends[frontend.Backend] = negroni
} else { } else {
log.Debugf("Reusing backend %s", frontend.Backend) log.Infof("Reusing backend %s", frontend.Backend)
} }
// stream.New(backends[frontend.Backend], stream.Retry("IsNetworkError() && Attempts() <= " + strconv.Itoa(globalConfiguration.Replay)), stream.Logger(oxyLogger)) // stream.New(backends[frontend.Backend], stream.Retry("IsNetworkError() && Attempts() <= " + strconv.Itoa(globalConfiguration.Replay)), stream.Logger(oxyLogger))
@ -306,8 +325,7 @@ func Invoke(any interface{}, name string, args ...interface{}) []reflect.Value {
func LoadFileConfig(file string) *GlobalConfiguration { func LoadFileConfig(file string) *GlobalConfiguration {
configuration := NewGlobalConfiguration() configuration := NewGlobalConfiguration()
if _, err := toml.DecodeFile(file, configuration); err != nil { if _, err := toml.DecodeFile(file, configuration); err != nil {
log.Fatal("Error reading file ", err) fmtlog.Fatalf("Error reading file: %s", err)
} }
log.Debugf("Global configuration loaded %+v", configuration)
return configuration return configuration
} }

View file

@ -199,7 +199,7 @@
# #
# Optional # Optional
# #
# prefix = "traefik" # prefix = "/traefik"
# Override default configuration template. For advanced users :) # Override default configuration template. For advanced users :)
# #

3
web.go
View file

@ -20,7 +20,7 @@ type Page struct {
Configurations configs Configurations configs
} }
func (provider *WebProvider) Provide(configurationChan chan<- configMessage) { func (provider *WebProvider) Provide(configurationChan chan<- configMessage) error {
systemRouter := mux.NewRouter() systemRouter := mux.NewRouter()
systemRouter.Methods("GET").Path("/").Handler(http.HandlerFunc(GetHTMLConfigHandler)) systemRouter.Methods("GET").Path("/").Handler(http.HandlerFunc(GetHTMLConfigHandler))
systemRouter.Methods("GET").Path("/health").Handler(http.HandlerFunc(GetHealthHandler)) systemRouter.Methods("GET").Path("/health").Handler(http.HandlerFunc(GetHealthHandler))
@ -67,6 +67,7 @@ func (provider *WebProvider) Provide(configurationChan chan<- configMessage) {
} }
} }
}() }()
return nil
} }
func GetConfigHandler(rw http.ResponseWriter, r *http.Request) { func GetConfigHandler(rw http.ResponseWriter, r *http.Request) {

14
zk.go Normal file
View file

@ -0,0 +1,14 @@
package main
type ZookepperProvider struct {
Watch bool
Endpoint string
Prefix string
Filename string
KvProvider *KvProvider
}
func (provider *ZookepperProvider) Provide(configurationChan chan<- configMessage) error {
provider.KvProvider = NewZkProvider(provider)
return provider.KvProvider.provide(configurationChan)
}