From 38bd49b97eedbfbeaef48712b8260ea794e637dd Mon Sep 17 00:00:00 2001 From: Manuel Laufenberg Date: Sun, 29 Jan 2017 00:01:56 +0100 Subject: [PATCH 1/5] add dependency, start provider and fetch data add tons of labels Provide - WIP add rancher data over rancher types first version of direct fetch - pagination still an issue --- configuration.go | 5 + glide.lock | 18 +- glide.yaml | 4 +- provider/rancher.go | 471 +++++++++++++++++++++++++++++++++++++++++ server.go | 3 + templates/rancher.tmpl | 38 ++++ 6 files changed, 533 insertions(+), 6 deletions(-) create mode 100644 provider/rancher.go create mode 100644 templates/rancher.tmpl diff --git a/configuration.go b/configuration.go index 717b73eb4..98aa636f0 100644 --- a/configuration.go +++ b/configuration.go @@ -51,6 +51,7 @@ type GlobalConfiguration struct { Mesos *provider.Mesos `description:"Enable Mesos backend"` Eureka *provider.Eureka `description:"Enable Eureka backend"` ECS *provider.ECS `description:"Enable ECS backend"` + Rancher *provider.Rancher `description:"Enable Rancher backend"` } // DefaultEntryPoints holds default entry points @@ -415,6 +416,10 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { ECS: &defaultECS, Retry: &Retry{}, } + + //default Rancher + //@TODO: ADD + return &TraefikConfiguration{ GlobalConfiguration: defaultConfiguration, } diff --git a/glide.lock b/glide.lock index 4539ab00f..f475a02ee 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: a0b0abed2162e490cbe75a6a36ebaaf39e748ee80e419e879e7253679a0bc134 -updated: 2017-02-05T18:09:09.856588042Z +hash: b2ac93355c3f551a75216a800337cee9321f6c9a04a18ab1fa8d8152e89b7595 +updated: 2017-02-05T23:00:24.927243212+01:00 imports: - name: bitbucket.org/ww/goautoneg version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675 @@ -300,6 +300,8 @@ imports: version: 44d81051d367757e1c7c6a5a86423ece9afcf63c - name: github.com/gorilla/context version: 1ea25387ff6f684839d82767c1733ff4d4d15d0a +- name: github.com/gorilla/websocket + version: c36f2fe5c330f0ac404b616b96c438b8616b1aaf - name: github.com/hashicorp/consul version: fce7d75609a04eeb9d4bf41c8dc592aac18fc97d subpackages: @@ -370,7 +372,7 @@ imports: - name: github.com/mvdan/xurls version: fa08908f19eca8c491d68c6bd8b4b44faea6daf8 - name: github.com/NYTimes/gziphandler - version: f6438dbf4a82c56684964b03956aa727b0d7816b + version: 6710af535839f57c687b62c4c23d649f9545d885 - name: github.com/ogier/pflag version: 45c278ab3607870051a2ea9040bb85fcb8557481 - name: github.com/opencontainers/runc @@ -386,6 +388,8 @@ imports: - ovh - name: github.com/pborman/uuid version: 5007efa264d92316c43112bc573e754bc889b7b1 +- name: github.com/pkg/errors + version: 248dadf4e9068a0b3e79f02ed0a610d935de5302 - name: github.com/pmezard/go-difflib version: d8ed2627bdf02c080bf22230dbb337003b7aba2d subpackages: @@ -414,6 +418,12 @@ imports: version: ab4b0d7ff424c462da486aef27f354cdeb29a319 subpackages: - src/egoscale +- name: github.com/rancher/go-rancher + version: 2c43ff300f3eafcbd7d0b89b10427fc630efdc1e + subpackages: + - client +- name: github.com/rcrowley/go-metrics + version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c - name: github.com/ryanuber/go-glob version: 572520ed46dbddaed19ea3d9541bdd0494163693 - name: github.com/samuel/go-zookeeper @@ -739,8 +749,6 @@ testImports: version: 06479209bdc0d4135911688c18157bd39bd99c22 subpackages: - specs-go -- name: github.com/pkg/errors - version: 01fa4104b9c248c8945d14d9f128454d5b28d595 - name: github.com/vbatts/tar-split version: 6810cedb21b2c3d0b9bb8f9af12ff2dc7a2f14df subpackages: diff --git a/glide.yaml b/glide.yaml index a13917963..a4ec033e0 100644 --- a/glide.yaml +++ b/glide.yaml @@ -135,4 +135,6 @@ import: - package: github.com/gogo/protobuf version: v0.3 subpackages: - - proto \ No newline at end of file + - proto +- package: github.com/rancher/go-rancher + version: 2c43ff300f3eafcbd7d0b89b10427fc630efdc1e diff --git a/provider/rancher.go b/provider/rancher.go new file mode 100644 index 000000000..de38d7fd0 --- /dev/null +++ b/provider/rancher.go @@ -0,0 +1,471 @@ +package provider + +import ( + rancher "github.com/rancher/go-rancher/client" + "github.com/containous/traefik/safe" + "github.com/containous/traefik/types" + "github.com/containous/traefik/log" + "github.com/cenk/backoff" + "github.com/containous/traefik/job" + "time" + "github.com/BurntSushi/ty/fun" + //"context" + "errors" + "strings" + "strconv" + "math" + "fmt" + "text/template" +) + +var _ Provider = (*Rancher)(nil) + +// Rancher holds configurations of the Docker provider. +type Rancher struct { + BaseProvider `mapstructure:",squash"` + Endpoint string `description:"Rancher server HTTP(S) endpoint."` + AccessKey string `description:"Rancher server access key."` + SecretKey string `description:"Rancher server Secret Key."` + ExposedByDefault bool `description:"Expose Services by default"` + Domain string `description:"Default domain used"` +} + +type rancherData struct { + Name string + Labels map[string]string // List of labels set to container or service + Containers []string + Health string +} + +func (r rancherData)String() string{ + return fmt.Sprintf("{name:%s, labels:%v, containers: %v, health: %s}", r.Name, r.Labels, r.Containers, r.Health) +} + + +// Frontend Labels +func (provider *Rancher) getPassHostHeader(service rancherData) string { + if passHostHeader, err := getServiceLabel(service, "traefik.frontend.passHostHeader"); err == nil { + return passHostHeader + } + return "true" +} + +func (provider *Rancher) getPriority(service rancherData) string { + if priority, err := getServiceLabel(service, "traefik.frontend.priority"); err == nil { + return priority + } + return "0" +} + +func (provider *Rancher) getEntryPoints(service rancherData) []string { + if entryPoints, err := getServiceLabel(service, "traefik.frontend.entryPoints"); err == nil { + return strings.Split(entryPoints, ",") + } + return []string{} +} + +func (provider *Rancher) getFrontendRule(service rancherData) string { + if label, err := getServiceLabel(service, "traefik.frontend.rule"); err == nil { + return label + } + return "Host:" + strings.ToLower(strings.Replace(service.Name, "/", "_", -1)) + "." + provider.Domain +} + + +func (provider *Rancher) getFrontendName(service rancherData) string { + // Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78 + return normalize(provider.getFrontendRule(service)) +} + +// Backend Labels +func (provider *Rancher) getLoadBalancerMethod(service rancherData) string { + if label, err := getServiceLabel(service, "traefik.backend.loadbalancer.method"); err == nil { + return label + } + return "wrr" +} + + +func (provider *Rancher) hasLoadBalancerLabel(service rancherData) bool { + _, errMethod := getServiceLabel(service, "traefik.backend.loadbalancer.method") + _, errSticky := getServiceLabel(service, "traefik.backend.loadbalancer.sticky") + if errMethod != nil && errSticky != nil { + return false + } + return true +} + + +func (provider *Rancher) hasCircuitBreakerLabel(service rancherData) bool { + if _, err := getServiceLabel(service, "traefik.backend.circuitbreaker.expression"); err != nil { + return false + } + return true +} + +func (provider *Rancher) getCircuitBreakerExpression(service rancherData) string { + if label, err := getServiceLabel(service, "traefik.backend.circuitbreaker.expression"); err == nil { + return label + } + return "NetworkErrorRatio() > 1" +} + +func (provider *Rancher) getSticky(service rancherData) string { + if _, err := getServiceLabel(service, "traefik.backend.loadbalancer.sticky"); err == nil { + return "true" + } + return "false" +} + +func (provider *Rancher) getBackend(service rancherData) string { + if label, err := getServiceLabel(service, "traefik.backend"); err == nil { + return normalize(label) + } + return normalize(service.Name) +} + +// Generall Application Stuff +func (provider *Rancher) getPort(service rancherData) string { + if label, err := getServiceLabel(service, "traefik.port"); err == nil { + return label + } + return "" +} + +func (provider *Rancher) getProtocol(service rancherData) string { + if label, err := getServiceLabel(service, "traefik.protocol"); err == nil { + return label + } + return "http" +} + +func (provider *Rancher) getWeight(service rancherData) string { + if label, err := getServiceLabel(service, "traefik.weight"); err == nil { + return label + } + return "0" +} + +func (provider *Rancher) getDomain(service rancherData) string { + if label, err := getServiceLabel(service, "traefik.domain"); err == nil { + return label + } + return "" +} + +func (provider *Rancher) hasMaxConnLabels(service rancherData) bool { + if _, err := getServiceLabel(service, "traefik.backend.maxconn.amount"); err != nil { + return false + } + if _, err := getServiceLabel(service, "traefik.backend.maxconn.extractorfunc"); err != nil { + return false + } + return true +} + +func (provider *Rancher) getMaxConnAmount(service rancherData) int64 { + if label, err := getServiceLabel(service, "traefik.backend.maxconn.amount"); err == nil { + i, errConv := strconv.ParseInt(label, 10, 64) + if errConv != nil { + log.Errorf("Unable to parse traefik.backend.maxconn.amount %s", label) + return math.MaxInt64 + } + return i + } + return math.MaxInt64 +} + +func (provider *Rancher) getMaxConnExtractorFunc(service rancherData) string { + if label, err := getServiceLabel(service, "traefik.backend.maxconn.extractorfunc"); err == nil { + return label + } + return "request.host" +} + + +// Container Stuff +func (provider *Rancher) getIPAddress(container *rancher.Container) string { + ipAdress := container.PrimaryIpAddress; + + if ipAdress != ""{ + return ipAdress + } + return "" +} + + + +func getServiceLabel(service rancherData, label string) (string, error) { + for key, value := range service.Labels { + if key == label { + return value, nil + } + } + return "", errors.New("Label not found:" + label) +} + +func (provider *Rancher) createClient() (*rancher.RancherClient, error) { + return rancher.NewRancherClient(&rancher.ClientOpts{ + Url: provider.Endpoint, + AccessKey: provider.AccessKey, + SecretKey: provider.SecretKey, + }) +} + +func (provider *Rancher) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error { + + safe.Go(func() { + operation := func() error { + rancherClient, err := provider.createClient() + //ctx := context.Background() + var environments = listRancherEnvironments(rancherClient) + var services = listRancherServices(rancherClient) + var container = listRancherContainer(rancherClient) + + var rancherData = parseRancherData(environments, services, container) + + fmt.Printf("Rancher Data #2 %s", &rancherData) + + if err != nil { + log.Errorf("Failed to create a client for docker, error: %s", err) + return err + } + + configuration := provider.loadRancherConfig(rancherData) + configurationChan <- types.ConfigMessage{ + ProviderName: "rancher", + Configuration: configuration, + } + + return nil + } + notify := func(err error, time time.Duration) { + log.Errorf("Rancher connection error %+v, retrying in %s", err, time) + } + err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify) + if err != nil { + log.Errorf("Cannot connect to Rancher Endpoint %+v", err) + } + }) + + return nil +} + +func listRancherEnvironments(client *rancher.RancherClient)([]*rancher.Environment){ + + var environmentList = []*rancher.Environment{} + + environments, err := client.Environment.List(nil) + + if err != nil { + log.Errorf("Cannot get Rancher Environments %+v", err) + } + + for k, environment := range environments.Data { + log.Debugf("Adding environment with id %s", environment.Id) + environmentList = append(environmentList, &environments.Data[k]) + } + + return environmentList +} + +/* +"io.rancher.stack.name" + */ +func listRancherServices(client *rancher.RancherClient)([]*rancher.Service){ + + var servicesList = []*rancher.Service{} + + services, err := client.Service.List(nil) + + if err != nil { + log.Errorf("Cannot get Rancher Services %+v", err) + } + + for k, service := range services.Data { + log.Debugf("Adding service with id %s", service.Id) + servicesList = append(servicesList, &services.Data[k]) + } + + return servicesList +} + +func listRancherContainer(client *rancher.RancherClient)([]*rancher.Container){ + + var containerList = []*rancher.Container{} + + container, err := client.Container.List(nil) + + if err != nil { + log.Errorf("Cannot get Rancher Services %+v", err) + } + + for k, singleContainer := range container.Data { + log.Debugf("Adding container with id %s", singleContainer.Id) + containerList = append(containerList, &container.Data[k]) + } + + return containerList +} + +func parseRancherData(environments []*rancher.Environment, services []*rancher.Service, containers []*rancher.Container) []rancherData { + + log.Debugf("Starting to parse Rancher Data") + + var rancherDataList []rancherData + + for _, environment := range environments { + + log.Debugf("Iterating trough environment %s", environment.Name) + + for _, service := range services { + + log.Debugf("Iterating trough service %s with id %s for environment %s", service.Name, service.AccountId, environment.Id) + + if service.EnvironmentId != environment.Id { + log.Debugf("NO MATCH") + continue + } + + rancherData := rancherData{ + Name: environment.Name + "/" + service.Name, + Health: service.HealthState, + Labels: make(map[string]string), + Containers: []string{}, + } + + for key, value := range service.LaunchConfig.Labels { + rancherData.Labels[key] = value.(string) + } + + for _, container := range containers { + + for key, value := range container.Labels { + + if key == "io.rancher.stack_service.name" && value == rancherData.Name { + rancherData.Containers = append(rancherData.Containers, container.PrimaryIpAddress) + } + } + } + rancherDataList = append(rancherDataList, rancherData) + } + } + + return rancherDataList +} + +func (provider *Rancher) loadRancherConfig(services []rancherData) *types.Configuration { + + var RancherFuncMap = template.FuncMap{ + "getIPAddress": provider.getIPAddress, + "getPort": provider.getPort, + "getBackend": provider.getBackend, + "getWeight": provider.getWeight, + "getDomain": provider.getDomain, + "getProtocol": provider.getProtocol, + "getPassHostHeader": provider.getPassHostHeader, + "getPriority": provider.getPriority, + "getEntryPoints": provider.getEntryPoints, + "getFrontendRule": provider.getFrontendRule, + "hasCircuitBreakerLabel": provider.hasCircuitBreakerLabel, + "getCircuitBreakerExpression": provider.getCircuitBreakerExpression, + "hasLoadBalancerLabel": provider.hasLoadBalancerLabel, + "getLoadBalancerMethod": provider.getLoadBalancerMethod, + "hasMaxConnLabels": provider.hasMaxConnLabels, + "getMaxConnAmount": provider.getMaxConnAmount, + "getMaxConnExtractorFunc": provider.getMaxConnExtractorFunc, + "getSticky": provider.getSticky, + } + + + // filter services + filteredServices := fun.Filter(func(service rancherData) bool { + return provider.serviceFilter(service) + }, services).([]rancherData) + + frontends := map[string]rancherData{} + backends := map[string]rancherData{} + + for _, service := range filteredServices { + frontendName := provider.getFrontendName(service) + frontends[frontendName] = service + backendName := provider.getBackend(service) + backends[backendName] = service + } + + fmt.Printf("Frontends %v", frontends) + fmt.Printf("Backends %v", backends) + + templateObjects := struct { + Frontends map[string]rancherData + Backends map[string]rancherData + Domain string + }{ + frontends, + backends, + provider.Domain, + } + + configuration, err := provider.getConfiguration("templates/rancher.tmpl", RancherFuncMap, templateObjects) + if err != nil { + log.Error(err) + } + return configuration + +} + +func (provider *Rancher) serviceFilter(service rancherData) bool { + + if service.Labels["traefik.port"] == "" { + log.Debugf("Filtering service %s without traefik.port label", service.Name) + return false; + } + + if !isServiceEnabled(service, provider.ExposedByDefault) { + log.Debugf("Filtering disabled service %s", service.Name) + return false + } + + /* + constraintTags := strings.Split(container.Labels["traefik.tags"], ",") + if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok { + if failingConstraint != nil { + log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String()) + } + return false + } + */ + + if service.Health != "" && service.Health != "healthy" { + log.Debugf("Filtering unhealthy or starting service %s", service.Name) + return false + } + + log.Debugf("Service %s is enabled!", service.Name) + + return true +} + +func (provider *Rancher) containerFilter(container *rancher.Container, instanceIds []string) bool { + + //log.Debugf("Filtering Containers for InstanceIds %v ", instanceIds) + for _, instanceId := range instanceIds { + + //log.Debugf("Looking for instanceId %s on on container %s", instanceId, container.Id) + if container.Id == instanceId { + //log.Debugf("Found container with id %s", instanceId) + return true + } + } + + return false +} + +func isServiceEnabled(service rancherData, exposedByDefault bool) bool { + + if service.Labels["traefik.enable"] != "" { + var v = service.Labels["traefik.enable"] + return exposedByDefault && v != "false" || v == "true" + } + return false +} diff --git a/server.go b/server.go index e9cd4decb..a2d3a406f 100644 --- a/server.go +++ b/server.go @@ -377,6 +377,9 @@ func (server *Server) configureProviders() { if server.globalConfiguration.ECS != nil { server.providers = append(server.providers, server.globalConfiguration.ECS) } + if server.globalConfiguration.Rancher != nil { + server.providers = append(server.providers, server.globalConfiguration.Rancher) + } } func (server *Server) startProviders() { diff --git a/templates/rancher.tmpl b/templates/rancher.tmpl new file mode 100644 index 000000000..15fb4571f --- /dev/null +++ b/templates/rancher.tmpl @@ -0,0 +1,38 @@ +{{$backendServers := .Backends}} +[backends]{{range $backendName, $backend := .Backends}} + {{if hasCircuitBreakerLabel $backend}} + [backends.backend-{{$backendName}}.circuitbreaker] + expression = "{{getCircuitBreakerExpression $backend}}" + {{end}} + + {{if hasLoadBalancerLabel $backend}} + [backends.backend-{{$backendName}}.loadbalancer] + method = "{{getLoadBalancerMethod $backend}}" + sticky = {{getSticky $backend}} + {{end}} + + {{if hasMaxConnLabels $backend}} + [backends.backend-{{$backendName}}.maxconn] + amount = {{getMaxConnAmount $backend}} + extractorfunc = "{{getMaxConnExtractorFunc $backend}}" + {{end}} + + {{range $index, $ip := $backend.Containers}} + [backends.backend-{{$backendName}}.servers.server-{{$index}}] + url = "{{getProtocol $backend}}://{{$ip}}:{{getPort $backend}}" + weight = {{getWeight $backend}} + {{end}} + +{{end}} + +[frontends]{{range $frontendName, $service := .Frontends}} + [frontends."frontend-{{$frontendName}}"] + backend = "backend-{{getBackend $service}}" + passHostHeader = {{getPassHostHeader $service}} + priority = {{getPriority $service}} + entryPoints = [{{range getEntryPoints $service}} + "{{.}}", + {{end}}] + [frontends."frontend-{{$frontendName}}".routes."route-frontend-{{$frontendName}}"] + rule = "{{getFrontendRule $service}}" +{{end}} From 48524a58fff61a8d7f5f0d444f304c11022acf23 Mon Sep 17 00:00:00 2001 From: Manuel Laufenberg Date: Sun, 5 Feb 2017 22:54:24 +0100 Subject: [PATCH 2/5] fix all containers - no matter of pagination fmt & lint --- provider/rancher.go | 152 ++++++++++++++++++++------------------------ 1 file changed, 69 insertions(+), 83 deletions(-) diff --git a/provider/rancher.go b/provider/rancher.go index de38d7fd0..bd162cec0 100644 --- a/provider/rancher.go +++ b/provider/rancher.go @@ -1,47 +1,46 @@ package provider import ( - rancher "github.com/rancher/go-rancher/client" - "github.com/containous/traefik/safe" - "github.com/containous/traefik/types" - "github.com/containous/traefik/log" + "github.com/BurntSushi/ty/fun" "github.com/cenk/backoff" "github.com/containous/traefik/job" + "github.com/containous/traefik/log" + "github.com/containous/traefik/safe" + "github.com/containous/traefik/types" + rancher "github.com/rancher/go-rancher/client" "time" - "github.com/BurntSushi/ty/fun" //"context" "errors" - "strings" - "strconv" - "math" "fmt" + "math" + "strconv" + "strings" "text/template" ) var _ Provider = (*Rancher)(nil) -// Rancher holds configurations of the Docker provider. +// Rancher holds configurations of the Rancher provider. type Rancher struct { BaseProvider `mapstructure:",squash"` - Endpoint string `description:"Rancher server HTTP(S) endpoint."` - AccessKey string `description:"Rancher server access key."` - SecretKey string `description:"Rancher server Secret Key."` - ExposedByDefault bool `description:"Expose Services by default"` - Domain string `description:"Default domain used"` + Endpoint string `description:"Rancher server HTTP(S) endpoint."` + AccessKey string `description:"Rancher server access key."` + SecretKey string `description:"Rancher server Secret Key."` + ExposedByDefault bool `description:"Expose Services by default"` + Domain string `description:"Default domain used"` } type rancherData struct { - Name string - Labels map[string]string // List of labels set to container or service - Containers []string - Health string + Name string + Labels map[string]string // List of labels set to container or service + Containers []string + Health string } -func (r rancherData)String() string{ +func (r rancherData) String() string { return fmt.Sprintf("{name:%s, labels:%v, containers: %v, health: %s}", r.Name, r.Labels, r.Containers, r.Health) } - // Frontend Labels func (provider *Rancher) getPassHostHeader(service rancherData) string { if passHostHeader, err := getServiceLabel(service, "traefik.frontend.passHostHeader"); err == nil { @@ -71,7 +70,6 @@ func (provider *Rancher) getFrontendRule(service rancherData) string { return "Host:" + strings.ToLower(strings.Replace(service.Name, "/", "_", -1)) + "." + provider.Domain } - func (provider *Rancher) getFrontendName(service rancherData) string { // Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78 return normalize(provider.getFrontendRule(service)) @@ -85,7 +83,6 @@ func (provider *Rancher) getLoadBalancerMethod(service rancherData) string { return "wrr" } - func (provider *Rancher) hasLoadBalancerLabel(service rancherData) bool { _, errMethod := getServiceLabel(service, "traefik.backend.loadbalancer.method") _, errSticky := getServiceLabel(service, "traefik.backend.loadbalancer.sticky") @@ -95,7 +92,6 @@ func (provider *Rancher) hasLoadBalancerLabel(service rancherData) bool { return true } - func (provider *Rancher) hasCircuitBreakerLabel(service rancherData) bool { if _, err := getServiceLabel(service, "traefik.backend.circuitbreaker.expression"); err != nil { return false @@ -182,19 +178,16 @@ func (provider *Rancher) getMaxConnExtractorFunc(service rancherData) string { return "request.host" } - // Container Stuff func (provider *Rancher) getIPAddress(container *rancher.Container) string { - ipAdress := container.PrimaryIpAddress; + ipAdress := container.PrimaryIpAddress - if ipAdress != ""{ + if ipAdress != "" { return ipAdress } return "" } - - func getServiceLabel(service rancherData, label string) (string, error) { for key, value := range service.Labels { if key == label { @@ -212,6 +205,8 @@ func (provider *Rancher) createClient() (*rancher.RancherClient, error) { }) } +// Provide allows the provider to provide configurations to traefik +// using the given configuration channel. func (provider *Rancher) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error { safe.Go(func() { @@ -224,10 +219,8 @@ func (provider *Rancher) Provide(configurationChan chan<- types.ConfigMessage, p var rancherData = parseRancherData(environments, services, container) - fmt.Printf("Rancher Data #2 %s", &rancherData) - if err != nil { - log.Errorf("Failed to create a client for docker, error: %s", err) + log.Errorf("Failed to create a client for rancher, error: %s", err) return err } @@ -251,7 +244,7 @@ func (provider *Rancher) Provide(configurationChan chan<- types.ConfigMessage, p return nil } -func listRancherEnvironments(client *rancher.RancherClient)([]*rancher.Environment){ +func listRancherEnvironments(client *rancher.RancherClient) []*rancher.Environment { var environmentList = []*rancher.Environment{} @@ -271,8 +264,8 @@ func listRancherEnvironments(client *rancher.RancherClient)([]*rancher.Environme /* "io.rancher.stack.name" - */ -func listRancherServices(client *rancher.RancherClient)([]*rancher.Service){ +*/ +func listRancherServices(client *rancher.RancherClient) []*rancher.Service { var servicesList = []*rancher.Service{} @@ -290,48 +283,61 @@ func listRancherServices(client *rancher.RancherClient)([]*rancher.Service){ return servicesList } -func listRancherContainer(client *rancher.RancherClient)([]*rancher.Container){ +func listRancherContainer(client *rancher.RancherClient) []*rancher.Container { - var containerList = []*rancher.Container{} + containerList := []*rancher.Container{} container, err := client.Container.List(nil) + log.Debugf("first container len: %i", len(container.Data)) + if err != nil { log.Errorf("Cannot get Rancher Services %+v", err) } - for k, singleContainer := range container.Data { - log.Debugf("Adding container with id %s", singleContainer.Id) - containerList = append(containerList, &container.Data[k]) + valid := true + + for valid { + for k, singleContainer := range container.Data { + log.Debugf("Adding container with id %s", singleContainer.Id) + containerList = append(containerList, &container.Data[k]) + } + + log.Debugf("calling container.Next()") + + container, err = container.Next() + + if err != nil { + log.Debugf("Error - Break it babe") + break + } + + if container == nil || len(container.Data) == 0 { + log.Debugf("No more containers - valid false") + valid = false + } else { + log.Debugf("Next length %i", len(container.Data)) + } } return containerList } func parseRancherData(environments []*rancher.Environment, services []*rancher.Service, containers []*rancher.Container) []rancherData { - - log.Debugf("Starting to parse Rancher Data") - var rancherDataList []rancherData for _, environment := range environments { - log.Debugf("Iterating trough environment %s", environment.Name) - for _, service := range services { - - log.Debugf("Iterating trough service %s with id %s for environment %s", service.Name, service.AccountId, environment.Id) - if service.EnvironmentId != environment.Id { - log.Debugf("NO MATCH") continue } rancherData := rancherData{ - Name: environment.Name + "/" + service.Name, - Health: service.HealthState, - Labels: make(map[string]string), - Containers: []string{}, + Name: environment.Name + "/" + service.Name, + Health: service.HealthState, + Labels: make(map[string]string), + Containers: []string{}, } for key, value := range service.LaunchConfig.Labels { @@ -339,7 +345,6 @@ func parseRancherData(environments []*rancher.Environment, services []*rancher.S } for _, container := range containers { - for key, value := range container.Labels { if key == "io.rancher.stack_service.name" && value == rancherData.Name { @@ -377,7 +382,6 @@ func (provider *Rancher) loadRancherConfig(services []rancherData) *types.Config "getSticky": provider.getSticky, } - // filter services filteredServices := fun.Filter(func(service rancherData) bool { return provider.serviceFilter(service) @@ -393,13 +397,10 @@ func (provider *Rancher) loadRancherConfig(services []rancherData) *types.Config backends[backendName] = service } - fmt.Printf("Frontends %v", frontends) - fmt.Printf("Backends %v", backends) - templateObjects := struct { - Frontends map[string]rancherData - Backends map[string]rancherData - Domain string + Frontends map[string]rancherData + Backends map[string]rancherData + Domain string }{ frontends, backends, @@ -418,7 +419,7 @@ func (provider *Rancher) serviceFilter(service rancherData) bool { if service.Labels["traefik.port"] == "" { log.Debugf("Filtering service %s without traefik.port label", service.Name) - return false; + return false } if !isServiceEnabled(service, provider.ExposedByDefault) { @@ -427,13 +428,13 @@ func (provider *Rancher) serviceFilter(service rancherData) bool { } /* - constraintTags := strings.Split(container.Labels["traefik.tags"], ",") - if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok { - if failingConstraint != nil { - log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String()) + constraintTags := strings.Split(container.Labels["traefik.tags"], ",") + if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok { + if failingConstraint != nil { + log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String()) + } + return false } - return false - } */ if service.Health != "" && service.Health != "healthy" { @@ -446,24 +447,9 @@ func (provider *Rancher) serviceFilter(service rancherData) bool { return true } -func (provider *Rancher) containerFilter(container *rancher.Container, instanceIds []string) bool { - - //log.Debugf("Filtering Containers for InstanceIds %v ", instanceIds) - for _, instanceId := range instanceIds { - - //log.Debugf("Looking for instanceId %s on on container %s", instanceId, container.Id) - if container.Id == instanceId { - //log.Debugf("Found container with id %s", instanceId) - return true - } - } - - return false -} - func isServiceEnabled(service rancherData, exposedByDefault bool) bool { - if service.Labels["traefik.enable"] != "" { + if service.Labels["traefik.enable"] != "" { var v = service.Labels["traefik.enable"] return exposedByDefault && v != "false" || v == "true" } From 9a5dc54f857c5b1369754d448fd86cc29a66a406 Mon Sep 17 00:00:00 2001 From: Manuel Laufenberg Date: Mon, 6 Feb 2017 00:58:05 +0100 Subject: [PATCH 3/5] add some unit tests fmt & lint --- provider/rancher.go | 27 +-- provider/rancher_test.go | 448 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 450 insertions(+), 25 deletions(-) create mode 100644 provider/rancher_test.go diff --git a/provider/rancher.go b/provider/rancher.go index bd162cec0..f2106caa9 100644 --- a/provider/rancher.go +++ b/provider/rancher.go @@ -146,7 +146,7 @@ func (provider *Rancher) getDomain(service rancherData) string { if label, err := getServiceLabel(service, "traefik.domain"); err == nil { return label } - return "" + return provider.Domain } func (provider *Rancher) hasMaxConnLabels(service rancherData) bool { @@ -178,16 +178,6 @@ func (provider *Rancher) getMaxConnExtractorFunc(service rancherData) string { return "request.host" } -// Container Stuff -func (provider *Rancher) getIPAddress(container *rancher.Container) string { - ipAdress := container.PrimaryIpAddress - - if ipAdress != "" { - return ipAdress - } - return "" -} - func getServiceLabel(service rancherData, label string) (string, error) { for key, value := range service.Labels { if key == label { @@ -362,7 +352,6 @@ func parseRancherData(environments []*rancher.Environment, services []*rancher.S func (provider *Rancher) loadRancherConfig(services []rancherData) *types.Configuration { var RancherFuncMap = template.FuncMap{ - "getIPAddress": provider.getIPAddress, "getPort": provider.getPort, "getBackend": provider.getBackend, "getWeight": provider.getWeight, @@ -427,23 +416,11 @@ func (provider *Rancher) serviceFilter(service rancherData) bool { return false } - /* - constraintTags := strings.Split(container.Labels["traefik.tags"], ",") - if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok { - if failingConstraint != nil { - log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String()) - } - return false - } - */ - if service.Health != "" && service.Health != "healthy" { log.Debugf("Filtering unhealthy or starting service %s", service.Name) return false } - log.Debugf("Service %s is enabled!", service.Name) - return true } @@ -453,5 +430,5 @@ func isServiceEnabled(service rancherData, exposedByDefault bool) bool { var v = service.Labels["traefik.enable"] return exposedByDefault && v != "false" || v == "true" } - return false + return exposedByDefault } diff --git a/provider/rancher_test.go b/provider/rancher_test.go new file mode 100644 index 000000000..d0b920f2d --- /dev/null +++ b/provider/rancher_test.go @@ -0,0 +1,448 @@ +package provider + +import ( + "github.com/containous/traefik/types" + "reflect" + "strings" + "testing" +) + +func TestRancherGetFrontendName(t *testing.T) { + provider := &Rancher{ + Domain: "rancher.localhost", + } + + services := []struct { + service rancherData + expected string + }{ + { + service: rancherData{ + Name: "foo", + }, + expected: "Host-foo-rancher-localhost", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.frontend.rule": "Headers:User-Agent,bat/0.1.0", + }, + }, + + expected: "Headers-User-Agent-bat-0-1-0", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.frontend.rule": "Host:foo.bar", + }, + }, + + expected: "Host-foo-bar", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.frontend.rule": "Path:/test", + }, + }, + + expected: "Path-test", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.frontend.rule": "PathPrefix:/test2", + }, + }, + + expected: "PathPrefix-test2", + }, + } + + for _, e := range services { + actual := provider.getFrontendName(e.service) + if actual != e.expected { + t.Fatalf("expected %q, got %q", e.expected, actual) + } + } +} + +func TestRancherGetFrontendRule(t *testing.T) { + provider := &Rancher{ + Domain: "rancher.localhost", + } + + services := []struct { + service rancherData + expected string + }{ + { + service: rancherData{ + Name: "foo", + }, + expected: "Host:foo.rancher.localhost", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.frontend.rule": "Host:foo.bar.com", + }, + }, + + expected: "Host:foo.bar.com", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.frontend.rule": "Path:/test", + }, + }, + + expected: "Path:/test", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.frontend.rule": "PathPrefix:/test2", + }, + }, + + expected: "PathPrefix:/test2", + }, + } + + for _, e := range services { + actual := provider.getFrontendRule(e.service) + if actual != e.expected { + t.Fatalf("expected %q, got %q", e.expected, actual) + } + } +} + +func TestRancherGetBackend(t *testing.T) { + provider := &Rancher{ + Domain: "rancher.localhost", + } + + services := []struct { + service rancherData + expected string + }{ + { + service: rancherData{ + Name: "test-service", + }, + expected: "test-service", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.backend": "foobar", + }, + }, + + expected: "foobar", + }, + } + + for _, e := range services { + actual := provider.getBackend(e.service) + if actual != e.expected { + t.Fatalf("expected %q, got %q", e.expected, actual) + } + } +} + +func TestRancherGetWeight(t *testing.T) { + provider := &Rancher{ + Domain: "rancher.localhost", + } + + services := []struct { + service rancherData + expected string + }{ + { + service: rancherData{ + Name: "test-service", + }, + expected: "0", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.weight": "5", + }, + }, + + expected: "5", + }, + } + + for _, e := range services { + actual := provider.getWeight(e.service) + if actual != e.expected { + t.Fatalf("expected %q, got %q", e.expected, actual) + } + } +} + +func TestRancherGetPort(t *testing.T) { + provider := &Rancher{ + Domain: "rancher.localhost", + } + + services := []struct { + service rancherData + expected string + }{ + { + service: rancherData{ + Name: "test-service", + }, + expected: "", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.port": "1337", + }, + }, + + expected: "1337", + }, + } + + for _, e := range services { + actual := provider.getPort(e.service) + if actual != e.expected { + t.Fatalf("expected %q, got %q", e.expected, actual) + } + } +} + +func TestRancherGetDomain(t *testing.T) { + provider := &Rancher{ + Domain: "rancher.localhost", + } + + services := []struct { + service rancherData + expected string + }{ + { + service: rancherData{ + Name: "test-service", + }, + expected: "rancher.localhost", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.domain": "foo.bar", + }, + }, + + expected: "foo.bar", + }, + } + + for _, e := range services { + actual := provider.getDomain(e.service) + if actual != e.expected { + t.Fatalf("expected %q, got %q", e.expected, actual) + } + } +} + +func TestRancherGetProtocol(t *testing.T) { + provider := &Rancher{ + Domain: "rancher.localhost", + } + + services := []struct { + service rancherData + expected string + }{ + { + service: rancherData{ + Name: "test-service", + }, + expected: "http", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.protocol": "https", + }, + }, + + expected: "https", + }, + } + + for _, e := range services { + actual := provider.getProtocol(e.service) + if actual != e.expected { + t.Fatalf("expected %q, got %q", e.expected, actual) + } + } +} + +func TestRancherGetPassHostHeader(t *testing.T) { + provider := &Rancher{ + Domain: "rancher.localhost", + } + + services := []struct { + service rancherData + expected string + }{ + { + service: rancherData{ + Name: "test-service", + }, + expected: "true", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "traefik.frontend.passHostHeader": "false", + }, + }, + + expected: "false", + }, + } + + for _, e := range services { + actual := provider.getPassHostHeader(e.service) + if actual != e.expected { + t.Fatalf("expected %q, got %q", e.expected, actual) + } + } +} + +func TestRancherGetLabel(t *testing.T) { + services := []struct { + service rancherData + expected string + }{ + { + service: rancherData{ + Name: "test-service", + }, + expected: "Label not found", + }, + { + service: rancherData{ + Name: "test-service", + Labels: map[string]string{ + "foo": "bar", + }, + }, + + expected: "", + }, + } + + for _, e := range services { + label, err := getServiceLabel(e.service, "foo") + if e.expected != "" { + if err == nil || !strings.Contains(err.Error(), e.expected) { + t.Fatalf("expected an error with %q, got %v", e.expected, err) + } + } else { + if label != "bar" { + t.Fatalf("expected label 'bar', got %s", label) + } + } + } +} + +func TestRancherLoadRancherConfig(t *testing.T) { + cases := []struct { + services []rancherData + expectedFrontends map[string]*types.Frontend + expectedBackends map[string]*types.Backend + }{ + { + services: []rancherData{}, + expectedFrontends: map[string]*types.Frontend{}, + expectedBackends: map[string]*types.Backend{}, + }, + { + services: []rancherData{ + { + Name: "test-service", + Labels: map[string]string{ + "traefik.port": "80", + }, + Health: "healthy", + Containers: []string{"127.0.0.1"}, + }, + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-Host-test-service-rancher-localhost": { + Backend: "backend-test-service", + PassHostHeader: true, + EntryPoints: []string{}, + Priority: 0, + + Routes: map[string]types.Route{ + "route-frontend-Host-test-service-rancher-localhost": { + Rule: "Host:test-service.rancher.localhost", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-test-service": { + Servers: map[string]types.Server{ + "server-0": { + URL: "http://127.0.0.1:80", + Weight: 0, + }, + }, + CircuitBreaker: nil, + }, + }, + }, + } + + provider := &Rancher{ + Domain: "rancher.localhost", + ExposedByDefault: true, + } + + for _, c := range cases { + var rancherDataList []rancherData + for _, service := range c.services { + rancherDataList = append(rancherDataList, service) + } + + actualConfig := provider.loadRancherConfig(rancherDataList) + + // Compare backends + if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) { + t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends) + } + if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) { + t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends) + } + } +} From bdb63ac78501df9950cfbf9ed0d88388a60eadbd Mon Sep 17 00:00:00 2001 From: Manuel Laufenberg Date: Mon, 6 Feb 2017 16:28:12 +0100 Subject: [PATCH 4/5] add watch function --- provider/rancher.go | 63 +++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/provider/rancher.go b/provider/rancher.go index f2106caa9..1c9b7ba41 100644 --- a/provider/rancher.go +++ b/provider/rancher.go @@ -1,6 +1,9 @@ package provider import ( + "context" + "errors" + "fmt" "github.com/BurntSushi/ty/fun" "github.com/cenk/backoff" "github.com/containous/traefik/job" @@ -8,14 +11,15 @@ import ( "github.com/containous/traefik/safe" "github.com/containous/traefik/types" rancher "github.com/rancher/go-rancher/client" - "time" - //"context" - "errors" - "fmt" "math" "strconv" "strings" "text/template" + "time" +) + +const ( + RancherDefaultWatchTime = 15 * time.Second ) var _ Provider = (*Rancher)(nil) @@ -202,7 +206,7 @@ func (provider *Rancher) Provide(configurationChan chan<- types.ConfigMessage, p safe.Go(func() { operation := func() error { rancherClient, err := provider.createClient() - //ctx := context.Background() + ctx := context.Background() var environments = listRancherEnvironments(rancherClient) var services = listRancherServices(rancherClient) var container = listRancherContainer(rancherClient) @@ -220,6 +224,37 @@ func (provider *Rancher) Provide(configurationChan chan<- types.ConfigMessage, p Configuration: configuration, } + if provider.Watch { + _, cancel := context.WithCancel(ctx) + ticker := time.NewTicker(RancherDefaultWatchTime) + pool.Go(func(stop chan bool) { + for { + select { + case <-ticker.C: + + log.Debugf("Refreshing new Data from Rancher API") + var environments = listRancherEnvironments(rancherClient) + var services = listRancherServices(rancherClient) + var container = listRancherContainer(rancherClient) + + rancherData := parseRancherData(environments, services, container) + + configuration := provider.loadRancherConfig(rancherData) + if configuration != nil { + configurationChan <- types.ConfigMessage{ + ProviderName: "rancher", + Configuration: configuration, + } + } + case <-stop: + ticker.Stop() + cancel() + return + } + } + }) + } + return nil } notify := func(err error, time time.Duration) { @@ -244,17 +279,13 @@ func listRancherEnvironments(client *rancher.RancherClient) []*rancher.Environme log.Errorf("Cannot get Rancher Environments %+v", err) } - for k, environment := range environments.Data { - log.Debugf("Adding environment with id %s", environment.Id) + for k, _ := range environments.Data { environmentList = append(environmentList, &environments.Data[k]) } return environmentList } -/* -"io.rancher.stack.name" -*/ func listRancherServices(client *rancher.RancherClient) []*rancher.Service { var servicesList = []*rancher.Service{} @@ -265,8 +296,7 @@ func listRancherServices(client *rancher.RancherClient) []*rancher.Service { log.Errorf("Cannot get Rancher Services %+v", err) } - for k, service := range services.Data { - log.Debugf("Adding service with id %s", service.Id) + for k, _ := range services.Data { servicesList = append(servicesList, &services.Data[k]) } @@ -288,25 +318,18 @@ func listRancherContainer(client *rancher.RancherClient) []*rancher.Container { valid := true for valid { - for k, singleContainer := range container.Data { - log.Debugf("Adding container with id %s", singleContainer.Id) + for k := range container.Data { containerList = append(containerList, &container.Data[k]) } - log.Debugf("calling container.Next()") - container, err = container.Next() if err != nil { - log.Debugf("Error - Break it babe") break } if container == nil || len(container.Data) == 0 { - log.Debugf("No more containers - valid false") valid = false - } else { - log.Debugf("Next length %i", len(container.Data)) } } From 3a875e29549ab5feb606b4e7c1e5c7d05d2fa696 Mon Sep 17 00:00:00 2001 From: Manuel Laufenberg Date: Mon, 6 Feb 2017 16:30:21 +0100 Subject: [PATCH 5/5] add default config lint files --- configuration.go | 6 ++++++ provider/rancher.go | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/configuration.go b/configuration.go index 98aa636f0..7013455c7 100644 --- a/configuration.go +++ b/configuration.go @@ -401,6 +401,11 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { defaultECS.Cluster = "default" defaultECS.Constraints = types.Constraints{} + //default Rancher + var defaultRancher provider.Rancher + defaultRancher.Watch = true + defaultRancher.ExposedByDefault = true + defaultConfiguration := GlobalConfiguration{ Docker: &defaultDocker, File: &defaultFile, @@ -414,6 +419,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { Kubernetes: &defaultKubernetes, Mesos: &defaultMesos, ECS: &defaultECS, + Rancher: &defaultRancher, Retry: &Retry{}, } diff --git a/provider/rancher.go b/provider/rancher.go index 1c9b7ba41..77072f0a9 100644 --- a/provider/rancher.go +++ b/provider/rancher.go @@ -19,6 +19,7 @@ import ( ) const ( + // RancherDefaultWatchTime is the duration of the interval when polling rancher RancherDefaultWatchTime = 15 * time.Second ) @@ -279,7 +280,7 @@ func listRancherEnvironments(client *rancher.RancherClient) []*rancher.Environme log.Errorf("Cannot get Rancher Environments %+v", err) } - for k, _ := range environments.Data { + for k := range environments.Data { environmentList = append(environmentList, &environments.Data[k]) } @@ -296,7 +297,7 @@ func listRancherServices(client *rancher.RancherClient) []*rancher.Service { log.Errorf("Cannot get Rancher Services %+v", err) } - for k, _ := range services.Data { + for k := range services.Data { servicesList = append(servicesList, &services.Data[k]) }