1
0
Fork 0

Dynamic Configuration Refactoring

This commit is contained in:
Ludovic Fernandez 2018-11-14 10:18:03 +01:00 committed by Traefiker Bot
parent d3ae88f108
commit a09dfa3ce1
452 changed files with 21023 additions and 9419 deletions

View file

@ -1,6 +1,7 @@
package metrics
import (
"context"
"time"
"github.com/containous/traefik/log"
@ -11,7 +12,7 @@ import (
)
var datadogClient = dogstatsd.New("traefik.", kitlog.LoggerFunc(func(keyvals ...interface{}) error {
log.Info(keyvals)
log.WithoutContext().WithField(log.MetricsProviderName, "datadog").Info(keyvals)
return nil
}))
@ -34,9 +35,9 @@ const (
)
// RegisterDatadog registers the metrics pusher if this didn't happen yet and creates a datadog Registry instance.
func RegisterDatadog(config *types.Datadog) Registry {
func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry {
if datadogTicker == nil {
datadogTicker = initDatadogClient(config)
datadogTicker = initDatadogClient(ctx, config)
}
registry := &standardRegistry{
@ -58,14 +59,14 @@ func RegisterDatadog(config *types.Datadog) Registry {
return registry
}
func initDatadogClient(config *types.Datadog) *time.Ticker {
func initDatadogClient(ctx context.Context, config *types.Datadog) *time.Ticker {
address := config.Address
if len(address) == 0 {
address = "localhost:8125"
}
pushInterval, err := time.ParseDuration(config.PushInterval)
if err != nil {
log.Warnf("Unable to parse %s into pushInterval, using 10s as default value", config.PushInterval)
log.FromContext(ctx).Warnf("Unable to parse %s from config.PushInterval: using 10s as the default value", config.PushInterval)
pushInterval = 10 * time.Second
}

View file

@ -1,6 +1,7 @@
package metrics
import (
"context"
"net/http"
"strconv"
"testing"
@ -15,7 +16,7 @@ func TestDatadog(t *testing.T) {
// This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond
udp.Timeout = 5 * time.Second
datadogRegistry := RegisterDatadog(&types.Datadog{Address: ":18125", PushInterval: "1s"})
datadogRegistry := RegisterDatadog(context.Background(), &types.Datadog{Address: ":18125", PushInterval: "1s"})
defer StopDatadog()
if !datadogRegistry.IsEnabled() {

View file

@ -2,6 +2,7 @@ package metrics
import (
"bytes"
"context"
"fmt"
"net/url"
"regexp"
@ -39,13 +40,18 @@ const (
influxDBServerUpName = "traefik.backend.server.up"
)
const (
protocolHTTP = "http"
protocolUDP = "udp"
)
// RegisterInfluxDB registers the metrics pusher if this didn't happen yet and creates a InfluxDB Registry instance.
func RegisterInfluxDB(config *types.InfluxDB) Registry {
func RegisterInfluxDB(ctx context.Context, config *types.InfluxDB) Registry {
if influxDBClient == nil {
influxDBClient = initInfluxDBClient(config)
influxDBClient = initInfluxDBClient(ctx, config)
}
if influxDBTicker == nil {
influxDBTicker = initInfluxDBTicker(config)
influxDBTicker = initInfluxDBTicker(ctx, config)
}
return &standardRegistry{
@ -66,30 +72,32 @@ func RegisterInfluxDB(config *types.InfluxDB) Registry {
}
// initInfluxDBTicker creates a influxDBClient
func initInfluxDBClient(config *types.InfluxDB) *influx.Influx {
func initInfluxDBClient(ctx context.Context, config *types.InfluxDB) *influx.Influx {
logger := log.FromContext(ctx)
// TODO deprecated: move this switch into configuration.SetEffectiveConfiguration when web provider will be removed.
switch config.Protocol {
case "udp":
case protocolUDP:
if len(config.Database) > 0 || len(config.RetentionPolicy) > 0 {
log.Warn("Database and RetentionPolicy are only used when protocol is http.")
logger.Warn("Database and RetentionPolicy options have no effect with UDP.")
config.Database = ""
config.RetentionPolicy = ""
}
case "http":
case protocolHTTP:
if u, err := url.Parse(config.Address); err == nil {
if u.Scheme != "http" && u.Scheme != "https" {
log.Warnf("InfluxDB address %s should specify a scheme of http or https, defaulting to http.", config.Address)
logger.Warnf("InfluxDB address %s should specify a scheme (http or https): falling back on HTTP.", config.Address)
config.Address = "http://" + config.Address
}
} else {
log.Errorf("Unable to parse influxdb address: %v, defaulting to udp.", err)
config.Protocol = "udp"
logger.Errorf("Unable to parse the InfluxDB address %v: falling back on UDP.", err)
config.Protocol = protocolUDP
config.Database = ""
config.RetentionPolicy = ""
}
default:
log.Warnf("Unsupported protocol: %s, defaulting to udp.", config.Protocol)
config.Protocol = "udp"
logger.Warnf("Unsupported protocol %s: falling back on UDP.", config.Protocol)
config.Protocol = protocolUDP
config.Database = ""
config.RetentionPolicy = ""
}
@ -101,16 +109,16 @@ func initInfluxDBClient(config *types.InfluxDB) *influx.Influx {
RetentionPolicy: config.RetentionPolicy,
},
kitlog.LoggerFunc(func(keyvals ...interface{}) error {
log.Info(keyvals)
log.WithoutContext().WithField(log.MetricsProviderName, "influxdb").Info(keyvals)
return nil
}))
}
// initInfluxDBTicker initializes metrics pusher
func initInfluxDBTicker(config *types.InfluxDB) *time.Ticker {
func initInfluxDBTicker(ctx context.Context, config *types.InfluxDB) *time.Ticker {
pushInterval, err := time.ParseDuration(config.PushInterval)
if err != nil {
log.Warnf("Unable to parse %s into pushInterval, using 10s as default value", config.PushInterval)
log.FromContext(ctx).Warnf("Unable to parse %s from config.PushInterval: using 10s as the default value", config.PushInterval)
pushInterval = 10 * time.Second
}
@ -144,8 +152,10 @@ func (w *influxDBWriter) Write(bp influxdb.BatchPoints) error {
defer c.Close()
if writeErr := c.Write(bp); writeErr != nil {
log.Errorf("Error writing to influx: %s", writeErr.Error())
if handleErr := w.handleWriteError(c, writeErr); handleErr != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "influxdb"))
log.FromContext(ctx).Errorf("Error while writing to InfluxDB: %s", writeErr.Error())
if handleErr := w.handleWriteError(ctx, c, writeErr); handleErr != nil {
return handleErr
}
// Retry write after successful handling of writeErr
@ -168,8 +178,8 @@ func (w *influxDBWriter) initWriteClient() (influxdb.Client, error) {
})
}
func (w *influxDBWriter) handleWriteError(c influxdb.Client, writeErr error) error {
if w.config.Protocol != "http" {
func (w *influxDBWriter) handleWriteError(ctx context.Context, c influxdb.Client, writeErr error) error {
if w.config.Protocol != protocolHTTP {
return writeErr
}
@ -184,7 +194,9 @@ func (w *influxDBWriter) handleWriteError(c influxdb.Client, writeErr error) err
qStr = fmt.Sprintf("%s WITH NAME \"%s\"", qStr, w.config.RetentionPolicy)
}
log.Debugf("Influx database does not exist, attempting to create with query: %s", qStr)
logger := log.FromContext(ctx)
logger.Debugf("InfluxDB database not found: attempting to create one with %s", qStr)
q := influxdb.NewQuery(qStr, "", "")
response, queryErr := c.Query(q)
@ -192,10 +204,10 @@ func (w *influxDBWriter) handleWriteError(c influxdb.Client, writeErr error) err
queryErr = response.Error()
}
if queryErr != nil {
log.Errorf("Error creating InfluxDB database: %s", queryErr)
logger.Errorf("Error while creating the InfluxDB database %s", queryErr)
return queryErr
}
log.Debugf("Successfully created influx database: %s", w.config.Database)
logger.Debugf("Successfully created the InfluxDB database %s", w.config.Database)
return nil
}

View file

@ -1,6 +1,7 @@
package metrics
import (
"context"
"fmt"
"io/ioutil"
"net/http"
@ -19,7 +20,7 @@ func TestInfluxDB(t *testing.T) {
// This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond
udp.Timeout = 5 * time.Second
influxDBRegistry := RegisterInfluxDB(&types.InfluxDB{Address: ":8089", PushInterval: "1s"})
influxDBRegistry := RegisterInfluxDB(context.Background(), &types.InfluxDB{Address: ":8089", PushInterval: "1s"})
defer StopInfluxDB()
if !influxDBRegistry.IsEnabled() {
@ -79,7 +80,7 @@ func TestInfluxDBHTTP(t *testing.T) {
}))
defer ts.Close()
influxDBRegistry := RegisterInfluxDB(&types.InfluxDB{Address: ts.URL, Protocol: "http", PushInterval: "1s", Database: "test", RetentionPolicy: "autogen"})
influxDBRegistry := RegisterInfluxDB(context.Background(), &types.InfluxDB{Address: ts.URL, Protocol: "http", PushInterval: "1s", Database: "test", RetentionPolicy: "autogen"})
defer StopInfluxDB()
if !influxDBRegistry.IsEnabled() {

View file

@ -1,12 +1,14 @@
package metrics
import (
"context"
"net/http"
"sort"
"strings"
"sync"
"github.com/containous/mux"
"github.com/containous/traefik/config"
"github.com/containous/traefik/log"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
@ -60,17 +62,17 @@ var promState = newPrometheusState()
// PrometheusHandler exposes Prometheus routes.
type PrometheusHandler struct{}
// AddRoutes adds Prometheus routes on a router.
func (h PrometheusHandler) AddRoutes(router *mux.Router) {
// Append adds Prometheus routes on a router.
func (h PrometheusHandler) Append(router *mux.Router) {
router.Methods(http.MethodGet).Path("/metrics").Handler(promhttp.Handler())
}
// RegisterPrometheus registers all Prometheus metrics.
// It must be called only once and failing to register the metrics will lead to a panic.
func RegisterPrometheus(config *types.Prometheus) Registry {
func RegisterPrometheus(ctx context.Context, config *types.Prometheus) Registry {
standardRegistry := initStandardRegistry(config)
if !registerPromState() {
if !registerPromState(ctx) {
return nil
}
@ -172,13 +174,14 @@ func initStandardRegistry(config *types.Prometheus) Registry {
}
}
func registerPromState() bool {
func registerPromState(ctx context.Context) bool {
if err := stdprometheus.Register(promState); err != nil {
logger := log.FromContext(ctx)
if _, ok := err.(stdprometheus.AlreadyRegisteredError); !ok {
log.Errorf("Unable to register Traefik to Prometheus: %v", err)
logger.Errorf("Unable to register Traefik to Prometheus: %v", err)
return false
}
log.Debug("Prometheus collector already registered.")
logger.Debug("Prometheus collector already registered.")
}
return true
}
@ -186,23 +189,24 @@ func registerPromState() bool {
// OnConfigurationUpdate receives the current configuration from Traefik.
// It then converts the configuration to the optimized package internal format
// and sets it to the promState.
func OnConfigurationUpdate(configurations types.Configurations) {
func OnConfigurationUpdate(configurations config.Configurations) {
dynamicConfig := newDynamicConfig()
for _, config := range configurations {
for _, frontend := range config.Frontends {
for _, entrypointName := range frontend.EntryPoints {
dynamicConfig.entrypoints[entrypointName] = true
}
}
for backendName, backend := range config.Backends {
dynamicConfig.backends[backendName] = make(map[string]bool)
for _, server := range backend.Servers {
dynamicConfig.backends[backendName][server.URL] = true
}
}
}
// FIXME metrics
// for _, config := range configurations {
// for _, frontend := range config.Frontends {
// for _, entrypointName := range frontend.EntryPoints {
// dynamicConfig.entrypoints[entrypointName] = true
// }
// }
//
// for backendName, backend := range config.Backends {
// dynamicConfig.backends[backendName] = make(map[string]bool)
// for _, server := range backend.Servers {
// dynamicConfig.backends[backendName][server.URL] = true
// }
// }
// }
promState.SetDynamicConfig(dynamicConfig)
}

View file

@ -1,12 +1,14 @@
package metrics
import (
"context"
"fmt"
"net/http"
"strconv"
"testing"
"time"
"github.com/containous/traefik/config"
th "github.com/containous/traefik/testhelpers"
"github.com/containous/traefik/types"
"github.com/prometheus/client_golang/prometheus"
@ -69,8 +71,7 @@ func TestRegisterPromState(t *testing.T) {
initStandardRegistry(prom)
}
promReg := registerPromState()
if promReg != false {
if registerPromState(context.Background()) {
actualNbRegistries++
}
@ -101,7 +102,7 @@ func TestPrometheus(t *testing.T) {
// Reset state of global promState.
defer promState.reset()
prometheusRegistry := RegisterPrometheus(&types.Prometheus{})
prometheusRegistry := RegisterPrometheus(context.Background(), &types.Prometheus{})
defer prometheus.Unregister(promState)
if !prometheusRegistry.IsEnabled() {
@ -266,21 +267,27 @@ func TestPrometheus(t *testing.T) {
}
func TestPrometheusMetricRemoval(t *testing.T) {
// FIXME metrics
t.Skip("waiting for metrics")
// Reset state of global promState.
defer promState.reset()
prometheusRegistry := RegisterPrometheus(&types.Prometheus{})
prometheusRegistry := RegisterPrometheus(context.Background(), &types.Prometheus{})
defer prometheus.Unregister(promState)
configurations := make(types.Configurations)
configurations := make(config.Configurations)
configurations["providerName"] = th.BuildConfiguration(
th.WithFrontends(
th.WithFrontend("backend1", th.WithEntryPoints("entrypoint1")),
th.WithRouters(
th.WithRouter("foo",
th.WithServiceName("bar")),
),
th.WithBackends(
th.WithBackendNew("backend1", th.WithServersNew(th.WithServerNew("http://localhost:9000"))),
th.WithLoadBalancerServices(th.WithService("bar",
th.WithLBMethod("wrr"),
th.WithServers(th.WithServer("http://localhost:9000"))),
),
)
OnConfigurationUpdate(configurations)
// Register some metrics manually that are not part of the active configuration.
@ -321,7 +328,7 @@ func TestPrometheusRemovedMetricsReset(t *testing.T) {
// Reset state of global promState.
defer promState.reset()
prometheusRegistry := RegisterPrometheus(&types.Prometheus{})
prometheusRegistry := RegisterPrometheus(context.Background(), &types.Prometheus{})
defer prometheus.Unregister(promState)
labelNamesValues := []string{

View file

@ -1,6 +1,7 @@
package metrics
import (
"context"
"time"
"github.com/containous/traefik/log"
@ -11,7 +12,7 @@ import (
)
var statsdClient = statsd.New("traefik.", kitlog.LoggerFunc(func(keyvals ...interface{}) error {
log.Info(keyvals)
log.WithoutContext().WithField(log.MetricsProviderName, "statsd").Info(keyvals)
return nil
}))
@ -33,9 +34,9 @@ const (
)
// RegisterStatsd registers the metrics pusher if this didn't happen yet and creates a statsd Registry instance.
func RegisterStatsd(config *types.Statsd) Registry {
func RegisterStatsd(ctx context.Context, config *types.Statsd) Registry {
if statsdTicker == nil {
statsdTicker = initStatsdTicker(config)
statsdTicker = initStatsdTicker(ctx, config)
}
return &standardRegistry{
@ -56,14 +57,14 @@ func RegisterStatsd(config *types.Statsd) Registry {
}
// initStatsdTicker initializes metrics pusher and creates a statsdClient if not created already
func initStatsdTicker(config *types.Statsd) *time.Ticker {
func initStatsdTicker(ctx context.Context, config *types.Statsd) *time.Ticker {
address := config.Address
if len(address) == 0 {
address = "localhost:8125"
}
pushInterval, err := time.ParseDuration(config.PushInterval)
if err != nil {
log.Warnf("Unable to parse %s into pushInterval, using 10s as default value", config.PushInterval)
log.FromContext(ctx).Warnf("Unable to parse %s from config.PushInterval: using 10s as the default value", config.PushInterval)
pushInterval = 10 * time.Second
}

View file

@ -1,6 +1,7 @@
package metrics
import (
"context"
"net/http"
"testing"
"time"
@ -14,7 +15,7 @@ func TestStatsD(t *testing.T) {
// This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond
udp.Timeout = 5 * time.Second
statsdRegistry := RegisterStatsd(&types.Statsd{Address: ":18125", PushInterval: "1s"})
statsdRegistry := RegisterStatsd(context.Background(), &types.Statsd{Address: ":18125", PushInterval: "1s"})
defer StopStatsd()
if !statsdRegistry.IsEnabled() {