mprove Rancher provider functionality:

- Improves default filtering behavior to filter by container health/healthState
- Optionally allows filtering by service health/healthState
- Allows configuration of refresh interval
This commit is contained in:
Matthew Kelch 2017-04-29 15:37:54 -04:00
parent e2fdc27d64
commit 44db6e9290
5 changed files with 205 additions and 22 deletions

View file

@ -1604,6 +1604,12 @@ domain = "rancher.localhost"
# #
Watch = true Watch = true
# Polling interval (in seconds)
#
# Optional
#
RefreshSeconds = 15
# Expose Rancher services by default in traefik # Expose Rancher services by default in traefik
# #
# Optional # Optional
@ -1611,6 +1617,13 @@ Watch = true
# #
ExposedByDefault = false ExposedByDefault = false
# Filter services with unhealthy states and health states
#
# Optional
# Default: false
#
EnableServiceHealthFilter = false
# Endpoint to use when connecting to Rancher # Endpoint to use when connecting to Rancher
# #
# Required # Required

View file

@ -21,11 +21,6 @@ import (
rancher "github.com/rancher/go-rancher/client" rancher "github.com/rancher/go-rancher/client"
) )
const (
// RancherDefaultWatchTime is the duration of the interval when polling rancher
RancherDefaultWatchTime = 15 * time.Second
)
var ( var (
withoutPagination *rancher.ListOpts withoutPagination *rancher.ListOpts
) )
@ -34,12 +29,14 @@ var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider. // Provider holds configurations of the provider.
type Provider struct { type Provider struct {
provider.BaseProvider `mapstructure:",squash"` provider.BaseProvider `mapstructure:",squash"`
Endpoint string `description:"Rancher server HTTP(S) endpoint."` Endpoint string `description:"Rancher server HTTP(S) endpoint."`
AccessKey string `description:"Rancher server access key."` AccessKey string `description:"Rancher server access key."`
SecretKey string `description:"Rancher server Secret Key."` SecretKey string `description:"Rancher server Secret Key."`
ExposedByDefault bool `description:"Expose Services by default"` ExposedByDefault bool `description:"Expose Services by default"`
Domain string `description:"Default domain used"` Domain string `description:"Default domain used"`
RefreshSeconds int `description:"Polling interval (in seconds)"`
EnableServiceHealthFilter bool `description:"Filter services with unhealthy states and health states."`
} }
type rancherData struct { type rancherData struct {
@ -47,6 +44,7 @@ type rancherData struct {
Labels map[string]string // List of labels set to container or service Labels map[string]string // List of labels set to container or service
Containers []string Containers []string
Health string Health string
State string
} }
func init() { func init() {
@ -56,7 +54,7 @@ func init() {
} }
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) return fmt.Sprintf("{name:%s, labels:%v, containers: %v, health: %s, state: %s}", r.Name, r.Labels, r.Containers, r.Health, r.State)
} }
// Frontend Labels // Frontend Labels
@ -261,7 +259,7 @@ func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *s
if p.Watch { if p.Watch {
_, cancel := context.WithCancel(ctx) _, cancel := context.WithCancel(ctx)
ticker := time.NewTicker(RancherDefaultWatchTime) ticker := time.NewTicker(time.Second * time.Duration(p.RefreshSeconds))
pool.Go(func(stop chan bool) { pool.Go(func(stop chan bool) {
for { for {
select { select {
@ -384,6 +382,7 @@ func parseRancherData(environments []*rancher.Environment, services []*rancher.S
rancherData := rancherData{ rancherData := rancherData{
Name: environment.Name + "/" + service.Name, Name: environment.Name + "/" + service.Name,
Health: service.HealthState, Health: service.HealthState,
State: service.State,
Labels: make(map[string]string), Labels: make(map[string]string),
Containers: []string{}, Containers: []string{},
} }
@ -393,11 +392,8 @@ func parseRancherData(environments []*rancher.Environment, services []*rancher.S
} }
for _, container := range containers { for _, container := range containers {
for key, value := range container.Labels { if container.Labels["io.rancher.stack_service.name"] == rancherData.Name && containerFilter(container) {
rancherData.Containers = append(rancherData.Containers, container.PrimaryIpAddress)
if key == "io.rancher.stack_service.name" && value == rancherData.Name {
rancherData.Containers = append(rancherData.Containers, container.PrimaryIpAddress)
}
} }
} }
rancherDataList = append(rancherDataList, rancherData) rancherDataList = append(rancherDataList, rancherData)
@ -463,6 +459,20 @@ func (p *Provider) loadRancherConfig(services []rancherData) *types.Configuratio
} }
func containerFilter(container *rancher.Container) bool {
if container.HealthState != "" && container.HealthState != "healthy" && container.HealthState != "updating-healthy" {
log.Debugf("Filtering container %s with healthState of %s", container.Name, container.HealthState)
return false
}
if container.State != "" && container.State != "running" && container.State != "updating-running" {
log.Debugf("Filtering container %s with state of %s", container.Name, container.State)
return false
}
return true
}
func (p *Provider) serviceFilter(service rancherData) bool { func (p *Provider) serviceFilter(service rancherData) bool {
if service.Labels["traefik.port"] == "" { if service.Labels["traefik.port"] == "" {
@ -475,9 +485,18 @@ func (p *Provider) serviceFilter(service rancherData) bool {
return false return false
} }
if service.Health != "" && service.Health != "healthy" { // Only filter services by Health (HealthState) and State if EnableServiceHealthFilter is true
log.Debugf("Filtering unhealthy or starting service %s", service.Name) if p.EnableServiceHealthFilter {
return false
if service.Health != "" && service.Health != "healthy" && service.Health != "updating-healthy" {
log.Debugf("Filtering service %s with healthState of %s", service.Name, service.Health)
return false
}
if service.State != "" && service.State != "active" && service.State != "updating-active" && service.State != "upgraded" {
log.Debugf("Filtering service %s with state of %s", service.Name, service.State)
return false
}
} }
return true return true

View file

@ -1,12 +1,148 @@
package rancher package rancher
import ( import (
"github.com/containous/traefik/types"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"github.com/containous/traefik/types"
rancher "github.com/rancher/go-rancher/client"
) )
func TestRancherServiceFilter(t *testing.T) {
provider := &Provider{
Domain: "rancher.localhost",
EnableServiceHealthFilter: true,
}
services := []struct {
service rancherData
expected bool
}{
{
service: rancherData{
Labels: map[string]string{
"traefik.enable": "true",
},
Health: "healthy",
State: "active",
},
expected: false,
},
{
service: rancherData{
Labels: map[string]string{
"traefik.port": "80",
"traefik.enable": "false",
},
Health: "healthy",
State: "active",
},
expected: false,
},
{
service: rancherData{
Labels: map[string]string{
"traefik.port": "80",
"traefik.enable": "true",
},
Health: "unhealthy",
State: "active",
},
expected: false,
},
{
service: rancherData{
Labels: map[string]string{
"traefik.port": "80",
"traefik.enable": "true",
},
Health: "healthy",
State: "inactive",
},
expected: false,
},
{
service: rancherData{
Labels: map[string]string{
"traefik.port": "80",
"traefik.enable": "true",
},
Health: "healthy",
State: "active",
},
expected: true,
},
{
service: rancherData{
Labels: map[string]string{
"traefik.port": "80",
"traefik.enable": "true",
},
Health: "healthy",
State: "upgraded",
},
expected: true,
},
}
for _, e := range services {
actual := provider.serviceFilter(e.service)
if actual != e.expected {
t.Fatalf("expected %t, got %t", e.expected, actual)
}
}
}
func TestRancherContainerFilter(t *testing.T) {
containers := []struct {
container *rancher.Container
expected bool
}{
{
container: &rancher.Container{
HealthState: "unhealthy",
State: "running",
},
expected: false,
},
{
container: &rancher.Container{
HealthState: "healthy",
State: "stopped",
},
expected: false,
},
{
container: &rancher.Container{
State: "stopped",
},
expected: false,
},
{
container: &rancher.Container{
HealthState: "healthy",
State: "running",
},
expected: true,
},
{
container: &rancher.Container{
HealthState: "updating-healthy",
State: "updating-running",
},
expected: true,
},
}
for _, e := range containers {
actual := containerFilter(e.container)
if actual != e.expected {
t.Fatalf("expected %t, got %t", e.expected, actual)
}
}
}
func TestRancherGetFrontendName(t *testing.T) { func TestRancherGetFrontendName(t *testing.T) {
provider := &Provider{ provider := &Provider{
Domain: "rancher.localhost", Domain: "rancher.localhost",

View file

@ -446,6 +446,8 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
var defaultRancher rancher.Provider var defaultRancher rancher.Provider
defaultRancher.Watch = true defaultRancher.Watch = true
defaultRancher.ExposedByDefault = true defaultRancher.ExposedByDefault = true
defaultRancher.RefreshSeconds = 15
defaultRancher.EnableServiceHealthFilter = false
// default DynamoDB // default DynamoDB
var defaultDynamoDB dynamodb.Provider var defaultDynamoDB dynamodb.Provider

View file

@ -1046,6 +1046,12 @@
# #
# Watch = true # Watch = true
# Polling interval (in seconds)
#
# Optional
#
# RefreshSeconds = 15
# Expose Rancher services by default in traefik # Expose Rancher services by default in traefik
# #
# Optional # Optional
@ -1053,6 +1059,13 @@
# #
# ExposedByDefault = false # ExposedByDefault = false
# Filter services with unhealthy states and health states
#
# Optional
# Default: false
#
# EnableServiceHealthFilter = false
# Endpoint to use when connecting to Rancher # Endpoint to use when connecting to Rancher
# #
# Required # Required