Add internal provider

Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
This commit is contained in:
Ludovic Fernandez 2019-11-14 16:40:05 +01:00 committed by Traefiker Bot
parent 2ee2e29262
commit 424e2a9439
71 changed files with 2523 additions and 1469 deletions

View file

@ -3,7 +3,6 @@ package main
import (
"context"
"encoding/json"
"fmt"
stdlog "log"
"net/http"
"os"
@ -20,12 +19,17 @@ import (
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/metrics"
"github.com/containous/traefik/v2/pkg/middlewares/accesslog"
"github.com/containous/traefik/v2/pkg/provider/acme"
"github.com/containous/traefik/v2/pkg/provider/aggregator"
"github.com/containous/traefik/v2/pkg/provider/traefik"
"github.com/containous/traefik/v2/pkg/safe"
"github.com/containous/traefik/v2/pkg/server"
"github.com/containous/traefik/v2/pkg/server/router"
"github.com/containous/traefik/v2/pkg/server/middleware"
"github.com/containous/traefik/v2/pkg/server/service"
traefiktls "github.com/containous/traefik/v2/pkg/tls"
"github.com/containous/traefik/v2/pkg/types"
"github.com/containous/traefik/v2/pkg/version"
"github.com/coreos/go-systemd/daemon"
assetfs "github.com/elazarl/go-bindata-assetfs"
@ -77,7 +81,7 @@ func runCmd(staticConfiguration *static.Configuration) error {
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment
if err := roundrobin.SetDefaultWeight(0); err != nil {
log.WithoutContext().Errorf("Could not set roundrobin default weight: %v", err)
log.WithoutContext().Errorf("Could not set round robin default weight: %v", err)
}
staticConfiguration.SetEffectiveConfiguration()
@ -105,43 +109,11 @@ func runCmd(staticConfiguration *static.Configuration) error {
stats(staticConfiguration)
providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers)
tlsManager := traefiktls.NewManager()
acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager)
serverEntryPointsTCP := make(server.TCPEntryPoints)
for entryPointName, config := range staticConfiguration.EntryPoints {
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
serverEntryPointsTCP[entryPointName], err = server.NewTCPEntryPoint(ctx, config)
if err != nil {
return fmt.Errorf("error while building entryPoint %s: %v", entryPointName, err)
}
serverEntryPointsTCP[entryPointName].RouteAppenderFactory = router.NewRouteAppenderFactory(*staticConfiguration, entryPointName, acmeProviders)
svr, err := setupServer(staticConfiguration)
if err != nil {
return err
}
svr := server.NewServer(*staticConfiguration, providerAggregator, serverEntryPointsTCP, tlsManager)
resolverNames := map[string]struct{}{}
for _, p := range acmeProviders {
resolverNames[p.ResolverName] = struct{}{}
svr.AddListener(p.ListenConfiguration)
}
svr.AddListener(func(config dynamic.Configuration) {
for rtName, rt := range config.HTTP.Routers {
if rt.TLS == nil || rt.TLS.CertResolver == "" {
continue
}
if _, ok := resolverNames[rt.TLS.CertResolver]; !ok {
log.WithoutContext().Errorf("the router %s uses a non-existent resolver: %s", rtName, rt.TLS.CertResolver)
}
}
})
ctx := cmd.ContextWithSignal(context.Background())
if staticConfiguration.Ping != nil {
@ -168,7 +140,7 @@ func runCmd(staticConfiguration *static.Configuration) error {
for range tick {
resp, errHealthCheck := healthcheck.Do(*staticConfiguration)
if resp != nil {
resp.Body.Close()
_ = resp.Body.Close()
}
if staticConfiguration.Ping == nil || errHealthCheck == nil {
@ -188,6 +160,94 @@ func runCmd(staticConfiguration *static.Configuration) error {
return nil
}
func setupServer(staticConfiguration *static.Configuration) (*server.Server, error) {
providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers)
// adds internal provider
err := providerAggregator.AddProvider(traefik.New(*staticConfiguration))
if err != nil {
return nil, err
}
tlsManager := traefiktls.NewManager()
acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager)
serverEntryPointsTCP, err := server.NewTCPEntryPoints(*staticConfiguration)
if err != nil {
return nil, err
}
ctx := context.Background()
routinesPool := safe.NewPool(ctx)
metricsRegistry := registerMetricClients(staticConfiguration.Metrics)
accessLog := setupAccessLog(staticConfiguration.AccessLog)
chainBuilder := middleware.NewChainBuilder(*staticConfiguration, metricsRegistry, accessLog)
managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry)
tcpRouterFactory := server.NewTCPRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder)
watcher := server.NewConfigurationWatcher(routinesPool, providerAggregator, time.Duration(staticConfiguration.Providers.ProvidersThrottleDuration))
watcher.AddListener(func(conf dynamic.Configuration) {
ctx := context.Background()
tlsManager.UpdateConfigs(ctx, conf.TLS.Stores, conf.TLS.Options, conf.TLS.Certificates)
})
watcher.AddListener(func(_ dynamic.Configuration) {
metricsRegistry.ConfigReloadsCounter().Add(1)
metricsRegistry.LastConfigReloadSuccessGauge().Set(float64(time.Now().Unix()))
})
watcher.AddListener(switchRouter(tcpRouterFactory, acmeProviders, serverEntryPointsTCP))
watcher.AddListener(func(conf dynamic.Configuration) {
if metricsRegistry.IsEpEnabled() || metricsRegistry.IsSvcEnabled() {
var eps []string
for key := range serverEntryPointsTCP {
eps = append(eps, key)
}
metrics.OnConfigurationUpdate(conf, eps)
}
})
resolverNames := map[string]struct{}{}
for _, p := range acmeProviders {
resolverNames[p.ResolverName] = struct{}{}
watcher.AddListener(p.ListenConfiguration)
}
watcher.AddListener(func(config dynamic.Configuration) {
for rtName, rt := range config.HTTP.Routers {
if rt.TLS == nil || rt.TLS.CertResolver == "" {
continue
}
if _, ok := resolverNames[rt.TLS.CertResolver]; !ok {
log.WithoutContext().Errorf("the router %s uses a non-existent resolver: %s", rtName, rt.TLS.CertResolver)
}
}
})
return server.NewServer(routinesPool, serverEntryPointsTCP, watcher, chainBuilder, accessLog), nil
}
func switchRouter(tcpRouterFactory *server.TCPRouterFactory, acmeProviders []*acme.Provider, serverEntryPointsTCP server.TCPEntryPoints) func(conf dynamic.Configuration) {
return func(conf dynamic.Configuration) {
routers := tcpRouterFactory.CreateTCPRouters(conf)
for entryPointName, rt := range routers {
for _, p := range acmeProviders {
if p != nil && p.HTTPChallenge != nil && p.HTTPChallenge.EntryPoint == entryPointName {
rt.HTTPHandler(p.CreateHandler(rt.GetHTTPHandler()))
break
}
}
}
serverEntryPointsTCP.Switch(routers)
}
}
// initACMEProvider creates an acme provider from the ACME part of globalConfiguration
func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager) []*acme.Provider {
challengeStore := acme.NewLocalChallengeStore()
@ -222,6 +282,60 @@ func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.Pr
return resolvers
}
func registerMetricClients(metricsConfig *types.Metrics) metrics.Registry {
if metricsConfig == nil {
return metrics.NewVoidRegistry()
}
var registries []metrics.Registry
if metricsConfig.Prometheus != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "prometheus"))
prometheusRegister := metrics.RegisterPrometheus(ctx, metricsConfig.Prometheus)
if prometheusRegister != nil {
registries = append(registries, prometheusRegister)
log.FromContext(ctx).Debug("Configured Prometheus metrics")
}
}
if metricsConfig.Datadog != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "datadog"))
registries = append(registries, metrics.RegisterDatadog(ctx, metricsConfig.Datadog))
log.FromContext(ctx).Debugf("Configured Datadog metrics: pushing to %s once every %s",
metricsConfig.Datadog.Address, metricsConfig.Datadog.PushInterval)
}
if metricsConfig.StatsD != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "statsd"))
registries = append(registries, metrics.RegisterStatsd(ctx, metricsConfig.StatsD))
log.FromContext(ctx).Debugf("Configured StatsD metrics: pushing to %s once every %s",
metricsConfig.StatsD.Address, metricsConfig.StatsD.PushInterval)
}
if metricsConfig.InfluxDB != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "influxdb"))
registries = append(registries, metrics.RegisterInfluxDB(ctx, metricsConfig.InfluxDB))
log.FromContext(ctx).Debugf("Configured InfluxDB metrics: pushing to %s once every %s",
metricsConfig.InfluxDB.Address, metricsConfig.InfluxDB.PushInterval)
}
return metrics.NewMultiRegistry(registries)
}
func setupAccessLog(conf *types.AccessLog) *accesslog.Handler {
if conf == nil {
return nil
}
accessLoggerMiddleware, err := accesslog.NewHandler(conf)
if err != nil {
log.WithoutContext().Warnf("Unable to create access logger : %v", err)
return nil
}
return accessLoggerMiddleware
}
func configureLogging(staticConfiguration *static.Configuration) {
// configure default log flags
stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags)

View file

@ -116,3 +116,25 @@ metrics:
--entryPoints.metrics.address=":8082"
--metrics.prometheus.entryPoint="metrics"
```
#### `manualRouting`
_Optional, Default=false_
If `manualRouting` is `true`, it disables the default internal router in order to allow one to create a custom router for the `prometheus@internal` service.
```toml tab="File (TOML)"
[metrics]
[metrics.prometheus]
manualRouting = true
```
```yaml tab="File (YAML)"
metrics:
prometheus:
manualRouting: true
```
```bash tab="CLI"
--metrics.prometheus.manualrouting=true
```

View file

@ -58,3 +58,23 @@ ping:
--entryPoints.ping.address=":8082"
--ping.entryPoint="ping"
```
#### `manualRouting`
_Optional, Default=false_
If `manualRouting` is `true`, it disables the default internal router in order to allow one to create a custom router for the `ping@internal` service.
```toml tab="File (TOML)"
[ping]
manualRouting = true
```
```yaml tab="File (YAML)"
ping:
manualRouting: true
```
```bash tab="CLI"
--ping.manualrouting=true
```

View file

@ -213,6 +213,9 @@ Buckets for latency metrics. (Default: ```0.100000, 0.300000, 1.200000, 5.000000
`--metrics.prometheus.entrypoint`:
EntryPoint (Default: ```traefik```)
`--metrics.prometheus.manualrouting`:
Manual routing (Default: ```false```)
`--metrics.statsd`:
StatsD metrics exporter type. (Default: ```false```)
@ -237,6 +240,9 @@ Enable ping. (Default: ```false```)
`--ping.entrypoint`:
EntryPoint (Default: ```traefik```)
`--ping.manualrouting`:
Manual routing (Default: ```false```)
`--providers.consulcatalog.cache`:
Use local agent caching for catalog reads. (Default: ```false```)

View file

@ -213,6 +213,9 @@ Buckets for latency metrics. (Default: ```0.100000, 0.300000, 1.200000, 5.000000
`TRAEFIK_METRICS_PROMETHEUS_ENTRYPOINT`:
EntryPoint (Default: ```traefik```)
`TRAEFIK_METRICS_PROMETHEUS_MANUALROUTING`:
Manual routing (Default: ```false```)
`TRAEFIK_METRICS_STATSD`:
StatsD metrics exporter type. (Default: ```false```)
@ -237,6 +240,9 @@ Enable ping. (Default: ```false```)
`TRAEFIK_PING_ENTRYPOINT`:
EntryPoint (Default: ```traefik```)
`TRAEFIK_PING_MANUALROUTING`:
Manual routing (Default: ```false```)
`TRAEFIK_PROVIDERS_CONSULCATALOG_CACHE`:
Use local agent caching for catalog reads. (Default: ```false```)

View file

@ -141,6 +141,7 @@
addEntryPointsLabels = true
addServicesLabels = true
entryPoint = "foobar"
manualRouting = true
[metrics.datadog]
address = "foobar"
pushInterval = "10s"
@ -165,6 +166,7 @@
[ping]
entryPoint = "foobar"
manualRouting = true
[log]
level = "foobar"

View file

@ -148,6 +148,7 @@ metrics:
addEntryPointsLabels: true
addServicesLabels: true
entryPoint: foobar
manualRouting: true
datadog:
address: foobar
pushInterval: 42
@ -171,6 +172,7 @@ metrics:
addServicesLabels: true
ping:
entryPoint: foobar
manualRouting: true
log:
level: foobar
filePath: foobar

View file

@ -8,6 +8,7 @@ import (
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"time"
@ -546,8 +547,16 @@ func checkAccessLogExactValuesOutput(c *check.C, values []accessLogValue) int {
func extractLines(c *check.C) []string {
accessLog, err := ioutil.ReadFile(traefikTestAccessLogFile)
c.Assert(err, checker.IsNil)
lines := strings.Split(string(accessLog), "\n")
return lines
var clean []string
for _, line := range lines {
if !strings.Contains(line, "/api/rawdata") {
clean = append(clean, line)
}
}
return clean
}
func checkStatsForLogFile(c *check.C) {
@ -580,28 +589,31 @@ func CheckAccessLogFormat(c *check.C, line string, i int) {
c.Assert(err, checker.IsNil)
c.Assert(results, checker.HasLen, 14)
c.Assert(results[accesslog.OriginStatus], checker.Matches, `^(-|\d{3})$`)
c.Assert(results[accesslog.RequestCount], checker.Equals, fmt.Sprintf("%d", i+1))
c.Assert(results[accesslog.RouterName], checker.Matches, `"rt-.+@docker"`)
c.Assert(results[accesslog.ServiceURL], checker.HasPrefix, "\"http://")
count, _ := strconv.Atoi(results[accesslog.RequestCount])
c.Assert(count, checker.GreaterOrEqualThan, i+1)
c.Assert(results[accesslog.RouterName], checker.Matches, `"(rt-.+@docker|api@internal)"`)
c.Assert(results[accesslog.ServiceURL], checker.HasPrefix, `"http://`)
c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`)
}
func checkAccessLogExactValues(c *check.C, line string, i int, v accessLogValue) {
results, err := accesslog.ParseAccessLog(line)
// c.Assert(nil, checker.Equals, line)
c.Assert(err, checker.IsNil)
c.Assert(results, checker.HasLen, 14)
if len(v.user) > 0 {
c.Assert(results[accesslog.ClientUsername], checker.Equals, v.user)
}
c.Assert(results[accesslog.OriginStatus], checker.Equals, v.code)
c.Assert(results[accesslog.RequestCount], checker.Equals, fmt.Sprintf("%d", i+1))
count, _ := strconv.Atoi(results[accesslog.RequestCount])
c.Assert(count, checker.GreaterOrEqualThan, i+1)
c.Assert(results[accesslog.RouterName], checker.Matches, `^"?`+v.routerName+`.*(@docker)?$`)
c.Assert(results[accesslog.ServiceURL], checker.Matches, `^"?`+v.serviceURL+`.*$`)
c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`)
}
func waitForTraefik(c *check.C, containerName string) {
time.Sleep(1 * time.Second)
// Wait for Traefik to turn ready.
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/api/rawdata", nil)
c.Assert(err, checker.IsNil)

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"net/http"
"os"
"strings"
"time"
"github.com/containous/traefik/v2/integration/try"
@ -70,15 +71,18 @@ func (s *DockerComposeSuite) TestComposeScale(c *check.C) {
err = json.NewDecoder(resp.Body).Decode(&rtconf)
c.Assert(err, checker.IsNil)
// check that we have only one router
c.Assert(rtconf.Routers, checker.HasLen, 1)
// check that we have only three routers (the one from this test + 2 unrelated internal ones)
c.Assert(rtconf.Routers, checker.HasLen, 3)
// check that we have only one service with n servers
// check that we have only one service (not counting the internal ones) with n servers
services := rtconf.Services
c.Assert(services, checker.HasLen, 1)
for k, v := range services {
c.Assert(k, checker.Equals, composeService+"-integrationtest"+composeProject+"@docker")
c.Assert(v.LoadBalancer.Servers, checker.HasLen, serviceCount)
c.Assert(services, checker.HasLen, 3)
for name, service := range services {
if strings.HasSuffix(name, "@internal") {
continue
}
c.Assert(name, checker.Equals, composeService+"-integrationtest"+composeProject+"@docker")
c.Assert(service.LoadBalancer.Servers, checker.HasLen, serviceCount)
// We could break here, but we don't just to keep us honest.
}
}

View file

@ -7,6 +7,7 @@ import (
"io/ioutil"
"net/http"
"os"
"strings"
"syscall"
"time"
@ -155,10 +156,14 @@ func verifyLogLines(c *check.C, fileName string, countInit int, accessLog bool)
line := rotatedLog.Text()
if accessLog {
if len(line) > 0 {
CheckAccessLogFormat(c, line, count)
if !strings.Contains(line, "/api/rawdata") {
CheckAccessLogFormat(c, line, count)
count++
}
}
} else {
count++
}
count++
}
return count

View file

@ -30,6 +30,10 @@ func (s *RestSuite) TestSimpleConfigurationInsecure(c *check.C) {
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("rest@internal"))
c.Assert(err, checker.IsNil)
// Expected a 404 as we did not configure anything.
err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil)
@ -44,15 +48,15 @@ func (s *RestSuite) TestSimpleConfigurationInsecure(c *check.C) {
config: &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"router1": {
"routerHTTP": {
EntryPoints: []string{"web"},
Middlewares: []string{},
Service: "service1",
Service: "serviceHTTP",
Rule: "PathPrefix(`/`)",
},
},
Services: map[string]*dynamic.Service{
"service1": {
"serviceHTTP": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
@ -71,14 +75,14 @@ func (s *RestSuite) TestSimpleConfigurationInsecure(c *check.C) {
config: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"router1": {
"routerTCP": {
EntryPoints: []string{"web"},
Service: "service1",
Service: "serviceTCP",
Rule: "HostSNI(`*`)",
},
},
Services: map[string]*dynamic.TCPService{
"service1": {
"serviceTCP": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
@ -95,17 +99,17 @@ func (s *RestSuite) TestSimpleConfigurationInsecure(c *check.C) {
}
for _, test := range testCase {
json, err := json.Marshal(test.config)
data, err := json.Marshal(test.config)
c.Assert(err, checker.IsNil)
request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", bytes.NewReader(json))
request, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:8080/api/providers/rest", bytes.NewReader(data))
c.Assert(err, checker.IsNil)
response, err := http.DefaultClient.Do(request)
c.Assert(err, checker.IsNil)
c.Assert(response.StatusCode, checker.Equals, http.StatusOK)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains(test.ruleMatch))
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 3*time.Second, try.BodyContains(test.ruleMatch))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusOK))

View file

@ -1,5 +1,33 @@
{
"routers": {
"api@internal": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 9223372036854775806,
"status": "enabled",
"using": [
"traefik"
]
},
"dashboard@internal": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 9223372036854775805,
"status": "enabled",
"using": [
"traefik"
]
},
"default-test.route-6b204d94623b3df4370c@kubernetescrd": {
"entryPoints": [
"web"
@ -31,6 +59,29 @@
}
},
"middlewares": {
"dashboard_redirect@internal": {
"redirectRegex": {
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
"replacement": "${1}/dashboard/",
"permanent": true
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"dashboard_stripprefix@internal": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"default-mychain@kubernetescrd": {
"chain": {
"middlewares": [
@ -52,11 +103,23 @@
}
},
"services": {
"api@internal": {
"status": "enabled",
"usedBy": [
"api@internal"
]
},
"dashboard@internal": {
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"default-test.route-6b204d94623b3df4370c@kubernetescrd": {
"loadBalancer": {
"servers": [
{
"url": "http://10.42.0.2:80"
"url": "http://10.42.0.3:80"
},
{
"url": "http://10.42.0.6:80"
@ -69,7 +132,7 @@
"default-test.route-6b204d94623b3df4370c@kubernetescrd"
],
"serverStatus": {
"http://10.42.0.2:80": "UP",
"http://10.42.0.3:80": "UP",
"http://10.42.0.6:80": "UP"
}
},
@ -77,7 +140,7 @@
"loadBalancer": {
"servers": [
{
"url": "http://10.42.0.2:80"
"url": "http://10.42.0.3:80"
},
{
"url": "http://10.42.0.6:80"
@ -90,7 +153,7 @@
"default-test2.route-23c7f4c450289ee29016@kubernetescrd"
],
"serverStatus": {
"http://10.42.0.2:80": "UP",
"http://10.42.0.3:80": "UP",
"http://10.42.0.6:80": "UP"
}
}
@ -118,7 +181,7 @@
"terminationDelay": 100,
"servers": [
{
"address": "10.42.0.4:8080"
"address": "10.42.0.2:8080"
},
{
"address": "10.42.0.5:8080"

View file

@ -1,5 +1,33 @@
{
"routers": {
"api@internal": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 9223372036854775806,
"status": "enabled",
"using": [
"traefik"
]
},
"dashboard@internal": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 9223372036854775805,
"status": "enabled",
"using": [
"traefik"
]
},
"whoami-test-https-whoami-tls@kubernetes": {
"service": "default-whoami-http",
"rule": "Host(`whoami.test.https`) \u0026\u0026 PathPrefix(`/whoami`)",
@ -29,7 +57,44 @@
]
}
},
"middlewares": {
"dashboard_redirect@internal": {
"redirectRegex": {
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
"replacement": "${1}/dashboard/",
"permanent": true
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"dashboard_stripprefix@internal": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
},
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
}
},
"services": {
"api@internal": {
"status": "enabled",
"usedBy": [
"api@internal"
]
},
"dashboard@internal": {
"status": "enabled",
"usedBy": [
"dashboard@internal"
]
},
"default-whoami-http@kubernetes": {
"loadBalancer": {
"servers": [
@ -37,7 +102,7 @@
"url": "http://10.42.0.2:80"
},
{
"url": "http://10.42.0.4:80"
"url": "http://10.42.0.3:80"
}
],
"passHostHeader": true
@ -50,7 +115,7 @@
],
"serverStatus": {
"http://10.42.0.2:80": "UP",
"http://10.42.0.4:80": "UP"
"http://10.42.0.3:80": "UP"
}
}
}

View file

@ -27,12 +27,6 @@ func (g DashboardHandler) Append(router *mux.Router) {
http.Redirect(response, request, request.Header.Get("X-Forwarded-Prefix")+"/dashboard/", http.StatusFound)
})
router.Methods(http.MethodGet).
Path("/dashboard/status").
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
http.Redirect(response, request, "/dashboard/", http.StatusFound)
})
router.Methods(http.MethodGet).
PathPrefix("/dashboard/").
Handler(http.StripPrefix("/dashboard/", http.FileServer(g.Assets)))

View file

@ -44,23 +44,19 @@ type RunTimeRepresentation struct {
// Handler serves the configuration and status of Traefik on API endpoints.
type Handler struct {
dashboard bool
debug bool
dashboard bool
debug bool
staticConfig static.Configuration
dashboardAssets *assetfs.AssetFS
// runtimeConfiguration is the data set used to create all the data representations exposed by the API.
runtimeConfiguration *runtime.Configuration
staticConfig static.Configuration
// statistics *types.Statistics
// stats *thoasstats.Stats // FIXME stats
// StatsRecorder *middlewares.StatsRecorder // FIXME stats
dashboardAssets *assetfs.AssetFS
}
// NewBuilder returns a http.Handler builder based on runtime.Configuration
func NewBuilder(staticConfig static.Configuration) func(*runtime.Configuration) http.Handler {
return func(configuration *runtime.Configuration) http.Handler {
router := mux.NewRouter()
New(staticConfig, configuration).Append(router)
return router
return New(staticConfig, configuration).createRouter()
}
}
@ -73,8 +69,7 @@ func New(staticConfig static.Configuration, runtimeConfig *runtime.Configuration
}
return &Handler{
dashboard: staticConfig.API.Dashboard,
// statistics: staticConfig.API.Statistics,
dashboard: staticConfig.API.Dashboard,
dashboardAssets: staticConfig.API.DashboardAssets,
runtimeConfiguration: rConfig,
staticConfig: staticConfig,
@ -82,8 +77,10 @@ func New(staticConfig static.Configuration, runtimeConfig *runtime.Configuration
}
}
// Append add api routes on a router
func (h Handler) Append(router *mux.Router) {
// createRouter creates API routes and router.
func (h Handler) createRouter() *mux.Router {
router := mux.NewRouter()
if h.debug {
DebugHandler{}.Append(router)
}
@ -108,15 +105,13 @@ func (h Handler) Append(router *mux.Router) {
router.Methods(http.MethodGet).Path("/api/tcp/services").HandlerFunc(h.getTCPServices)
router.Methods(http.MethodGet).Path("/api/tcp/services/{serviceID}").HandlerFunc(h.getTCPService)
// FIXME stats
// health route
// router.Methods(http.MethodGet).Path("/health").HandlerFunc(p.getHealthHandler)
version.Handler{}.Append(router)
if h.dashboard {
DashboardHandler{Assets: h.dashboardAssets}.Append(router)
}
return router
}
func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.Request) {

View file

@ -11,7 +11,6 @@ import (
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -199,10 +198,7 @@ func TestHandler_EntryPoints(t *testing.T) {
t.Parallel()
handler := New(test.conf, &runtime.Configuration{})
router := mux.NewRouter()
handler.Append(router)
server := httptest.NewServer(router)
server := httptest.NewServer(handler.createRouter())
resp, err := http.DefaultClient.Get(server.URL + test.path)
require.NoError(t, err)

View file

@ -13,7 +13,6 @@ import (
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -813,10 +812,7 @@ func TestHandler_HTTP(t *testing.T) {
rtConf.GetRoutersByEntryPoints(context.Background(), []string{"web"}, false)
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
router := mux.NewRouter()
handler.Append(router)
server := httptest.NewServer(router)
server := httptest.NewServer(handler.createRouter())
resp, err := http.DefaultClient.Get(server.URL + test.path)
require.NoError(t, err)

View file

@ -19,7 +19,6 @@ import (
"github.com/containous/traefik/v2/pkg/provider/rest"
"github.com/containous/traefik/v2/pkg/tracing/jaeger"
"github.com/containous/traefik/v2/pkg/types"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -252,10 +251,7 @@ func TestHandler_Overview(t *testing.T) {
t.Parallel()
handler := New(test.confStatic, &test.confDyn)
router := mux.NewRouter()
handler.Append(router)
server := httptest.NewServer(router)
server := httptest.NewServer(handler.createRouter())
resp, err := http.DefaultClient.Get(server.URL + test.path)
require.NoError(t, err)

View file

@ -11,7 +11,6 @@ import (
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -520,10 +519,7 @@ func TestHandler_TCP(t *testing.T) {
rtConf.GetTCPRoutersByEntryPoints(context.Background(), []string{"web"})
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
router := mux.NewRouter()
handler.Append(router)
server := httptest.NewServer(router)
server := httptest.NewServer(handler.createRouter())
resp, err := http.DefaultClient.Get(server.URL + test.path)
require.NoError(t, err)

View file

@ -11,7 +11,6 @@ import (
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -137,10 +136,7 @@ func TestHandler_RawData(t *testing.T) {
rtConf.PopulateUsedBy()
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
router := mux.NewRouter()
handler.Append(router)
server := httptest.NewServer(router)
server := httptest.NewServer(handler.createRouter())
resp, err := http.DefaultClient.Get(server.URL + test.path)
require.NoError(t, err)

View file

@ -177,9 +177,9 @@ func (c *Configuration) SetEffectiveConfiguration() {
}
if (c.API != nil && c.API.Insecure) ||
(c.Ping != nil && c.Ping.EntryPoint == DefaultInternalEntryPointName) ||
(c.Metrics != nil && c.Metrics.Prometheus != nil && c.Metrics.Prometheus.EntryPoint == DefaultInternalEntryPointName) ||
(c.Providers.Rest != nil) {
(c.Ping != nil && !c.Ping.ManualRouting && c.Ping.EntryPoint == DefaultInternalEntryPointName) ||
(c.Metrics != nil && c.Metrics.Prometheus != nil && !c.Metrics.Prometheus.ManualRouting && c.Metrics.Prometheus.EntryPoint == DefaultInternalEntryPointName) ||
(c.Providers != nil && c.Providers.Rest != nil && c.Providers.Rest.Insecure) {
if _, ok := c.EntryPoints[DefaultInternalEntryPointName]; !ok {
ep := &EntryPoint{Address: ":8080"}
ep.SetDefaults()

View file

@ -2,7 +2,6 @@ package metrics
import (
"context"
"fmt"
"net/http"
"sort"
"strings"
@ -13,7 +12,6 @@ import (
"github.com/containous/traefik/v2/pkg/safe"
"github.com/containous/traefik/v2/pkg/types"
"github.com/go-kit/kit/metrics"
"github.com/gorilla/mux"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
@ -63,11 +61,8 @@ var promState = newPrometheusState()
var promRegistry = stdprometheus.NewRegistry()
// PrometheusHandler exposes Prometheus routes.
type PrometheusHandler struct{}
// Append adds Prometheus routes on a router.
func (h PrometheusHandler) Append(router *mux.Router) {
router.Methods(http.MethodGet).Path("/metrics").Handler(promhttp.HandlerFor(promRegistry, promhttp.HandlerOpts{}))
func PrometheusHandler() http.Handler {
return promhttp.HandlerFor(promRegistry, promhttp.HandlerOpts{})
}
// RegisterPrometheus registers all Prometheus metrics.
@ -216,21 +211,22 @@ func registerPromState(ctx context.Context) 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(dynConf dynamic.Configurations, entryPoints []string) {
func OnConfigurationUpdate(conf dynamic.Configuration, entryPoints []string) {
dynamicConfig := newDynamicConfig()
for _, value := range entryPoints {
dynamicConfig.entryPoints[value] = true
}
for key, config := range dynConf {
for name := range config.HTTP.Routers {
dynamicConfig.routers[fmt.Sprintf("%s@%s", name, key)] = true
}
for serviceName, service := range config.HTTP.Services {
dynamicConfig.services[fmt.Sprintf("%s@%s", serviceName, key)] = make(map[string]bool)
for name := range conf.HTTP.Routers {
dynamicConfig.routers[name] = true
}
for serviceName, service := range conf.HTTP.Services {
dynamicConfig.services[serviceName] = make(map[string]bool)
if service.LoadBalancer != nil {
for _, server := range service.LoadBalancer.Servers {
dynamicConfig.services[fmt.Sprintf("%s@%s", serviceName, key)][server.URL] = true
dynamicConfig.services[serviceName][server.URL] = true
}
}
}

View file

@ -281,20 +281,19 @@ func TestPrometheusMetricRemoval(t *testing.T) {
prometheusRegistry := RegisterPrometheus(context.Background(), &types.Prometheus{AddEntryPointsLabels: true, AddServicesLabels: true})
defer promRegistry.Unregister(promState)
configurations := make(dynamic.Configurations)
configurations["providerName"] = &dynamic.Configuration{
conf := dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(
th.WithRouter("foo",
th.WithRouter("foo@providerName",
th.WithServiceName("bar")),
),
th.WithLoadBalancerServices(th.WithService("bar",
th.WithLoadBalancerServices(th.WithService("bar@providerName",
th.WithServers(th.WithServer("http://localhost:9000"))),
),
),
}
OnConfigurationUpdate(configurations, []string{"entrypoint1"})
OnConfigurationUpdate(conf, []string{"entrypoint1"})
// Register some metrics manually that are not part of the active configuration.
// Those metrics should be part of the /metrics output on the first scrape but

View file

@ -4,14 +4,13 @@ import (
"context"
"fmt"
"net/http"
"github.com/gorilla/mux"
)
// Handler expose ping routes.
type Handler struct {
EntryPoint string `description:"EntryPoint" export:"true" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty"`
terminating bool
EntryPoint string `description:"EntryPoint" export:"true" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty"`
ManualRouting bool `description:"Manual routing" json:"manualRouting,omitempty" toml:"manualRouting,omitempty" yaml:"manualRouting,omitempty"`
terminating bool
}
// SetDefaults sets the default values.
@ -27,15 +26,11 @@ func (h *Handler) WithContext(ctx context.Context) {
}()
}
// Append adds ping routes on a router.
func (h *Handler) Append(router *mux.Router) {
router.Methods(http.MethodGet, http.MethodHead).Path("/ping").
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
statusCode := http.StatusOK
if h.terminating {
statusCode = http.StatusServiceUnavailable
}
response.WriteHeader(statusCode)
fmt.Fprint(response, http.StatusText(statusCode))
})
func (h *Handler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
statusCode := http.StatusOK
if h.terminating {
statusCode = http.StatusServiceUnavailable
}
response.WriteHeader(statusCode)
fmt.Fprint(response, http.StatusText(statusCode))
}

View file

@ -35,8 +35,11 @@ func (c *challengeHTTP) Timeout() (timeout, interval time.Duration) {
return 60 * time.Second, 5 * time.Second
}
// Append adds routes on internal router
func (p *Provider) Append(router *mux.Router) {
// CreateHandler creates a HTTP handler to expose the token for the HTTP challenge.
func (p *Provider) CreateHandler(notFoundHandler http.Handler) http.Handler {
router := mux.NewRouter().SkipClean(true)
router.NotFoundHandler = notFoundHandler
router.Methods(http.MethodGet).
Path(http01.ChallengePath("{token}")).
Handler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
@ -64,6 +67,8 @@ func (p *Provider) Append(router *mux.Router) {
}
rw.WriteHeader(http.StatusNotFound)
}))
return router
}
func getTokenValue(ctx context.Context, token, domain string, store ChallengeStore) []byte {

View file

@ -3,7 +3,6 @@ package rest
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/containous/traefik/v2/pkg/config/dynamic"
@ -23,8 +22,7 @@ type Provider struct {
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
}
func (p *Provider) SetDefaults() {}
var templatesRenderer = render.New(render.Options{Directory: "nowhere"})
@ -33,40 +31,32 @@ func (p *Provider) Init() error {
return nil
}
// Handler creates an http.Handler for the Rest API
func (p *Provider) Handler() http.Handler {
// CreateRouter creates a router for the Rest API
func (p *Provider) CreateRouter() *mux.Router {
router := mux.NewRouter()
p.Append(router)
router.Methods(http.MethodPut).Path("/api/providers/{provider}").Handler(p)
return router
}
// Append add rest provider routes on a router.
func (p *Provider) Append(systemRouter *mux.Router) {
systemRouter.
Methods(http.MethodPut).
Path("/api/providers/{provider}").
HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
if vars["provider"] != "rest" {
response.WriteHeader(http.StatusBadRequest)
fmt.Fprint(response, "Only 'rest' provider can be updated through the REST API")
return
}
func (p *Provider) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
if vars["provider"] != "rest" {
http.Error(rw, "Only 'rest' provider can be updated through the REST API", http.StatusBadRequest)
return
}
configuration := new(dynamic.Configuration)
body, _ := ioutil.ReadAll(request.Body)
configuration := new(dynamic.Configuration)
if err := json.Unmarshal(body, configuration); err != nil {
log.WithoutContext().Errorf("Error parsing configuration %+v", err)
http.Error(response, fmt.Sprintf("%+v", err), http.StatusBadRequest)
return
}
if err := json.NewDecoder(req.Body).Decode(configuration); err != nil {
log.WithoutContext().Errorf("Error parsing configuration %+v", err)
http.Error(rw, fmt.Sprintf("%+v", err), http.StatusBadRequest)
return
}
p.configurationChan <- dynamic.Message{ProviderName: "rest", Configuration: configuration}
if err := templatesRenderer.JSON(response, http.StatusOK, configuration); err != nil {
log.WithoutContext().Error(err)
}
})
p.configurationChan <- dynamic.Message{ProviderName: "rest", Configuration: configuration}
if err := templatesRenderer.JSON(rw, http.StatusOK, configuration); err != nil {
log.WithoutContext().Error(err)
}
}
// Provide allows the provider to provide configurations to traefik

View file

@ -0,0 +1,49 @@
{
"http": {
"routers": {
"api": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 9223372036854775806
},
"dashboard": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 9223372036854775805
}
},
"middlewares": {
"dashboard_redirect": {
"redirectRegex": {
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
"replacement": "${1}/dashboard/",
"permanent": true
}
},
"dashboard_stripprefix": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
}
}
},
"services": {
"api": {},
"dashboard": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,19 @@
{
"http": {
"routers": {
"api": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 9223372036854775806
}
},
"services": {
"api": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,10 @@
{
"http": {
"services": {
"api": {},
"dashboard": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,9 @@
{
"http": {
"services": {
"api": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,76 @@
{
"http": {
"routers": {
"api": {
"entryPoints": [
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"priority": 9223372036854775806
},
"dashboard": {
"entryPoints": [
"traefik"
],
"middlewares": [
"dashboard_redirect@internal",
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"priority": 9223372036854775805
},
"ping": {
"entryPoints": [
"test"
],
"service": "ping@internal",
"rule": "PathPrefix(`/ping`)",
"priority": 9223372036854775807
},
"prometheus": {
"entryPoints": [
"test"
],
"service": "prometheus@internal",
"rule": "PathPrefix(`/metrics`)",
"priority": 9223372036854775807
},
"rest": {
"entryPoints": [
"traefik"
],
"service": "rest@internal",
"rule": "PathPrefix(`/api/providers`)",
"priority": 9223372036854775807
}
},
"middlewares": {
"dashboard_redirect": {
"redirectRegex": {
"regex": "^(http:\\/\\/[^:]+(:\\d+)?)/$",
"replacement": "${1}/dashboard/",
"permanent": true
}
},
"dashboard_stripprefix": {
"stripPrefix": {
"prefixes": [
"/dashboard/",
"/dashboard"
]
}
}
},
"services": {
"api": {},
"dashboard": {},
"ping": {},
"prometheus": {},
"rest": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,13 @@
{
"http": {
"services": {
"api": {},
"dashboard": {},
"ping": {},
"prometheus": {},
"rest": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,9 @@
{
"http": {
"services": {
"ping": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,19 @@
{
"http": {
"routers": {
"ping": {
"entryPoints": [
"test"
],
"service": "ping@internal",
"rule": "PathPrefix(`/ping`)",
"priority": 9223372036854775807
}
},
"services": {
"ping": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,9 @@
{
"http": {
"services": {
"prometheus": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,19 @@
{
"http": {
"routers": {
"prometheus": {
"entryPoints": [
"test"
],
"service": "prometheus@internal",
"rule": "PathPrefix(`/metrics`)",
"priority": 9223372036854775807
}
},
"services": {
"prometheus": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,19 @@
{
"http": {
"routers": {
"rest": {
"entryPoints": [
"traefik"
],
"service": "rest@internal",
"rule": "PathPrefix(`/api/providers`)",
"priority": 9223372036854775807
}
},
"services": {
"rest": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,9 @@
{
"http": {
"services": {
"rest": {}
}
},
"tcp": {},
"tls": {}
}

View file

@ -0,0 +1,156 @@
package traefik
import (
"math"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/safe"
"github.com/containous/traefik/v2/pkg/tls"
)
var _ provider.Provider = (*Provider)(nil)
// Provider is a provider.Provider implementation that provides the internal routers.
type Provider struct {
staticCfg static.Configuration
}
// New creates a new instance of the internal provider.
func New(staticCfg static.Configuration) *Provider {
return &Provider{staticCfg: staticCfg}
}
// Provide allows the provider to provide configurations to traefik using the given configuration channel.
func (i *Provider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Pool) error {
configurationChan <- dynamic.Message{
ProviderName: "internal",
Configuration: i.createConfiguration(),
}
return nil
}
// Init the provider.
func (i *Provider) Init() error {
return nil
}
func (i *Provider) createConfiguration() *dynamic.Configuration {
cfg := &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
},
TCP: &dynamic.TCPConfiguration{
Routers: make(map[string]*dynamic.TCPRouter),
Services: make(map[string]*dynamic.TCPService),
},
TLS: &dynamic.TLSConfiguration{
Stores: make(map[string]tls.Store),
Options: make(map[string]tls.Options),
},
}
i.apiConfiguration(cfg)
i.pingConfiguration(cfg)
i.restConfiguration(cfg)
i.prometheusConfiguration(cfg)
return cfg
}
func (i *Provider) apiConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.API == nil {
return
}
if i.staticCfg.API.Insecure {
cfg.HTTP.Routers["api"] = &dynamic.Router{
EntryPoints: []string{"traefik"},
Service: "api@internal",
Priority: math.MaxInt64 - 1,
Rule: "PathPrefix(`/api`)",
}
if i.staticCfg.API.Dashboard {
cfg.HTTP.Routers["dashboard"] = &dynamic.Router{
EntryPoints: []string{"traefik"},
Service: "dashboard@internal",
Priority: math.MaxInt64 - 2,
Rule: "PathPrefix(`/`)",
Middlewares: []string{"dashboard_redirect@internal", "dashboard_stripprefix@internal"},
}
cfg.HTTP.Middlewares["dashboard_redirect"] = &dynamic.Middleware{
RedirectRegex: &dynamic.RedirectRegex{
Regex: `^(http:\/\/[^:]+(:\d+)?)/$`,
Replacement: "${1}/dashboard/",
Permanent: true,
},
}
cfg.HTTP.Middlewares["dashboard_stripprefix"] = &dynamic.Middleware{
StripPrefix: &dynamic.StripPrefix{Prefixes: []string{"/dashboard/", "/dashboard"}},
}
}
}
cfg.HTTP.Services["api"] = &dynamic.Service{}
if i.staticCfg.API.Dashboard {
cfg.HTTP.Services["dashboard"] = &dynamic.Service{}
}
}
func (i *Provider) pingConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.Ping == nil {
return
}
if !i.staticCfg.Ping.ManualRouting {
cfg.HTTP.Routers["ping"] = &dynamic.Router{
EntryPoints: []string{i.staticCfg.Ping.EntryPoint},
Service: "ping@internal",
Priority: math.MaxInt64,
Rule: "PathPrefix(`/ping`)",
}
}
cfg.HTTP.Services["ping"] = &dynamic.Service{}
}
func (i *Provider) restConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.Providers == nil || i.staticCfg.Providers.Rest == nil {
return
}
if i.staticCfg.Providers.Rest.Insecure {
cfg.HTTP.Routers["rest"] = &dynamic.Router{
EntryPoints: []string{"traefik"},
Service: "rest@internal",
Priority: math.MaxInt64,
Rule: "PathPrefix(`/api/providers`)",
}
}
cfg.HTTP.Services["rest"] = &dynamic.Service{}
}
func (i *Provider) prometheusConfiguration(cfg *dynamic.Configuration) {
if i.staticCfg.Metrics == nil || i.staticCfg.Metrics.Prometheus == nil {
return
}
if !i.staticCfg.Metrics.Prometheus.ManualRouting {
cfg.HTTP.Routers["prometheus"] = &dynamic.Router{
EntryPoints: []string{i.staticCfg.Metrics.Prometheus.EntryPoint},
Service: "prometheus@internal",
Priority: math.MaxInt64,
Rule: "PathPrefix(`/metrics`)",
}
}
cfg.HTTP.Services["prometheus"] = &dynamic.Service{}
}

View file

@ -0,0 +1,199 @@
package traefik
import (
"encoding/json"
"flag"
"io/ioutil"
"path/filepath"
"testing"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/ping"
"github.com/containous/traefik/v2/pkg/provider/rest"
"github.com/containous/traefik/v2/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var updateExpected = flag.Bool("update_expected", false, "Update expected files in fixtures")
func Test_createConfiguration(t *testing.T) {
testCases := []struct {
desc string
staticCfg static.Configuration
}{
{
desc: "full_configuration.json",
staticCfg: static.Configuration{
API: &static.API{
Insecure: true,
Dashboard: true,
},
Ping: &ping.Handler{
EntryPoint: "test",
ManualRouting: false,
},
Providers: &static.Providers{
Rest: &rest.Provider{
Insecure: true,
},
},
Metrics: &types.Metrics{
Prometheus: &types.Prometheus{
EntryPoint: "test",
ManualRouting: false,
},
},
},
},
{
desc: "full_configuration_secure.json",
staticCfg: static.Configuration{
API: &static.API{
Insecure: false,
Dashboard: true,
},
Ping: &ping.Handler{
EntryPoint: "test",
ManualRouting: true,
},
Providers: &static.Providers{
Rest: &rest.Provider{
Insecure: false,
},
},
Metrics: &types.Metrics{
Prometheus: &types.Prometheus{
EntryPoint: "test",
ManualRouting: true,
},
},
},
},
{
desc: "api_insecure_with_dashboard.json",
staticCfg: static.Configuration{
API: &static.API{
Insecure: true,
Dashboard: true,
},
},
},
{
desc: "api_insecure_without_dashboard.json",
staticCfg: static.Configuration{
API: &static.API{
Insecure: true,
Dashboard: false,
},
},
},
{
desc: "api_secure_with_dashboard.json",
staticCfg: static.Configuration{
API: &static.API{
Insecure: false,
Dashboard: true,
},
},
},
{
desc: "api_secure_without_dashboard.json",
staticCfg: static.Configuration{
API: &static.API{
Insecure: false,
Dashboard: false,
},
},
},
{
desc: "ping_simple.json",
staticCfg: static.Configuration{
Ping: &ping.Handler{
EntryPoint: "test",
ManualRouting: false,
},
},
},
{
desc: "ping_custom.json",
staticCfg: static.Configuration{
Ping: &ping.Handler{
EntryPoint: "test",
ManualRouting: true,
},
},
},
{
desc: "rest_insecure.json",
staticCfg: static.Configuration{
Providers: &static.Providers{
Rest: &rest.Provider{
Insecure: true,
},
},
},
},
{
desc: "rest_secure.json",
staticCfg: static.Configuration{
Providers: &static.Providers{
Rest: &rest.Provider{
Insecure: false,
},
},
},
},
{
desc: "prometheus_simple.json",
staticCfg: static.Configuration{
Metrics: &types.Metrics{
Prometheus: &types.Prometheus{
EntryPoint: "test",
ManualRouting: false,
},
},
},
},
{
desc: "prometheus_custom.json",
staticCfg: static.Configuration{
Metrics: &types.Metrics{
Prometheus: &types.Prometheus{
EntryPoint: "test",
ManualRouting: true,
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
provider := Provider{staticCfg: test.staticCfg}
cfg := provider.createConfiguration()
filename := filepath.Join("fixtures", test.desc)
if *updateExpected {
newJSON, err := json.MarshalIndent(cfg, "", " ")
require.NoError(t, err)
err = ioutil.WriteFile(filename, newJSON, 0644)
require.NoError(t, err)
}
expectedJSON, err := ioutil.ReadFile(filename)
require.NoError(t, err)
actualJSON, err := json.MarshalIndent(cfg, "", " ")
require.NoError(t, err)
assert.JSONEq(t, string(expectedJSON), string(actualJSON))
})
}
}

View file

@ -67,7 +67,7 @@ func mergeConfiguration(configurations dynamic.Configurations) dynamic.Configura
}
if len(defaultTLSOptionProviders) == 0 {
conf.TLS.Options["default"] = tls.Options{}
conf.TLS.Options["default"] = tls.DefaultTLSOptions
} else if len(defaultTLSOptionProviders) > 1 {
log.WithoutContext().Errorf("Default TLS Options defined multiple times in %v", defaultTLSOptionProviders)
// We do not set an empty tls.TLS{} as above so that we actually get a "cascading failure" later on,

View file

@ -0,0 +1,238 @@
package server
import (
"encoding/json"
"reflect"
"time"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/safe"
"github.com/eapache/channels"
"github.com/sirupsen/logrus"
)
// ConfigurationWatcher watches configuration changes.
type ConfigurationWatcher struct {
provider provider.Provider
providersThrottleDuration time.Duration
currentConfigurations safe.Safe
configurationChan chan dynamic.Message
configurationValidatedChan chan dynamic.Message
providerConfigUpdateMap map[string]chan dynamic.Message
configurationListeners []func(dynamic.Configuration)
routinesPool *safe.Pool
}
// NewConfigurationWatcher creates a new ConfigurationWatcher.
func NewConfigurationWatcher(routinesPool *safe.Pool, pvd provider.Provider, providersThrottleDuration time.Duration) *ConfigurationWatcher {
watcher := &ConfigurationWatcher{
provider: pvd,
configurationChan: make(chan dynamic.Message, 100),
configurationValidatedChan: make(chan dynamic.Message, 100),
providerConfigUpdateMap: make(map[string]chan dynamic.Message),
providersThrottleDuration: providersThrottleDuration,
routinesPool: routinesPool,
}
currentConfigurations := make(dynamic.Configurations)
watcher.currentConfigurations.Set(currentConfigurations)
return watcher
}
// Start the configuration watcher.
func (c *ConfigurationWatcher) Start() {
c.routinesPool.Go(func(stop chan bool) { c.listenProviders(stop) })
c.routinesPool.Go(func(stop chan bool) { c.listenConfigurations(stop) })
c.startProvider()
}
// Stop the configuration watcher.
func (c *ConfigurationWatcher) Stop() {
close(c.configurationChan)
close(c.configurationValidatedChan)
}
// AddListener adds a new listener function used when new configuration is provided
func (c *ConfigurationWatcher) AddListener(listener func(dynamic.Configuration)) {
if c.configurationListeners == nil {
c.configurationListeners = make([]func(dynamic.Configuration), 0)
}
c.configurationListeners = append(c.configurationListeners, listener)
}
func (c *ConfigurationWatcher) startProvider() {
logger := log.WithoutContext()
jsonConf, err := json.Marshal(c.provider)
if err != nil {
logger.Debugf("Unable to marshal provider configuration %T: %v", c.provider, err)
}
logger.Infof("Starting provider %T %s", c.provider, jsonConf)
currentProvider := c.provider
safe.Go(func() {
err := currentProvider.Provide(c.configurationChan, c.routinesPool)
if err != nil {
logger.Errorf("Error starting provider %T: %s", currentProvider, err)
}
})
}
// listenProviders receives configuration changes from the providers.
// The configuration message then gets passed along a series of check
// to finally end up in a throttler that sends it to listenConfigurations (through c. configurationValidatedChan).
func (c *ConfigurationWatcher) listenProviders(stop chan bool) {
for {
select {
case <-stop:
return
case configMsg, ok := <-c.configurationChan:
if !ok {
return
}
if configMsg.Configuration == nil {
log.WithoutContext().WithField(log.ProviderName, configMsg.ProviderName).
Debug("Received nil configuration from provider, skipping.")
return
}
c.preLoadConfiguration(configMsg)
}
}
}
func (c *ConfigurationWatcher) listenConfigurations(stop chan bool) {
for {
select {
case <-stop:
return
case configMsg, ok := <-c.configurationValidatedChan:
if !ok || configMsg.Configuration == nil {
return
}
c.loadMessage(configMsg)
}
}
}
func (c *ConfigurationWatcher) loadMessage(configMsg dynamic.Message) {
currentConfigurations := c.currentConfigurations.Get().(dynamic.Configurations)
// Copy configurations to new map so we don't change current if LoadConfig fails
newConfigurations := currentConfigurations.DeepCopy()
newConfigurations[configMsg.ProviderName] = configMsg.Configuration
c.currentConfigurations.Set(newConfigurations)
conf := mergeConfiguration(newConfigurations)
for _, listener := range c.configurationListeners {
listener(conf)
}
}
func (c *ConfigurationWatcher) preLoadConfiguration(configMsg dynamic.Message) {
currentConfigurations := c.currentConfigurations.Get().(dynamic.Configurations)
logger := log.WithoutContext().WithField(log.ProviderName, configMsg.ProviderName)
if log.GetLevel() == logrus.DebugLevel {
copyConf := configMsg.Configuration.DeepCopy()
if copyConf.TLS != nil {
copyConf.TLS.Certificates = nil
for _, v := range copyConf.TLS.Stores {
v.DefaultCertificate = nil
}
}
jsonConf, err := json.Marshal(copyConf)
if err != nil {
logger.Errorf("Could not marshal dynamic configuration: %v", err)
logger.Debugf("Configuration received from provider %s: [struct] %#v", configMsg.ProviderName, copyConf)
} else {
logger.Debugf("Configuration received from provider %s: %s", configMsg.ProviderName, string(jsonConf))
}
}
if isEmptyConfiguration(configMsg.Configuration) {
logger.Infof("Skipping empty Configuration for provider %s", configMsg.ProviderName)
return
}
if reflect.DeepEqual(currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
logger.Infof("Skipping same configuration for provider %s", configMsg.ProviderName)
return
}
providerConfigUpdateCh, ok := c.providerConfigUpdateMap[configMsg.ProviderName]
if !ok {
providerConfigUpdateCh = make(chan dynamic.Message)
c.providerConfigUpdateMap[configMsg.ProviderName] = providerConfigUpdateCh
c.routinesPool.Go(func(stop chan bool) {
c.throttleProviderConfigReload(c.providersThrottleDuration, c.configurationValidatedChan, providerConfigUpdateCh, stop)
})
}
providerConfigUpdateCh <- configMsg
}
// throttleProviderConfigReload throttles the configuration reload speed for a single provider.
// It will immediately publish a new configuration and then only publish the next configuration after the throttle duration.
// Note that in the case it receives N new configs in the timeframe of the throttle duration after publishing,
// it will publish the last of the newly received configurations.
func (c *ConfigurationWatcher) throttleProviderConfigReload(throttle time.Duration, publish chan<- dynamic.Message, in <-chan dynamic.Message, stop chan bool) {
ring := channels.NewRingChannel(1)
defer ring.Close()
c.routinesPool.Go(func(stop chan bool) {
for {
select {
case <-stop:
return
case nextConfig := <-ring.Out():
if config, ok := nextConfig.(dynamic.Message); ok {
publish <- config
time.Sleep(throttle)
}
}
}
})
for {
select {
case <-stop:
return
case nextConfig := <-in:
ring.In() <- nextConfig
}
}
}
func isEmptyConfiguration(conf *dynamic.Configuration) bool {
if conf == nil {
return true
}
if conf.TCP == nil {
conf.TCP = &dynamic.TCPConfiguration{}
}
if conf.HTTP == nil {
conf.HTTP = &dynamic.HTTPConfiguration{}
}
httpEmpty := conf.HTTP.Routers == nil && conf.HTTP.Services == nil && conf.HTTP.Middlewares == nil
tlsEmpty := conf.TLS == nil || conf.TLS.Certificates == nil && conf.TLS.Stores == nil && conf.TLS.Options == nil
tcpEmpty := conf.TCP.Routers == nil && conf.TCP.Services == nil
return httpEmpty && tlsEmpty && tcpEmpty
}

View file

@ -0,0 +1,228 @@
package server
import (
"context"
"fmt"
"strconv"
"testing"
"time"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/safe"
th "github.com/containous/traefik/v2/pkg/testhelpers"
"github.com/containous/traefik/v2/pkg/tls"
"github.com/stretchr/testify/assert"
)
type mockProvider struct {
messages []dynamic.Message
wait time.Duration
}
func (p *mockProvider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
for _, message := range p.messages {
configurationChan <- message
wait := p.wait
if wait == 0 {
wait = 20 * time.Millisecond
}
fmt.Println("wait", wait, time.Now().Nanosecond())
time.Sleep(wait)
}
return nil
}
func (p *mockProvider) Init() error {
panic("implement me")
}
func TestNewConfigurationWatcher(t *testing.T) {
routinesPool := safe.NewPool(context.Background())
pvd := &mockProvider{
messages: []dynamic.Message{{
ProviderName: "mock",
Configuration: &dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(
th.WithRouter("test",
th.WithEntryPoints("e"),
th.WithServiceName("scv"))),
),
},
}},
}
watcher := NewConfigurationWatcher(routinesPool, pvd, time.Second)
run := make(chan struct{})
watcher.AddListener(func(conf dynamic.Configuration) {
expected := dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(
th.WithRouter("test@mock",
th.WithEntryPoints("e"),
th.WithServiceName("scv"))),
th.WithMiddlewares(),
th.WithLoadBalancerServices(),
),
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{
"default": {},
},
Stores: map[string]tls.Store{},
},
}
assert.Equal(t, expected, conf)
close(run)
})
watcher.Start()
<-run
}
func TestListenProvidersThrottleProviderConfigReload(t *testing.T) {
routinesPool := safe.NewPool(context.Background())
pvd := &mockProvider{
wait: 10 * time.Millisecond,
}
for i := 0; i < 5; i++ {
pvd.messages = append(pvd.messages, dynamic.Message{
ProviderName: "mock",
Configuration: &dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i))),
th.WithLoadBalancerServices(th.WithService("bar")),
),
},
})
}
watcher := NewConfigurationWatcher(routinesPool, pvd, 30*time.Millisecond)
publishedConfigCount := 0
watcher.AddListener(func(_ dynamic.Configuration) {
publishedConfigCount++
})
watcher.Start()
defer watcher.Stop()
// give some time so that the configuration can be processed
time.Sleep(100 * time.Millisecond)
// after 50 milliseconds 5 new configs were published
// with a throttle duration of 30 milliseconds this means, we should have received 3 new configs
assert.Equal(t, 3, publishedConfigCount, "times configs were published")
}
func TestListenProvidersSkipsEmptyConfigs(t *testing.T) {
routinesPool := safe.NewPool(context.Background())
pvd := &mockProvider{
messages: []dynamic.Message{{ProviderName: "mock"}},
}
watcher := NewConfigurationWatcher(routinesPool, pvd, time.Second)
watcher.AddListener(func(_ dynamic.Configuration) {
t.Error("An empty configuration was published but it should not")
})
watcher.Start()
defer watcher.Stop()
// give some time so that the configuration can be processed
time.Sleep(100 * time.Millisecond)
}
func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) {
routinesPool := safe.NewPool(context.Background())
message := dynamic.Message{
ProviderName: "mock",
Configuration: &dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo")),
th.WithLoadBalancerServices(th.WithService("bar")),
),
},
}
pvd := &mockProvider{
messages: []dynamic.Message{message, message},
}
watcher := NewConfigurationWatcher(routinesPool, pvd, 0)
alreadyCalled := false
watcher.AddListener(func(_ dynamic.Configuration) {
if alreadyCalled {
t.Error("Same configuration should not be published multiple times")
}
alreadyCalled = true
})
watcher.Start()
defer watcher.Stop()
// give some time so that the configuration can be processed
time.Sleep(100 * time.Millisecond)
}
func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
routinesPool := safe.NewPool(context.Background())
configuration := &dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo")),
th.WithLoadBalancerServices(th.WithService("bar")),
),
}
pvd := &mockProvider{
messages: []dynamic.Message{
{ProviderName: "mock", Configuration: configuration},
{ProviderName: "mock2", Configuration: configuration},
},
}
watcher := NewConfigurationWatcher(routinesPool, pvd, 0)
var publishedProviderConfig dynamic.Configuration
watcher.AddListener(func(conf dynamic.Configuration) {
publishedProviderConfig = conf
})
watcher.Start()
defer watcher.Stop()
// give some time so that the configuration can be processed
time.Sleep(100 * time.Millisecond)
expected := dynamic.Configuration{
HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo@mock"), th.WithRouter("foo@mock2")),
th.WithLoadBalancerServices(th.WithService("bar@mock"), th.WithService("bar@mock2")),
th.WithMiddlewares(),
),
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
TLS: &dynamic.TLSConfiguration{
Options: map[string]tls.Options{
"default": {},
},
Stores: map[string]tls.Store{},
},
}
assert.Equal(t, expected, publishedProviderConfig)
}

View file

@ -0,0 +1,115 @@
package internal
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddProviderInContext(t *testing.T) {
testCases := []struct {
desc string
ctx context.Context
name string
expected string
}{
{
desc: "without provider information",
ctx: context.Background(),
name: "test",
expected: "",
},
{
desc: "provider name embedded in element name",
ctx: context.Background(),
name: "test@foo",
expected: "foo",
},
{
desc: "provider name in context",
ctx: context.WithValue(context.Background(), providerKey, "foo"),
name: "test",
expected: "foo",
},
{
desc: "provider name in context and different provider name embedded in element name",
ctx: context.WithValue(context.Background(), providerKey, "foo"),
name: "test@fii",
expected: "fii",
},
{
desc: "provider name in context and same provider name embedded in element name",
ctx: context.WithValue(context.Background(), providerKey, "foo"),
name: "test@foo",
expected: "foo",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
newCtx := AddProviderInContext(test.ctx, test.name)
var providerName string
if name, ok := newCtx.Value(providerKey).(string); ok {
providerName = name
}
assert.Equal(t, test.expected, providerName)
})
}
}
func TestGetQualifiedName(t *testing.T) {
testCases := []struct {
desc string
ctx context.Context
name string
expected string
}{
{
desc: "empty name",
ctx: context.Background(),
name: "",
expected: "",
},
{
desc: "without provider",
ctx: context.Background(),
name: "test",
expected: "test",
},
{
desc: "with explicit provider",
ctx: context.Background(),
name: "test@foo",
expected: "test@foo",
},
{
desc: "with provider in context",
ctx: context.WithValue(context.Background(), providerKey, "foo"),
name: "test",
expected: "test@foo",
},
{
desc: "with provider in context and explicit name",
ctx: context.WithValue(context.Background(), providerKey, "foo"),
name: "test@fii",
expected: "test@fii",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
qualifiedName := GetQualifiedName(test.ctx, test.name)
assert.Equal(t, test.expected, qualifiedName)
})
}
}

View file

@ -0,0 +1,124 @@
package middleware
import (
"context"
"github.com/containous/alice"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/metrics"
"github.com/containous/traefik/v2/pkg/middlewares/accesslog"
metricsmiddleware "github.com/containous/traefik/v2/pkg/middlewares/metrics"
"github.com/containous/traefik/v2/pkg/middlewares/requestdecorator"
mTracing "github.com/containous/traefik/v2/pkg/middlewares/tracing"
"github.com/containous/traefik/v2/pkg/tracing"
"github.com/containous/traefik/v2/pkg/tracing/jaeger"
)
// ChainBuilder Creates a middleware chain by entry point. It is used for middlewares that are created almost systematically and that need to be created before all others.
type ChainBuilder struct {
metricsRegistry metrics.Registry
accessLoggerMiddleware *accesslog.Handler
tracer *tracing.Tracing
requestDecorator *requestdecorator.RequestDecorator
}
// NewChainBuilder Creates a new ChainBuilder.
func NewChainBuilder(staticConfiguration static.Configuration, metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler) *ChainBuilder {
return &ChainBuilder{
metricsRegistry: metricsRegistry,
accessLoggerMiddleware: accessLoggerMiddleware,
tracer: setupTracing(staticConfiguration.Tracing),
requestDecorator: requestdecorator.New(staticConfiguration.HostResolver),
}
}
// Build a middleware chain by entry point.
func (c *ChainBuilder) Build(ctx context.Context, entryPointName string) alice.Chain {
chain := alice.New()
if c.accessLoggerMiddleware != nil {
chain = chain.Append(accesslog.WrapHandler(c.accessLoggerMiddleware))
}
if c.tracer != nil {
chain = chain.Append(mTracing.WrapEntryPointHandler(ctx, c.tracer, entryPointName))
}
if c.metricsRegistry != nil && c.metricsRegistry.IsEpEnabled() {
chain = chain.Append(metricsmiddleware.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName))
}
return chain.Append(requestdecorator.WrapHandler(c.requestDecorator))
}
// Close accessLogger and tracer.
func (c *ChainBuilder) Close() {
if c.accessLoggerMiddleware != nil {
if err := c.accessLoggerMiddleware.Close(); err != nil {
log.WithoutContext().Errorf("Could not close the access log file: %s", err)
}
}
if c.tracer != nil {
c.tracer.Close()
}
}
func setupTracing(conf *static.Tracing) *tracing.Tracing {
if conf == nil {
return nil
}
var backend tracing.Backend
if conf.Jaeger != nil {
backend = conf.Jaeger
}
if conf.Zipkin != nil {
if backend != nil {
log.WithoutContext().Error("Multiple tracing backend are not supported: cannot create Zipkin backend.")
} else {
backend = conf.Zipkin
}
}
if conf.Datadog != nil {
if backend != nil {
log.WithoutContext().Error("Multiple tracing backend are not supported: cannot create Datadog backend.")
} else {
backend = conf.Datadog
}
}
if conf.Instana != nil {
if backend != nil {
log.WithoutContext().Error("Multiple tracing backend are not supported: cannot create Instana backend.")
} else {
backend = conf.Instana
}
}
if conf.Haystack != nil {
if backend != nil {
log.WithoutContext().Error("Multiple tracing backend are not supported: cannot create Haystack backend.")
} else {
backend = conf.Haystack
}
}
if backend == nil {
log.WithoutContext().Debug("Could not initialize tracing, using Jaeger by default")
defaultBackend := &jaeger.Config{}
defaultBackend.SetDefaults()
backend = defaultBackend
}
tracer, err := tracing.NewTracing(conf.ServiceName, conf.SpanNameLimit, backend)
if err != nil {
log.WithoutContext().Warnf("Unable to create tracer: %v", err)
return nil
}
return tracer
}

View file

@ -1,89 +0,0 @@
package router
import (
"context"
"github.com/containous/alice"
"github.com/containous/traefik/v2/pkg/api"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/metrics"
"github.com/containous/traefik/v2/pkg/types"
"github.com/gorilla/mux"
)
// NewRouteAppenderAggregator Creates a new RouteAppenderAggregator
func NewRouteAppenderAggregator(ctx context.Context, conf static.Configuration,
entryPointName string, runtimeConfiguration *runtime.Configuration) *RouteAppenderAggregator {
aggregator := &RouteAppenderAggregator{}
if conf.Ping != nil && conf.Ping.EntryPoint == entryPointName {
aggregator.AddAppender(conf.Ping)
}
if conf.Metrics != nil && conf.Metrics.Prometheus != nil && conf.Metrics.Prometheus.EntryPoint == entryPointName {
aggregator.AddAppender(metrics.PrometheusHandler{})
}
if entryPointName != "traefik" {
return aggregator
}
if conf.Providers != nil && conf.Providers.Rest != nil && conf.Providers.Rest.Insecure {
aggregator.AddAppender(conf.Providers.Rest)
}
if conf.API != nil && conf.API.Insecure {
aggregator.AddAppender(api.New(conf, runtimeConfiguration))
}
return aggregator
}
// RouteAppenderAggregator RouteAppender that aggregate other RouteAppender
type RouteAppenderAggregator struct {
appenders []types.RouteAppender
}
// Append Adds routes to the router
func (r *RouteAppenderAggregator) Append(systemRouter *mux.Router) {
for _, router := range r.appenders {
router.Append(systemRouter)
}
}
// AddAppender adds a router in the aggregator
func (r *RouteAppenderAggregator) AddAppender(router types.RouteAppender) {
r.appenders = append(r.appenders, router)
}
// WithMiddleware router with internal middleware
type WithMiddleware struct {
appender types.RouteAppender
routerMiddlewares *alice.Chain
}
// Append Adds routes to the router
func (wm *WithMiddleware) Append(systemRouter *mux.Router) {
realRouter := systemRouter.PathPrefix("/").Subrouter()
wm.appender.Append(realRouter)
if err := realRouter.Walk(wrapRoute(wm.routerMiddlewares)); err != nil {
log.WithoutContext().Error(err)
}
}
// wrapRoute with middlewares
func wrapRoute(middlewares *alice.Chain) func(*mux.Route, *mux.Router, []*mux.Route) error {
return func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
handler, err := middlewares.Then(route.GetHandler())
if err != nil {
return err
}
route.Handler(handler)
return nil
}
}

View file

@ -1,79 +0,0 @@
package router
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
)
func TestNewRouteAppenderAggregator(t *testing.T) {
testCases := []struct {
desc string
staticConf static.Configuration
expected map[string]int
}{
{
desc: "Secure API",
staticConf: static.Configuration{
Global: &static.Global{},
API: &static.API{
Insecure: false,
},
EntryPoints: static.EntryPoints{
"traefik": {},
},
},
expected: map[string]int{
"/api/providers": http.StatusBadGateway,
},
},
{
desc: "Insecure API",
staticConf: static.Configuration{
Global: &static.Global{},
API: &static.API{
Insecure: true,
},
EntryPoints: static.EntryPoints{
"traefik": {},
},
},
expected: map[string]int{
"/api/rawdata": http.StatusOK,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
ctx := context.Background()
router := NewRouteAppenderAggregator(ctx, test.staticConf, "traefik", nil)
internalMuxRouter := mux.NewRouter()
router.Append(internalMuxRouter)
internalMuxRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadGateway)
})
actual := make(map[string]int)
for calledURL := range test.expected {
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, calledURL, nil)
internalMuxRouter.ServeHTTP(recorder, request)
actual[calledURL] = recorder.Code
}
assert.Equal(t, test.expected, actual)
})
}
}

View file

@ -1,40 +0,0 @@
package router
import (
"context"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/provider/acme"
"github.com/containous/traefik/v2/pkg/types"
)
// NewRouteAppenderFactory Creates a new RouteAppenderFactory
func NewRouteAppenderFactory(staticConfiguration static.Configuration, entryPointName string, acmeProvider []*acme.Provider) *RouteAppenderFactory {
return &RouteAppenderFactory{
staticConfiguration: staticConfiguration,
entryPointName: entryPointName,
acmeProvider: acmeProvider,
}
}
// RouteAppenderFactory A factory of RouteAppender
type RouteAppenderFactory struct {
staticConfiguration static.Configuration
entryPointName string
acmeProvider []*acme.Provider
}
// NewAppender Creates a new RouteAppender
func (r *RouteAppenderFactory) NewAppender(ctx context.Context, runtimeConfiguration *runtime.Configuration) types.RouteAppender {
aggregator := NewRouteAppenderAggregator(ctx, r.staticConfiguration, r.entryPointName, runtimeConfiguration)
for _, p := range r.acmeProvider {
if p != nil && p.HTTPChallenge != nil && p.HTTPChallenge.EntryPoint == r.entryPointName {
aggregator.AddAppender(p)
break
}
}
return aggregator
}

View file

@ -11,41 +11,55 @@ import (
"github.com/containous/traefik/v2/pkg/middlewares/accesslog"
"github.com/containous/traefik/v2/pkg/middlewares/recovery"
"github.com/containous/traefik/v2/pkg/middlewares/tracing"
"github.com/containous/traefik/v2/pkg/responsemodifiers"
"github.com/containous/traefik/v2/pkg/rules"
"github.com/containous/traefik/v2/pkg/server/internal"
"github.com/containous/traefik/v2/pkg/server/middleware"
"github.com/containous/traefik/v2/pkg/server/service"
)
const (
recoveryMiddlewareName = "traefik-internal-recovery"
)
type middlewareBuilder interface {
BuildChain(ctx context.Context, names []string) *alice.Chain
}
type responseModifierBuilder interface {
Build(ctx context.Context, names []string) func(*http.Response) error
}
type serviceManager interface {
BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error)
LaunchHealthCheck()
}
// Manager A route/router manager
type Manager struct {
routerHandlers map[string]http.Handler
serviceManager serviceManager
middlewaresBuilder middlewareBuilder
chainBuilder *middleware.ChainBuilder
modifierBuilder responseModifierBuilder
conf *runtime.Configuration
}
// NewManager Creates a new Manager
func NewManager(conf *runtime.Configuration,
serviceManager *service.Manager,
middlewaresBuilder *middleware.Builder,
modifierBuilder *responsemodifiers.Builder,
serviceManager serviceManager,
middlewaresBuilder middlewareBuilder,
modifierBuilder responseModifierBuilder,
chainBuilder *middleware.ChainBuilder,
) *Manager {
return &Manager{
routerHandlers: make(map[string]http.Handler),
serviceManager: serviceManager,
middlewaresBuilder: middlewaresBuilder,
modifierBuilder: modifierBuilder,
chainBuilder: chainBuilder,
conf: conf,
}
}
// Manager A route/router manager
type Manager struct {
routerHandlers map[string]http.Handler
serviceManager *service.Manager
middlewaresBuilder *middleware.Builder
modifierBuilder *responsemodifiers.Builder
conf *runtime.Configuration
}
func (m *Manager) getHTTPRouters(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*runtime.RouterInfo {
if m.conf != nil {
return m.conf.GetRoutersByEntryPoints(ctx, entryPoints, tls)
@ -79,6 +93,22 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
}
}
for _, entryPointName := range entryPoints {
ctx := log.With(rootCtx, log.Str(log.EntryPointName, entryPointName))
handler, ok := entryPointHandlers[entryPointName]
if !ok || handler == nil {
handler = BuildDefaultHTTPRouter()
}
handlerWithMiddlewares, err := m.chainBuilder.Build(ctx, entryPointName).Then(handler)
if err != nil {
log.FromContext(ctx).Error(err)
continue
}
entryPointHandlers[entryPointName] = handlerWithMiddlewares
}
m.serviceManager.LaunchHealthCheck()
return entryPointHandlers
@ -167,3 +197,8 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
return alice.New().Extend(*mHandler).Append(tHandler).Then(sHandler)
}
// BuildDefaultHTTPRouter creates a default HTTP router.
func BuildDefaultHTTPRouter() http.Handler {
return http.NotFoundHandler()
}

View file

@ -10,6 +10,7 @@ import (
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/middlewares/accesslog"
"github.com/containous/traefik/v2/pkg/middlewares/requestdecorator"
"github.com/containous/traefik/v2/pkg/responsemodifiers"
@ -24,7 +25,7 @@ import (
func TestRouterManager_Get(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
type ExpectedResult struct {
type expectedResult struct {
StatusCode int
RequestHeaders map[string]string
}
@ -35,7 +36,7 @@ func TestRouterManager_Get(t *testing.T) {
serviceConfig map[string]*dynamic.Service
middlewaresConfig map[string]*dynamic.Middleware
entryPoints []string
expected ExpectedResult
expected expectedResult
}{
{
desc: "no middleware",
@ -58,7 +59,7 @@ func TestRouterManager_Get(t *testing.T) {
},
},
entryPoints: []string{"web"},
expected: ExpectedResult{StatusCode: http.StatusOK},
expected: expectedResult{StatusCode: http.StatusOK},
},
{
desc: "no load balancer",
@ -73,7 +74,7 @@ func TestRouterManager_Get(t *testing.T) {
"foo-service": {},
},
entryPoints: []string{"web"},
expected: ExpectedResult{StatusCode: http.StatusNotFound},
expected: expectedResult{StatusCode: http.StatusNotFound},
},
{
desc: "no middleware, default entry point",
@ -95,7 +96,7 @@ func TestRouterManager_Get(t *testing.T) {
},
},
entryPoints: []string{"web"},
expected: ExpectedResult{StatusCode: http.StatusOK},
expected: expectedResult{StatusCode: http.StatusOK},
},
{
desc: "no middleware, no matching",
@ -118,7 +119,7 @@ func TestRouterManager_Get(t *testing.T) {
},
},
entryPoints: []string{"web"},
expected: ExpectedResult{StatusCode: http.StatusNotFound},
expected: expectedResult{StatusCode: http.StatusNotFound},
},
{
desc: "middleware: headers > auth",
@ -154,7 +155,7 @@ func TestRouterManager_Get(t *testing.T) {
},
},
entryPoints: []string{"web"},
expected: ExpectedResult{
expected: expectedResult{
StatusCode: http.StatusUnauthorized,
RequestHeaders: map[string]string{
"X-Apero": "beer",
@ -195,7 +196,7 @@ func TestRouterManager_Get(t *testing.T) {
},
},
entryPoints: []string{"web"},
expected: ExpectedResult{
expected: expectedResult{
StatusCode: http.StatusUnauthorized,
RequestHeaders: map[string]string{
"X-Apero": "",
@ -223,7 +224,7 @@ func TestRouterManager_Get(t *testing.T) {
},
},
entryPoints: []string{"web"},
expected: ExpectedResult{StatusCode: http.StatusOK},
expected: expectedResult{StatusCode: http.StatusOK},
},
{
desc: "no middleware with specified provider name",
@ -246,7 +247,7 @@ func TestRouterManager_Get(t *testing.T) {
},
},
entryPoints: []string{"web"},
expected: ExpectedResult{StatusCode: http.StatusOK},
expected: expectedResult{StatusCode: http.StatusOK},
},
{
desc: "middleware: chain with provider name",
@ -285,7 +286,7 @@ func TestRouterManager_Get(t *testing.T) {
},
},
entryPoints: []string{"web"},
expected: ExpectedResult{
expected: expectedResult{
StatusCode: http.StatusUnauthorized,
RequestHeaders: map[string]string{
"X-Apero": "",
@ -306,10 +307,13 @@ func TestRouterManager_Get(t *testing.T) {
Middlewares: test.middlewaresConfig,
},
})
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil, nil, nil)
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory)
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
@ -407,10 +411,12 @@ func TestAccessLog(t *testing.T) {
},
})
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil, nil, nil)
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory)
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
@ -680,7 +686,6 @@ func TestRuntimeConfiguration(t *testing.T) {
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -693,10 +698,13 @@ func TestRuntimeConfiguration(t *testing.T) {
Middlewares: test.middlewareConfig,
},
})
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil, nil, nil)
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{})
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory)
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
@ -762,10 +770,13 @@ func TestProviderOnMiddlewares(t *testing.T) {
},
},
})
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil, nil, nil)
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{})
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory)
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
@ -790,6 +801,7 @@ func BenchmarkRouterServe(b *testing.B) {
StatusCode: 200,
Body: ioutil.NopCloser(strings.NewReader("")),
}
routersConfig := map[string]*dynamic.Router{
"foo": {
EntryPoints: []string{"web"},
@ -817,10 +829,13 @@ func BenchmarkRouterServe(b *testing.B) {
Middlewares: map[string]*dynamic.Middleware{},
},
})
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil, nil, nil)
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory)
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)
@ -857,7 +872,8 @@ func BenchmarkService(b *testing.B) {
Services: serviceConfig,
},
})
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil, nil, nil)
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil)
w := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)

View file

@ -16,6 +16,11 @@ import (
traefiktls "github.com/containous/traefik/v2/pkg/tls"
)
const (
defaultTLSConfigName = "default"
defaultTLSStoreName = "default"
)
// NewManager Creates a new Manager
func NewManager(conf *runtime.Configuration,
serviceManager *tcpservice.Manager,
@ -88,15 +93,18 @@ type nameAndConfig struct {
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*runtime.TCPRouterInfo, configsHTTP map[string]*runtime.RouterInfo, handlerHTTP http.Handler, handlerHTTPS http.Handler) (*tcp.Router, error) {
router := &tcp.Router{}
router.HTTPHandler(handlerHTTP)
const defaultTLSConfigName = "default"
defaultTLSConf, err := m.tlsManager.Get("default", defaultTLSConfigName)
defaultTLSConf, err := m.tlsManager.Get(defaultTLSStoreName, defaultTLSConfigName)
if err != nil {
log.FromContext(ctx).Errorf("Error during the build of the default TLS configuration: %v", err)
}
router.HTTPSHandler(handlerHTTPS, defaultTLSConf)
if len(configsHTTP) > 0 {
router.AddRouteHTTPTLS("*", defaultTLSConf)
}
// Keyed by domain, then by options reference.
tlsOptionsForHostSNI := map[string]map[string]nameAndConfig{}
for routerHTTPName, routerHTTPConfig := range configsHTTP {
@ -126,12 +134,13 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
tlsOptionsName = internal.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options)
}
tlsConf, err := m.tlsManager.Get("default", tlsOptionsName)
tlsConf, err := m.tlsManager.Get(defaultTLSStoreName, tlsOptionsName)
if err != nil {
routerHTTPConfig.AddError(err, true)
logger.Debug(err)
continue
}
if tlsOptionsForHostSNI[domain] == nil {
tlsOptionsForHostSNI[domain] = make(map[string]nameAndConfig)
}
@ -153,7 +162,9 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
config = v.TLSConfig
break
}
logger.Debugf("Adding route for %s with TLS options %s", hostSNI, optionsName)
router.AddRouteHTTPTLS(hostSNI, config)
} else {
routers := make([]string, 0, len(tlsConfigs))
@ -161,7 +172,9 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false)
routers = append(routers, v.routerName)
}
logger.Warnf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers)
router.AddRouteHTTPTLS(hostSNI, defaultTLSConf)
}
}
@ -216,7 +229,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
tlsOptionsName = internal.GetQualifiedName(ctxRouter, tlsOptionsName)
}
tlsConf, err := m.tlsManager.Get("default", tlsOptionsName)
tlsConf, err := m.tlsManager.Get(defaultTLSStoreName, tlsOptionsName)
if err != nil {
routerConfig.AddError(err, true)
logger.Debug(err)

View file

@ -2,166 +2,47 @@ package server
import (
"context"
"encoding/json"
"net/http"
"os"
"os/signal"
"sync"
"time"
"github.com/containous/traefik/v2/pkg/api"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/metrics"
"github.com/containous/traefik/v2/pkg/middlewares/accesslog"
"github.com/containous/traefik/v2/pkg/middlewares/requestdecorator"
"github.com/containous/traefik/v2/pkg/provider"
"github.com/containous/traefik/v2/pkg/safe"
"github.com/containous/traefik/v2/pkg/tls"
"github.com/containous/traefik/v2/pkg/tracing"
"github.com/containous/traefik/v2/pkg/tracing/jaeger"
"github.com/containous/traefik/v2/pkg/types"
"github.com/containous/traefik/v2/pkg/server/middleware"
)
// Server is the reverse-proxy/load-balancer engine
type Server struct {
entryPointsTCP TCPEntryPoints
configurationChan chan dynamic.Message
configurationValidatedChan chan dynamic.Message
signals chan os.Signal
stopChan chan bool
currentConfigurations safe.Safe
providerConfigUpdateMap map[string]chan dynamic.Message
accessLoggerMiddleware *accesslog.Handler
tracer *tracing.Tracing
routinesPool *safe.Pool
defaultRoundTripper http.RoundTripper
metricsRegistry metrics.Registry
provider provider.Provider
configurationListeners []func(dynamic.Configuration)
requestDecorator *requestdecorator.RequestDecorator
providersThrottleDuration time.Duration
tlsManager *tls.Manager
api func(configuration *runtime.Configuration) http.Handler
restHandler http.Handler
}
watcher *ConfigurationWatcher
tcpEntryPoints TCPEntryPoints
chainBuilder *middleware.ChainBuilder
// RouteAppenderFactory the route appender factory interface
type RouteAppenderFactory interface {
NewAppender(ctx context.Context, runtimeConfiguration *runtime.Configuration) types.RouteAppender
}
accessLoggerMiddleware *accesslog.Handler
func setupTracing(conf *static.Tracing) tracing.Backend {
var backend tracing.Backend
signals chan os.Signal
stopChan chan bool
if conf.Jaeger != nil {
backend = conf.Jaeger
}
if conf.Zipkin != nil {
if backend != nil {
log.WithoutContext().Error("Multiple tracing backend are not supported: cannot create Zipkin backend.")
} else {
backend = conf.Zipkin
}
}
if conf.Datadog != nil {
if backend != nil {
log.WithoutContext().Error("Multiple tracing backend are not supported: cannot create Datadog backend.")
} else {
backend = conf.Datadog
}
}
if conf.Instana != nil {
if backend != nil {
log.WithoutContext().Error("Multiple tracing backend are not supported: cannot create Instana backend.")
} else {
backend = conf.Instana
}
}
if conf.Haystack != nil {
if backend != nil {
log.WithoutContext().Error("Multiple tracing backend are not supported: cannot create Haystack backend.")
} else {
backend = conf.Haystack
}
}
if backend == nil {
log.WithoutContext().Debug("Could not initialize tracing, use Jaeger by default")
bcd := &jaeger.Config{}
bcd.SetDefaults()
backend = bcd
}
return backend
routinesPool *safe.Pool
}
// NewServer returns an initialized Server.
func NewServer(staticConfiguration static.Configuration, provider provider.Provider, entryPoints TCPEntryPoints, tlsManager *tls.Manager) *Server {
server := &Server{}
if staticConfiguration.API != nil {
server.api = api.NewBuilder(staticConfiguration)
func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, watcher *ConfigurationWatcher,
chainBuilder *middleware.ChainBuilder, accessLoggerMiddleware *accesslog.Handler) *Server {
srv := &Server{
watcher: watcher,
tcpEntryPoints: entryPoints,
chainBuilder: chainBuilder,
accessLoggerMiddleware: accessLoggerMiddleware,
signals: make(chan os.Signal, 1),
stopChan: make(chan bool, 1),
routinesPool: routinesPool,
}
if staticConfiguration.Providers != nil && staticConfiguration.Providers.Rest != nil {
server.restHandler = staticConfiguration.Providers.Rest.Handler()
}
srv.configureSignals()
server.provider = provider
server.entryPointsTCP = entryPoints
server.configurationChan = make(chan dynamic.Message, 100)
server.configurationValidatedChan = make(chan dynamic.Message, 100)
server.signals = make(chan os.Signal, 1)
server.stopChan = make(chan bool, 1)
server.configureSignals()
currentConfigurations := make(dynamic.Configurations)
server.currentConfigurations.Set(currentConfigurations)
server.providerConfigUpdateMap = make(map[string]chan dynamic.Message)
server.tlsManager = tlsManager
if staticConfiguration.Providers != nil {
server.providersThrottleDuration = time.Duration(staticConfiguration.Providers.ProvidersThrottleDuration)
}
transport, err := createHTTPTransport(staticConfiguration.ServersTransport)
if err != nil {
log.WithoutContext().Errorf("Could not configure HTTP Transport, fallbacking on default transport: %v", err)
server.defaultRoundTripper = http.DefaultTransport
} else {
server.defaultRoundTripper = transport
}
server.routinesPool = safe.NewPool(context.Background())
if staticConfiguration.Tracing != nil {
tracingBackend := setupTracing(staticConfiguration.Tracing)
if tracingBackend != nil {
server.tracer, err = tracing.NewTracing(staticConfiguration.Tracing.ServiceName, staticConfiguration.Tracing.SpanNameLimit, tracingBackend)
if err != nil {
log.WithoutContext().Warnf("Unable to create tracer: %v", err)
}
}
}
server.requestDecorator = requestdecorator.New(staticConfiguration.HostResolver)
server.metricsRegistry = registerMetricClients(staticConfiguration.Metrics)
if staticConfiguration.AccessLog != nil {
var err error
server.accessLoggerMiddleware, err = accesslog.NewHandler(staticConfiguration.AccessLog)
if err != nil {
log.WithoutContext().Warnf("Unable to create access logger : %v", err)
}
}
return server
return srv
}
// Start starts the server and Stop/Close it when context is Done
@ -175,20 +56,15 @@ func (s *Server) Start(ctx context.Context) {
s.Stop()
}()
s.startTCPServers()
s.routinesPool.Go(func(stop chan bool) {
s.listenProviders(stop)
})
s.routinesPool.Go(func(stop chan bool) {
s.listenConfigurations(stop)
})
s.startProvider()
s.tcpEntryPoints.Start()
s.watcher.Start()
s.routinesPool.Go(func(stop chan bool) {
s.listenSignals(stop)
})
}
// Wait blocks until server is shutted down.
// Wait blocks until the server shutdown.
func (s *Server) Wait() {
<-s.stopChan
}
@ -197,25 +73,15 @@ func (s *Server) Wait() {
func (s *Server) Stop() {
defer log.WithoutContext().Info("Server stopped")
var wg sync.WaitGroup
for epn, ep := range s.entryPointsTCP {
wg.Add(1)
go func(entryPointName string, entryPoint *TCPEntryPoint) {
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
defer wg.Done()
s.tcpEntryPoints.Stop()
entryPoint.Shutdown(ctx)
log.FromContext(ctx).Debugf("Entry point %s closed", entryPointName)
}(epn, ep)
}
wg.Wait()
s.stopChan <- true
}
// Close destroys the server
func (s *Server) Close() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
go func(ctx context.Context) {
<-ctx.Done()
if ctx.Err() == context.Canceled {
@ -226,125 +92,19 @@ func (s *Server) Close() {
}(ctx)
stopMetricsClients()
s.routinesPool.Cleanup()
close(s.configurationChan)
close(s.configurationValidatedChan)
signal.Stop(s.signals)
close(s.signals)
close(s.stopChan)
if s.accessLoggerMiddleware != nil {
if err := s.accessLoggerMiddleware.Close(); err != nil {
log.WithoutContext().Errorf("Could not close the access log file: %s", err)
}
}
if s.tracer != nil {
s.tracer.Close()
}
s.chainBuilder.Close()
cancel()
}
func (s *Server) startTCPServers() {
// Use an empty configuration in order to initialize the default handlers with internal routes
routers := s.loadConfigurationTCP(dynamic.Configurations{})
for entryPointName, router := range routers {
s.entryPointsTCP[entryPointName].switchRouter(router)
}
for entryPointName, serverEntryPoint := range s.entryPointsTCP {
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
go serverEntryPoint.startTCP(ctx)
}
}
func (s *Server) listenProviders(stop chan bool) {
for {
select {
case <-stop:
return
case configMsg, ok := <-s.configurationChan:
if !ok {
return
}
if configMsg.Configuration != nil {
s.preLoadConfiguration(configMsg)
} else {
log.WithoutContext().WithField(log.ProviderName, configMsg.ProviderName).
Debug("Received nil configuration from provider, skipping.")
}
}
}
}
// AddListener adds a new listener function used when new configuration is provided
func (s *Server) AddListener(listener func(dynamic.Configuration)) {
if s.configurationListeners == nil {
s.configurationListeners = make([]func(dynamic.Configuration), 0)
}
s.configurationListeners = append(s.configurationListeners, listener)
}
func (s *Server) startProvider() {
logger := log.WithoutContext()
jsonConf, err := json.Marshal(s.provider)
if err != nil {
logger.Debugf("Unable to marshal provider configuration %T: %v", s.provider, err)
}
logger.Infof("Starting provider %T %s", s.provider, jsonConf)
currentProvider := s.provider
safe.Go(func() {
err := currentProvider.Provide(s.configurationChan, s.routinesPool)
if err != nil {
logger.Errorf("Error starting provider %T: %s", s.provider, err)
}
})
}
func registerMetricClients(metricsConfig *types.Metrics) metrics.Registry {
if metricsConfig == nil {
return metrics.NewVoidRegistry()
}
var registries []metrics.Registry
if metricsConfig.Prometheus != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "prometheus"))
prometheusRegister := metrics.RegisterPrometheus(ctx, metricsConfig.Prometheus)
if prometheusRegister != nil {
registries = append(registries, prometheusRegister)
log.FromContext(ctx).Debug("Configured Prometheus metrics")
}
}
if metricsConfig.Datadog != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "datadog"))
registries = append(registries, metrics.RegisterDatadog(ctx, metricsConfig.Datadog))
log.FromContext(ctx).Debugf("Configured Datadog metrics: pushing to %s once every %s",
metricsConfig.Datadog.Address, metricsConfig.Datadog.PushInterval)
}
if metricsConfig.StatsD != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "statsd"))
registries = append(registries, metrics.RegisterStatsd(ctx, metricsConfig.StatsD))
log.FromContext(ctx).Debugf("Configured StatsD metrics: pushing to %s once every %s",
metricsConfig.StatsD.Address, metricsConfig.StatsD.PushInterval)
}
if metricsConfig.InfluxDB != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "influxdb"))
registries = append(registries, metrics.RegisterInfluxDB(ctx, metricsConfig.InfluxDB))
log.FromContext(ctx).Debugf("Configured InfluxDB metrics: pushing to %s once every %s",
metricsConfig.InfluxDB.Address, metricsConfig.InfluxDB.PushInterval)
}
return metrics.NewMultiRegistry(registries)
}
func stopMetricsClients() {
metrics.StopDatadog()
metrics.StopStatsd()

View file

@ -1,291 +0,0 @@
package server
import (
"context"
"encoding/json"
"net/http"
"reflect"
"time"
"github.com/containous/alice"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/metrics"
"github.com/containous/traefik/v2/pkg/middlewares/accesslog"
metricsmiddleware "github.com/containous/traefik/v2/pkg/middlewares/metrics"
"github.com/containous/traefik/v2/pkg/middlewares/requestdecorator"
"github.com/containous/traefik/v2/pkg/middlewares/tracing"
"github.com/containous/traefik/v2/pkg/responsemodifiers"
"github.com/containous/traefik/v2/pkg/server/middleware"
"github.com/containous/traefik/v2/pkg/server/router"
routertcp "github.com/containous/traefik/v2/pkg/server/router/tcp"
"github.com/containous/traefik/v2/pkg/server/service"
"github.com/containous/traefik/v2/pkg/server/service/tcp"
tcpCore "github.com/containous/traefik/v2/pkg/tcp"
"github.com/eapache/channels"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
// loadConfiguration manages dynamically routers, middlewares, servers and TLS configurations
func (s *Server) loadConfiguration(configMsg dynamic.Message) {
currentConfigurations := s.currentConfigurations.Get().(dynamic.Configurations)
// Copy configurations to new map so we don't change current if LoadConfig fails
newConfigurations := currentConfigurations.DeepCopy()
newConfigurations[configMsg.ProviderName] = configMsg.Configuration
s.metricsRegistry.ConfigReloadsCounter().Add(1)
handlersTCP := s.loadConfigurationTCP(newConfigurations)
for entryPointName, router := range handlersTCP {
s.entryPointsTCP[entryPointName].switchRouter(router)
}
s.metricsRegistry.LastConfigReloadSuccessGauge().Set(float64(time.Now().Unix()))
s.currentConfigurations.Set(newConfigurations)
for _, listener := range s.configurationListeners {
listener(*configMsg.Configuration)
}
if s.metricsRegistry.IsEpEnabled() || s.metricsRegistry.IsSvcEnabled() {
var entrypoints []string
for key := range s.entryPointsTCP {
entrypoints = append(entrypoints, key)
}
metrics.OnConfigurationUpdate(newConfigurations, entrypoints)
}
}
// loadConfigurationTCP returns a new gorilla.mux Route from the specified global configuration and the dynamic
// provider configurations.
func (s *Server) loadConfigurationTCP(configurations dynamic.Configurations) map[string]*tcpCore.Router {
ctx := context.Background()
var entryPoints []string
for entryPointName := range s.entryPointsTCP {
entryPoints = append(entryPoints, entryPointName)
}
conf := mergeConfiguration(configurations)
s.tlsManager.UpdateConfigs(ctx, conf.TLS.Stores, conf.TLS.Options, conf.TLS.Certificates)
rtConf := runtime.NewConfig(conf)
handlersNonTLS, handlersTLS := s.createHTTPHandlers(ctx, rtConf, entryPoints)
routersTCP := s.createTCPRouters(ctx, rtConf, entryPoints, handlersNonTLS, handlersTLS)
rtConf.PopulateUsedBy()
return routersTCP
}
// the given configuration must not be nil. its fields will get mutated.
func (s *Server) createTCPRouters(ctx context.Context, configuration *runtime.Configuration, entryPoints []string, handlers map[string]http.Handler, handlersTLS map[string]http.Handler) map[string]*tcpCore.Router {
if configuration == nil {
return make(map[string]*tcpCore.Router)
}
serviceManager := tcp.NewManager(configuration)
routerManager := routertcp.NewManager(configuration, serviceManager, handlers, handlersTLS, s.tlsManager)
return routerManager.BuildHandlers(ctx, entryPoints)
}
// createHTTPHandlers returns, for the given configuration and entryPoints, the HTTP handlers for non-TLS connections, and for the TLS ones. the given configuration must not be nil. its fields will get mutated.
func (s *Server) createHTTPHandlers(ctx context.Context, configuration *runtime.Configuration, entryPoints []string) (map[string]http.Handler, map[string]http.Handler) {
var apiHandler http.Handler
if s.api != nil {
apiHandler = s.api(configuration)
}
serviceManager := service.NewManager(configuration.Services, s.defaultRoundTripper, s.metricsRegistry, s.routinesPool, apiHandler, s.restHandler)
middlewaresBuilder := middleware.NewBuilder(configuration.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(configuration.Middlewares)
routerManager := router.NewManager(configuration, serviceManager, middlewaresBuilder, responseModifierFactory)
handlersNonTLS := routerManager.BuildHandlers(ctx, entryPoints, false)
handlersTLS := routerManager.BuildHandlers(ctx, entryPoints, true)
routerHandlers := make(map[string]http.Handler)
for _, entryPointName := range entryPoints {
internalMuxRouter := mux.NewRouter().SkipClean(true)
ctx = log.With(ctx, log.Str(log.EntryPointName, entryPointName))
factory := s.entryPointsTCP[entryPointName].RouteAppenderFactory
if factory != nil {
// FIXME remove currentConfigurations
appender := factory.NewAppender(ctx, configuration)
appender.Append(internalMuxRouter)
}
if h, ok := handlersNonTLS[entryPointName]; ok {
internalMuxRouter.NotFoundHandler = h
} else {
internalMuxRouter.NotFoundHandler = buildDefaultHTTPRouter()
}
routerHandlers[entryPointName] = internalMuxRouter
chain := alice.New()
if s.accessLoggerMiddleware != nil {
chain = chain.Append(accesslog.WrapHandler(s.accessLoggerMiddleware))
}
if s.tracer != nil {
chain = chain.Append(tracing.WrapEntryPointHandler(ctx, s.tracer, entryPointName))
}
if s.metricsRegistry.IsEpEnabled() {
chain = chain.Append(metricsmiddleware.WrapEntryPointHandler(ctx, s.metricsRegistry, entryPointName))
}
chain = chain.Append(requestdecorator.WrapHandler(s.requestDecorator))
handler, err := chain.Then(internalMuxRouter.NotFoundHandler)
if err != nil {
log.FromContext(ctx).Error(err)
continue
}
internalMuxRouter.NotFoundHandler = handler
handlerTLS, ok := handlersTLS[entryPointName]
if ok {
handlerTLSWithMiddlewares, err := chain.Then(handlerTLS)
if err != nil {
log.FromContext(ctx).Error(err)
continue
}
handlersTLS[entryPointName] = handlerTLSWithMiddlewares
}
}
return routerHandlers, handlersTLS
}
func isEmptyConfiguration(conf *dynamic.Configuration) bool {
if conf == nil {
return true
}
if conf.TCP == nil {
conf.TCP = &dynamic.TCPConfiguration{}
}
if conf.HTTP == nil {
conf.HTTP = &dynamic.HTTPConfiguration{}
}
return conf.HTTP.Routers == nil &&
conf.HTTP.Services == nil &&
conf.HTTP.Middlewares == nil &&
(conf.TLS == nil || conf.TLS.Certificates == nil && conf.TLS.Stores == nil && conf.TLS.Options == nil) &&
conf.TCP.Routers == nil &&
conf.TCP.Services == nil
}
func (s *Server) preLoadConfiguration(configMsg dynamic.Message) {
s.defaultConfigurationValues(configMsg.Configuration.HTTP)
currentConfigurations := s.currentConfigurations.Get().(dynamic.Configurations)
logger := log.WithoutContext().WithField(log.ProviderName, configMsg.ProviderName)
if log.GetLevel() == logrus.DebugLevel {
copyConf := configMsg.Configuration.DeepCopy()
if copyConf.TLS != nil {
copyConf.TLS.Certificates = nil
for _, v := range copyConf.TLS.Stores {
v.DefaultCertificate = nil
}
}
jsonConf, err := json.Marshal(copyConf)
if err != nil {
logger.Errorf("Could not marshal dynamic configuration: %v", err)
logger.Debugf("Configuration received from provider %s: [struct] %#v", configMsg.ProviderName, copyConf)
} else {
logger.Debugf("Configuration received from provider %s: %s", configMsg.ProviderName, string(jsonConf))
}
}
if isEmptyConfiguration(configMsg.Configuration) {
logger.Infof("Skipping empty Configuration for provider %s", configMsg.ProviderName)
return
}
if reflect.DeepEqual(currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
logger.Infof("Skipping same configuration for provider %s", configMsg.ProviderName)
return
}
providerConfigUpdateCh, ok := s.providerConfigUpdateMap[configMsg.ProviderName]
if !ok {
providerConfigUpdateCh = make(chan dynamic.Message)
s.providerConfigUpdateMap[configMsg.ProviderName] = providerConfigUpdateCh
s.routinesPool.Go(func(stop chan bool) {
s.throttleProviderConfigReload(s.providersThrottleDuration, s.configurationValidatedChan, providerConfigUpdateCh, stop)
})
}
providerConfigUpdateCh <- configMsg
}
func (s *Server) defaultConfigurationValues(configuration *dynamic.HTTPConfiguration) {
// FIXME create a config hook
}
func (s *Server) listenConfigurations(stop chan bool) {
for {
select {
case <-stop:
return
case configMsg, ok := <-s.configurationValidatedChan:
if !ok || configMsg.Configuration == nil {
return
}
s.loadConfiguration(configMsg)
}
}
}
// throttleProviderConfigReload throttles the configuration reload speed for a single provider.
// It will immediately publish a new configuration and then only publish the next configuration after the throttle duration.
// Note that in the case it receives N new configs in the timeframe of the throttle duration after publishing,
// it will publish the last of the newly received configurations.
func (s *Server) throttleProviderConfigReload(throttle time.Duration, publish chan<- dynamic.Message, in <-chan dynamic.Message, stop chan bool) {
ring := channels.NewRingChannel(1)
defer ring.Close()
s.routinesPool.Go(func(stop chan bool) {
for {
select {
case <-stop:
return
case nextConfig := <-ring.Out():
if config, ok := nextConfig.(dynamic.Message); ok {
publish <- config
time.Sleep(throttle)
}
}
}
})
for {
select {
case <-stop:
return
case nextConfig := <-in:
ring.In() <- nextConfig
}
}
}
func buildDefaultHTTPRouter() *mux.Router {
rt := mux.NewRouter()
rt.NotFoundHandler = http.HandlerFunc(http.NotFound)
rt.SkipClean(true)
return rt
}

View file

@ -1,122 +0,0 @@
package server
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
th "github.com/containous/traefik/v2/pkg/testhelpers"
"github.com/stretchr/testify/assert"
)
func TestReuseService(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
defer testServer.Close()
entryPoints := TCPEntryPoints{
"http": &TCPEntryPoint{},
}
staticConfig := static.Configuration{}
dynamicConfigs := th.BuildConfiguration(
th.WithRouters(
th.WithRouter("foo",
th.WithServiceName("bar"),
th.WithRule("Path(`/ok`)")),
th.WithRouter("foo2",
th.WithEntryPoints("http"),
th.WithRule("Path(`/unauthorized`)"),
th.WithServiceName("bar"),
th.WithRouterMiddlewares("basicauth")),
),
th.WithMiddlewares(th.WithMiddleware("basicauth",
th.WithBasicAuth(&dynamic.BasicAuth{Users: []string{"foo:bar"}}),
)),
th.WithLoadBalancerServices(th.WithService("bar",
th.WithServers(th.WithServer(testServer.URL))),
),
)
srv := NewServer(staticConfig, nil, entryPoints, nil)
rtConf := runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs})
entrypointsHandlers, _ := srv.createHTTPHandlers(context.Background(), rtConf, []string{"http"})
// Test that the /ok path returns a status 200.
responseRecorderOk := &httptest.ResponseRecorder{}
requestOk := httptest.NewRequest(http.MethodGet, testServer.URL+"/ok", nil)
entrypointsHandlers["http"].ServeHTTP(responseRecorderOk, requestOk)
assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code")
// Test that the /unauthorized path returns a 401 because of
// the basic authentication defined on the frontend.
responseRecorderUnauthorized := &httptest.ResponseRecorder{}
requestUnauthorized := httptest.NewRequest(http.MethodGet, testServer.URL+"/unauthorized", nil)
entrypointsHandlers["http"].ServeHTTP(responseRecorderUnauthorized, requestUnauthorized)
assert.Equal(t, http.StatusUnauthorized, responseRecorderUnauthorized.Result().StatusCode, "status code")
}
func TestThrottleProviderConfigReload(t *testing.T) {
throttleDuration := 30 * time.Millisecond
publishConfig := make(chan dynamic.Message)
providerConfig := make(chan dynamic.Message)
stop := make(chan bool)
defer func() {
stop <- true
}()
staticConfiguration := static.Configuration{}
server := NewServer(staticConfiguration, nil, nil, nil)
go server.throttleProviderConfigReload(throttleDuration, publishConfig, providerConfig, stop)
publishedConfigCount := 0
stopConsumeConfigs := make(chan bool)
go func() {
for {
select {
case <-stop:
return
case <-stopConsumeConfigs:
return
case <-publishConfig:
publishedConfigCount++
}
}
}()
// publish 5 new configs, one new config each 10 milliseconds
for i := 0; i < 5; i++ {
providerConfig <- dynamic.Message{}
time.Sleep(10 * time.Millisecond)
}
// after 50 milliseconds 5 new configs were published
// with a throttle duration of 30 milliseconds this means, we should have received 2 new configs
assert.Equal(t, 2, publishedConfigCount, "times configs were published")
stopConsumeConfigs <- true
select {
case <-publishConfig:
// There should be exactly one more message that we receive after ~60 milliseconds since the start of the test.
select {
case <-publishConfig:
t.Error("extra config publication found")
case <-time.After(100 * time.Millisecond):
return
}
case <-time.After(100 * time.Millisecond):
t.Error("Last config was not published in time")
}
}

View file

@ -16,6 +16,7 @@ import (
"github.com/containous/traefik/v2/pkg/middlewares"
"github.com/containous/traefik/v2/pkg/middlewares/forwardedheaders"
"github.com/containous/traefik/v2/pkg/safe"
"github.com/containous/traefik/v2/pkg/server/router"
"github.com/containous/traefik/v2/pkg/tcp"
"github.com/sirupsen/logrus"
"golang.org/x/net/http2"
@ -50,11 +51,60 @@ func (h *httpForwarder) Accept() (net.Conn, error) {
// TCPEntryPoints holds a map of TCPEntryPoint (the entrypoint names being the keys)
type TCPEntryPoints map[string]*TCPEntryPoint
// NewTCPEntryPoints creates a new TCPEntryPoints.
func NewTCPEntryPoints(staticConfiguration static.Configuration) (TCPEntryPoints, error) {
serverEntryPointsTCP := make(TCPEntryPoints)
for entryPointName, config := range staticConfiguration.EntryPoints {
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
var err error
serverEntryPointsTCP[entryPointName], err = NewTCPEntryPoint(ctx, config)
if err != nil {
return nil, fmt.Errorf("error while building entryPoint %s: %v", entryPointName, err)
}
}
return serverEntryPointsTCP, nil
}
// Start the server entry points.
func (eps TCPEntryPoints) Start() {
for entryPointName, serverEntryPoint := range eps {
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
go serverEntryPoint.StartTCP(ctx)
}
}
// Stop the server entry points.
func (eps TCPEntryPoints) Stop() {
var wg sync.WaitGroup
for epn, ep := range eps {
wg.Add(1)
go func(entryPointName string, entryPoint *TCPEntryPoint) {
defer wg.Done()
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
entryPoint.Shutdown(ctx)
log.FromContext(ctx).Debugf("Entry point %s closed", entryPointName)
}(epn, ep)
}
wg.Wait()
}
// Switch the TCP routers.
func (eps TCPEntryPoints) Switch(routersTCP map[string]*tcp.Router) {
for entryPointName, rt := range routersTCP {
eps[entryPointName].SwitchRouter(rt)
}
}
// TCPEntryPoint is the TCP server
type TCPEntryPoint struct {
listener net.Listener
switcher *tcp.HandlerSwitcher
RouteAppenderFactory RouteAppenderFactory
transportConfiguration *static.EntryPointsTransport
tracker *connectionTracker
httpServer *httpServer
@ -99,35 +149,8 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint) (*T
}, nil
}
// writeCloserWrapper wraps together a connection, and the concrete underlying
// connection type that was found to satisfy WriteCloser.
type writeCloserWrapper struct {
net.Conn
writeCloser tcp.WriteCloser
}
func (c *writeCloserWrapper) CloseWrite() error {
return c.writeCloser.CloseWrite()
}
// writeCloser returns the given connection, augmented with the WriteCloser
// implementation, if any was found within the underlying conn.
func writeCloser(conn net.Conn) (tcp.WriteCloser, error) {
switch typedConn := conn.(type) {
case *proxyprotocol.Conn:
underlying, err := writeCloser(typedConn.Conn)
if err != nil {
return nil, err
}
return &writeCloserWrapper{writeCloser: underlying, Conn: typedConn}, nil
case *net.TCPConn:
return typedConn, nil
default:
return nil, fmt.Errorf("unknown connection type %T", typedConn)
}
}
func (e *TCPEntryPoint) startTCP(ctx context.Context) {
// StartTCP starts the TCP server.
func (e *TCPEntryPoint) StartTCP(ctx context.Context) {
logger := log.FromContext(ctx)
logger.Debugf("Start TCP Server")
@ -213,22 +236,55 @@ func (e *TCPEntryPoint) Shutdown(ctx context.Context) {
cancel()
}
func (e *TCPEntryPoint) switchRouter(router *tcp.Router) {
router.HTTPForwarder(e.httpServer.Forwarder)
router.HTTPSForwarder(e.httpsServer.Forwarder)
// SwitchRouter switches the TCP router handler.
func (e *TCPEntryPoint) SwitchRouter(rt *tcp.Router) {
rt.HTTPForwarder(e.httpServer.Forwarder)
httpHandler := router.GetHTTPHandler()
httpsHandler := router.GetHTTPSHandler()
httpHandler := rt.GetHTTPHandler()
if httpHandler == nil {
httpHandler = buildDefaultHTTPRouter()
}
if httpsHandler == nil {
httpsHandler = buildDefaultHTTPRouter()
httpHandler = router.BuildDefaultHTTPRouter()
}
e.httpServer.Switcher.UpdateHandler(httpHandler)
rt.HTTPSForwarder(e.httpsServer.Forwarder)
httpsHandler := rt.GetHTTPSHandler()
if httpsHandler == nil {
httpsHandler = router.BuildDefaultHTTPRouter()
}
e.httpsServer.Switcher.UpdateHandler(httpsHandler)
e.switcher.Switch(router)
e.switcher.Switch(rt)
}
// writeCloserWrapper wraps together a connection, and the concrete underlying
// connection type that was found to satisfy WriteCloser.
type writeCloserWrapper struct {
net.Conn
writeCloser tcp.WriteCloser
}
func (c *writeCloserWrapper) CloseWrite() error {
return c.writeCloser.CloseWrite()
}
// writeCloser returns the given connection, augmented with the WriteCloser
// implementation, if any was found within the underlying conn.
func writeCloser(conn net.Conn) (tcp.WriteCloser, error) {
switch typedConn := conn.(type) {
case *proxyprotocol.Conn:
underlying, err := writeCloser(typedConn.Conn)
if err != nil {
return nil, err
}
return &writeCloserWrapper{writeCloser: underlying, Conn: typedConn}, nil
case *net.TCPConn:
return typedConn, nil
default:
return nil, fmt.Errorf("unknown connection type %T", typedConn)
}
}
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
@ -382,7 +438,7 @@ type httpServer struct {
}
func createHTTPServer(ctx context.Context, ln net.Listener, configuration *static.EntryPoint, withH2c bool) (*httpServer, error) {
httpSwitcher := middlewares.NewHandlerSwitcher(buildDefaultHTTPRouter())
httpSwitcher := middlewares.NewHandlerSwitcher(router.BuildDefaultHTTPRouter())
var handler http.Handler
var err error

View file

@ -28,14 +28,14 @@ func TestShutdownHTTP(t *testing.T) {
})
require.NoError(t, err)
go entryPoint.startTCP(context.Background())
go entryPoint.StartTCP(context.Background())
router := &tcp.Router{}
router.HTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
time.Sleep(1 * time.Second)
rw.WriteHeader(http.StatusOK)
}))
entryPoint.switchRouter(router)
entryPoint.SwitchRouter(router)
conn, err := net.Dial("tcp", entryPoint.listener.Addr().String())
require.NoError(t, err)
@ -66,7 +66,7 @@ func TestShutdownHTTPHijacked(t *testing.T) {
})
require.NoError(t, err)
go entryPoint.startTCP(context.Background())
go entryPoint.StartTCP(context.Background())
router := &tcp.Router{}
router.HTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
@ -79,7 +79,7 @@ func TestShutdownHTTPHijacked(t *testing.T) {
require.NoError(t, err)
}))
entryPoint.switchRouter(router)
entryPoint.SwitchRouter(router)
conn, err := net.Dial("tcp", entryPoint.listener.Addr().String())
require.NoError(t, err)
@ -110,7 +110,7 @@ func TestShutdownTCPConn(t *testing.T) {
})
require.NoError(t, err)
go entryPoint.startTCP(context.Background())
go entryPoint.StartTCP(context.Background())
router := &tcp.Router{}
router.AddCatchAllNoTLS(tcp.HandlerFunc(func(conn tcp.WriteCloser) {
@ -123,7 +123,7 @@ func TestShutdownTCPConn(t *testing.T) {
require.NoError(t, err)
}))
entryPoint.switchRouter(router)
entryPoint.SwitchRouter(router)
conn, err := net.Dial("tcp", entryPoint.listener.Addr().String())
require.NoError(t, err)

View file

@ -1,268 +0,0 @@
package server
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
th "github.com/containous/traefik/v2/pkg/testhelpers"
"github.com/containous/traefik/v2/pkg/types"
"github.com/stretchr/testify/assert"
)
func TestListenProvidersSkipsEmptyConfigs(t *testing.T) {
server, stop, invokeStopChan := setupListenProvider(10 * time.Millisecond)
defer invokeStopChan()
go func() {
for {
select {
case <-stop:
return
case <-server.configurationValidatedChan:
t.Error("An empty configuration was published but it should not")
}
}
}()
server.configurationChan <- dynamic.Message{ProviderName: "kubernetes"}
// give some time so that the configuration can be processed
time.Sleep(100 * time.Millisecond)
}
func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) {
server, stop, invokeStopChan := setupListenProvider(10 * time.Millisecond)
defer invokeStopChan()
publishedConfigCount := 0
go func() {
for {
select {
case <-stop:
return
case conf := <-server.configurationValidatedChan:
// set the current configuration
// this is usually done in the processing part of the published configuration
// so we have to emulate the behavior here
currentConfigurations := server.currentConfigurations.Get().(dynamic.Configurations)
currentConfigurations[conf.ProviderName] = conf.Configuration
server.currentConfigurations.Set(currentConfigurations)
publishedConfigCount++
if publishedConfigCount > 1 {
t.Error("Same configuration should not be published multiple times")
}
}
}
}()
conf := &dynamic.Configuration{}
conf.HTTP = th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo")),
th.WithLoadBalancerServices(th.WithService("bar")),
)
// provide a configuration
server.configurationChan <- dynamic.Message{ProviderName: "kubernetes", Configuration: conf}
// give some time so that the configuration can be processed
time.Sleep(20 * time.Millisecond)
// provide the same configuration a second time
server.configurationChan <- dynamic.Message{ProviderName: "kubernetes", Configuration: conf}
// give some time so that the configuration can be processed
time.Sleep(100 * time.Millisecond)
}
func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
server, stop, invokeStopChan := setupListenProvider(10 * time.Millisecond)
defer invokeStopChan()
publishedProviderConfigCount := map[string]int{}
publishedConfigCount := 0
consumePublishedConfigsDone := make(chan bool)
go func() {
for {
select {
case <-stop:
return
case newConfig := <-server.configurationValidatedChan:
publishedProviderConfigCount[newConfig.ProviderName]++
publishedConfigCount++
if publishedConfigCount == 2 {
consumePublishedConfigsDone <- true
return
}
}
}
}()
conf := &dynamic.Configuration{}
conf.HTTP = th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo")),
th.WithLoadBalancerServices(th.WithService("bar")),
)
server.configurationChan <- dynamic.Message{ProviderName: "kubernetes", Configuration: conf}
server.configurationChan <- dynamic.Message{ProviderName: "marathon", Configuration: conf}
select {
case <-consumePublishedConfigsDone:
if val := publishedProviderConfigCount["kubernetes"]; val != 1 {
t.Errorf("Got %d configuration publication(s) for provider %q, want 1", val, "kubernetes")
}
if val := publishedProviderConfigCount["marathon"]; val != 1 {
t.Errorf("Got %d configuration publication(s) for provider %q, want 1", val, "marathon")
}
case <-time.After(100 * time.Millisecond):
t.Errorf("Published configurations were not consumed in time")
}
}
// setupListenProvider configures the Server and starts listenProviders
func setupListenProvider(throttleDuration time.Duration) (server *Server, stop chan bool, invokeStopChan func()) {
stop = make(chan bool)
invokeStopChan = func() {
stop <- true
}
staticConfiguration := static.Configuration{
Providers: &static.Providers{
ProvidersThrottleDuration: types.Duration(throttleDuration),
},
}
server = NewServer(staticConfiguration, nil, nil, nil)
go server.listenProviders(stop)
return server, stop, invokeStopChan
}
func TestServerResponseEmptyBackend(t *testing.T) {
const requestPath = "/path"
const routeRule = "Path(`" + requestPath + "`)"
testCases := []struct {
desc string
config func(testServerURL string) *dynamic.HTTPConfiguration
expectedStatusCode int
}{
{
desc: "Ok",
config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"),
th.WithServiceName("bar"),
th.WithRule(routeRule)),
),
th.WithLoadBalancerServices(th.WithService("bar",
th.WithServers(th.WithServer(testServerURL))),
),
)
},
expectedStatusCode: http.StatusOK,
},
{
desc: "No Frontend",
config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration()
},
expectedStatusCode: http.StatusNotFound,
},
{
desc: "Empty Backend LB",
config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"),
th.WithServiceName("bar"),
th.WithRule(routeRule)),
),
th.WithLoadBalancerServices(th.WithService("bar")),
)
},
expectedStatusCode: http.StatusServiceUnavailable,
},
{
desc: "Empty Backend LB Sticky",
config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"),
th.WithServiceName("bar"),
th.WithRule(routeRule)),
),
th.WithLoadBalancerServices(th.WithService("bar",
th.WithSticky("test")),
),
)
},
expectedStatusCode: http.StatusServiceUnavailable,
},
{
desc: "Empty Backend LB",
config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"),
th.WithServiceName("bar"),
th.WithRule(routeRule)),
),
th.WithLoadBalancerServices(th.WithService("bar")),
)
},
expectedStatusCode: http.StatusServiceUnavailable,
},
{
desc: "Empty Backend LB Sticky",
config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"),
th.WithServiceName("bar"),
th.WithRule(routeRule)),
),
th.WithLoadBalancerServices(th.WithService("bar",
th.WithSticky("test")),
),
)
},
expectedStatusCode: http.StatusServiceUnavailable,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
defer testServer.Close()
globalConfig := static.Configuration{}
entryPointsConfig := TCPEntryPoints{
"http": &TCPEntryPoint{},
}
srv := NewServer(globalConfig, nil, entryPointsConfig, nil)
rtConf := runtime.NewConfig(dynamic.Configuration{HTTP: test.config(testServer.URL)})
entryPoints, _ := srv.createHTTPHandlers(context.Background(), rtConf, []string{"http"})
responseRecorder := &httptest.ResponseRecorder{}
request := httptest.NewRequest(http.MethodGet, testServer.URL+requestPath, nil)
entryPoints["http"].ServeHTTP(responseRecorder, request)
assert.Equal(t, test.expectedStatusCode, responseRecorder.Result().StatusCode, "status code")
})
}
}

View file

@ -0,0 +1,89 @@
package service
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"github.com/containous/traefik/v2/pkg/config/runtime"
)
type serviceManager interface {
BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error)
LaunchHealthCheck()
}
// InternalHandlers is the internal HTTP handlers builder.
type InternalHandlers struct {
api http.Handler
dashboard http.Handler
rest http.Handler
prometheus http.Handler
ping http.Handler
serviceManager
}
// NewInternalHandlers creates a new InternalHandlers.
func NewInternalHandlers(api func(configuration *runtime.Configuration) http.Handler, configuration *runtime.Configuration, rest http.Handler, metricsHandler http.Handler, pingHandler http.Handler, dashboard http.Handler, next serviceManager) *InternalHandlers {
var apiHandler http.Handler
if api != nil {
apiHandler = api(configuration)
}
return &InternalHandlers{
api: apiHandler,
dashboard: dashboard,
rest: rest,
prometheus: metricsHandler,
ping: pingHandler,
serviceManager: next,
}
}
// BuildHTTP builds an HTTP handler.
func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) {
if strings.HasSuffix(serviceName, "@internal") {
return m.get(serviceName)
}
return m.serviceManager.BuildHTTP(rootCtx, serviceName, responseModifier)
}
func (m *InternalHandlers) get(serviceName string) (http.Handler, error) {
switch serviceName {
case "api@internal":
if m.api == nil {
return nil, errors.New("api is not enabled")
}
return m.api, nil
case "dashboard@internal":
if m.dashboard == nil {
return nil, errors.New("dashboard is not enabled")
}
return m.dashboard, nil
case "rest@internal":
if m.rest == nil {
return nil, errors.New("rest is not enabled")
}
return m.rest, nil
case "ping@internal":
if m.ping == nil {
return nil, errors.New("ping is not enabled")
}
return m.ping, nil
case "prometheus@internal":
if m.prometheus == nil {
return nil, errors.New("prometheus is not enabled")
}
return m.prometheus, nil
default:
return nil, fmt.Errorf("unknown internal service %s", serviceName)
}
}

View file

@ -0,0 +1,61 @@
package service
import (
"net/http"
"github.com/containous/traefik/v2/pkg/api"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/metrics"
"github.com/containous/traefik/v2/pkg/safe"
)
// ManagerFactory a factory of service manager.
type ManagerFactory struct {
metricsRegistry metrics.Registry
defaultRoundTripper http.RoundTripper
api func(configuration *runtime.Configuration) http.Handler
restHandler http.Handler
dashboardHandler http.Handler
metricsHandler http.Handler
pingHandler http.Handler
routinesPool *safe.Pool
}
// NewManagerFactory creates a new ManagerFactory.
func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, metricsRegistry metrics.Registry) *ManagerFactory {
factory := &ManagerFactory{
metricsRegistry: metricsRegistry,
defaultRoundTripper: setupDefaultRoundTripper(staticConfiguration.ServersTransport),
routinesPool: routinesPool,
}
if staticConfiguration.API != nil {
factory.api = api.NewBuilder(staticConfiguration)
if staticConfiguration.API.Dashboard {
factory.dashboardHandler = http.FileServer(staticConfiguration.API.DashboardAssets)
}
}
if staticConfiguration.Providers != nil && staticConfiguration.Providers.Rest != nil {
factory.restHandler = staticConfiguration.Providers.Rest.CreateRouter()
}
if staticConfiguration.Metrics != nil && staticConfiguration.Metrics.Prometheus != nil {
factory.metricsHandler = metrics.PrometheusHandler()
}
factory.pingHandler = staticConfiguration.Ping
return factory
}
// Build creates a service manager.
func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers {
svcManager := NewManager(configuration.Services, f.defaultRoundTripper, f.metricsRegistry, f.routinesPool)
return NewInternalHandlers(f.api, configuration, f.restHandler, f.metricsHandler, f.pingHandler, f.dashboardHandler, svcManager)
}

View file

@ -1,4 +1,4 @@
package server
package service
import (
"crypto/tls"
@ -98,3 +98,13 @@ func createRootCACertPool(rootCAs []traefiktls.FileOrContent) *x509.CertPool {
return roots
}
func setupDefaultRoundTripper(conf *static.ServersTransport) http.RoundTripper {
transport, err := createHTTPTransport(conf)
if err != nil {
log.WithoutContext().Errorf("Could not configure HTTP Transport, fallbacking on default transport: %v", err)
return http.DefaultTransport
}
return transport
}

View file

@ -34,7 +34,7 @@ const (
)
// NewManager creates a new Manager
func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper http.RoundTripper, metricsRegistry metrics.Registry, routinePool *safe.Pool, api http.Handler, rest http.Handler) *Manager {
func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper http.RoundTripper, metricsRegistry metrics.Registry, routinePool *safe.Pool) *Manager {
return &Manager{
routinePool: routinePool,
metricsRegistry: metricsRegistry,
@ -42,8 +42,6 @@ func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper htt
defaultRoundTripper: defaultRoundTripper,
balancers: make(map[string][]healthcheck.BalancerHandler),
configs: configs,
api: api,
rest: rest,
}
}
@ -55,26 +53,10 @@ type Manager struct {
defaultRoundTripper http.RoundTripper
balancers map[string][]healthcheck.BalancerHandler
configs map[string]*runtime.ServiceInfo
api http.Handler
rest http.Handler
}
// BuildHTTP Creates a http.Handler for a service configuration.
func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) {
if serviceName == "api@internal" {
if m.api == nil {
return nil, errors.New("api is not enabled")
}
return m.api, nil
}
if serviceName == "rest@internal" {
if m.rest == nil {
return nil, errors.New("rest is not enabled")
}
return m.rest, nil
}
ctx := log.With(rootCtx, log.Str(log.ServiceName, serviceName))
serviceName = internal.GetQualifiedName(ctx, serviceName)

View file

@ -80,7 +80,7 @@ func TestGetLoadBalancer(t *testing.T) {
}
func TestGetLoadBalancerServiceHandler(t *testing.T) {
sm := NewManager(nil, http.DefaultTransport, nil, nil, nil, nil)
sm := NewManager(nil, http.DefaultTransport, nil, nil)
server1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-From", "first")
@ -332,7 +332,7 @@ func TestManager_Build(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
manager := NewManager(test.configs, http.DefaultTransport, nil, nil, nil, nil)
manager := NewManager(test.configs, http.DefaultTransport, nil, nil)
ctx := context.Background()
if len(test.providerName) > 0 {
@ -346,14 +346,16 @@ func TestManager_Build(t *testing.T) {
}
func TestMultipleTypeOnBuildHTTP(t *testing.T) {
manager := NewManager(map[string]*runtime.ServiceInfo{
services := map[string]*runtime.ServiceInfo{
"test@file": {
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{},
Weighted: &dynamic.WeightedRoundRobin{},
},
},
}, http.DefaultTransport, nil, nil, nil, nil)
}
manager := NewManager(services, http.DefaultTransport, nil, nil)
_, err := manager.BuildHTTP(context.Background(), "test@file", nil)
assert.Error(t, err, "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead")

View file

@ -0,0 +1,70 @@
package server
import (
"context"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/responsemodifiers"
"github.com/containous/traefik/v2/pkg/server/middleware"
"github.com/containous/traefik/v2/pkg/server/router"
routertcp "github.com/containous/traefik/v2/pkg/server/router/tcp"
"github.com/containous/traefik/v2/pkg/server/service"
"github.com/containous/traefik/v2/pkg/server/service/tcp"
tcpCore "github.com/containous/traefik/v2/pkg/tcp"
"github.com/containous/traefik/v2/pkg/tls"
)
// TCPRouterFactory the factory of TCP routers.
type TCPRouterFactory struct {
entryPoints []string
managerFactory *service.ManagerFactory
chainBuilder *middleware.ChainBuilder
tlsManager *tls.Manager
}
// NewTCPRouterFactory creates a new TCPRouterFactory
func NewTCPRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, chainBuilder *middleware.ChainBuilder) *TCPRouterFactory {
var entryPoints []string
for name := range staticConfiguration.EntryPoints {
entryPoints = append(entryPoints, name)
}
return &TCPRouterFactory{
entryPoints: entryPoints,
managerFactory: managerFactory,
tlsManager: tlsManager,
chainBuilder: chainBuilder,
}
}
// CreateTCPRouters creates new TCPRouters
func (f *TCPRouterFactory) CreateTCPRouters(conf dynamic.Configuration) map[string]*tcpCore.Router {
ctx := context.Background()
rtConf := runtime.NewConfig(conf)
// HTTP
serviceManager := f.managerFactory.Build(rtConf)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, f.chainBuilder)
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPoints, false)
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPoints, true)
// TCP
svcTCPManager := tcp.NewManager(rtConf)
rtTCPManager := routertcp.NewManager(rtConf, svcTCPManager, handlersNonTLS, handlersTLS, f.tlsManager)
routersTCP := rtTCPManager.BuildHandlers(ctx, f.entryPoints)
rtConf.PopulateUsedBy()
return routersTCP
}

View file

@ -0,0 +1,234 @@
package server
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/metrics"
"github.com/containous/traefik/v2/pkg/server/middleware"
"github.com/containous/traefik/v2/pkg/server/service"
th "github.com/containous/traefik/v2/pkg/testhelpers"
"github.com/containous/traefik/v2/pkg/tls"
"github.com/stretchr/testify/assert"
)
func TestReuseService(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
defer testServer.Close()
staticConfig := static.Configuration{
EntryPoints: map[string]*static.EntryPoint{
"http": {},
},
}
dynamicConfigs := th.BuildConfiguration(
th.WithRouters(
th.WithRouter("foo",
th.WithServiceName("bar"),
th.WithRule("Path(`/ok`)")),
th.WithRouter("foo2",
th.WithEntryPoints("http"),
th.WithRule("Path(`/unauthorized`)"),
th.WithServiceName("bar"),
th.WithRouterMiddlewares("basicauth")),
),
th.WithMiddlewares(th.WithMiddleware("basicauth",
th.WithBasicAuth(&dynamic.BasicAuth{Users: []string{"foo:bar"}}),
)),
th.WithLoadBalancerServices(th.WithService("bar",
th.WithServers(th.WithServer(testServer.URL))),
),
)
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
tlsManager := tls.NewManager()
factory := NewTCPRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil))
entryPointsHandlers := factory.CreateTCPRouters(dynamic.Configuration{HTTP: dynamicConfigs})
// Test that the /ok path returns a status 200.
responseRecorderOk := &httptest.ResponseRecorder{}
requestOk := httptest.NewRequest(http.MethodGet, testServer.URL+"/ok", nil)
entryPointsHandlers["http"].GetHTTPHandler().ServeHTTP(responseRecorderOk, requestOk)
assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code")
// Test that the /unauthorized path returns a 401 because of
// the basic authentication defined on the frontend.
responseRecorderUnauthorized := &httptest.ResponseRecorder{}
requestUnauthorized := httptest.NewRequest(http.MethodGet, testServer.URL+"/unauthorized", nil)
entryPointsHandlers["http"].GetHTTPHandler().ServeHTTP(responseRecorderUnauthorized, requestUnauthorized)
assert.Equal(t, http.StatusUnauthorized, responseRecorderUnauthorized.Result().StatusCode, "status code")
}
func TestServerResponseEmptyBackend(t *testing.T) {
const requestPath = "/path"
const routeRule = "Path(`" + requestPath + "`)"
testCases := []struct {
desc string
config func(testServerURL string) *dynamic.HTTPConfiguration
expectedStatusCode int
}{
{
desc: "Ok",
config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"),
th.WithServiceName("bar"),
th.WithRule(routeRule)),
),
th.WithLoadBalancerServices(th.WithService("bar",
th.WithServers(th.WithServer(testServerURL))),
),
)
},
expectedStatusCode: http.StatusOK,
},
{
desc: "No Frontend",
config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration()
},
expectedStatusCode: http.StatusNotFound,
},
{
desc: "Empty Backend LB",
config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"),
th.WithServiceName("bar"),
th.WithRule(routeRule)),
),
th.WithLoadBalancerServices(th.WithService("bar")),
)
},
expectedStatusCode: http.StatusServiceUnavailable,
},
{
desc: "Empty Backend LB Sticky",
config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"),
th.WithServiceName("bar"),
th.WithRule(routeRule)),
),
th.WithLoadBalancerServices(th.WithService("bar",
th.WithSticky("test")),
),
)
},
expectedStatusCode: http.StatusServiceUnavailable,
},
{
desc: "Empty Backend LB",
config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"),
th.WithServiceName("bar"),
th.WithRule(routeRule)),
),
th.WithLoadBalancerServices(th.WithService("bar")),
)
},
expectedStatusCode: http.StatusServiceUnavailable,
},
{
desc: "Empty Backend LB Sticky",
config: func(testServerURL string) *dynamic.HTTPConfiguration {
return th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo",
th.WithEntryPoints("http"),
th.WithServiceName("bar"),
th.WithRule(routeRule)),
),
th.WithLoadBalancerServices(th.WithService("bar",
th.WithSticky("test")),
),
)
},
expectedStatusCode: http.StatusServiceUnavailable,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
defer testServer.Close()
staticConfig := static.Configuration{
EntryPoints: map[string]*static.EntryPoint{
"http": {},
},
}
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
tlsManager := tls.NewManager()
factory := NewTCPRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil))
entryPointsHandlers := factory.CreateTCPRouters(dynamic.Configuration{HTTP: test.config(testServer.URL)})
responseRecorder := &httptest.ResponseRecorder{}
request := httptest.NewRequest(http.MethodGet, testServer.URL+requestPath, nil)
entryPointsHandlers["http"].GetHTTPHandler().ServeHTTP(responseRecorder, request)
assert.Equal(t, test.expectedStatusCode, responseRecorder.Result().StatusCode, "status code")
})
}
}
func TestInternalServices(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
defer testServer.Close()
staticConfig := static.Configuration{
API: &static.API{},
EntryPoints: map[string]*static.EntryPoint{
"http": {},
},
}
dynamicConfigs := th.BuildConfiguration(
th.WithRouters(
th.WithRouter("foo",
th.WithServiceName("api@internal"),
th.WithRule("PathPrefix(`/api`)")),
),
)
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
tlsManager := tls.NewManager()
factory := NewTCPRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil))
entryPointsHandlers := factory.CreateTCPRouters(dynamic.Configuration{HTTP: dynamicConfigs})
// Test that the /ok path returns a status 200.
responseRecorderOk := &httptest.ResponseRecorder{}
requestOk := httptest.NewRequest(http.MethodGet, testServer.URL+"/api/rawdata", nil)
entryPointsHandlers["http"].GetHTTPHandler().ServeHTTP(responseRecorderOk, requestOk)
assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code")
}

View file

@ -28,7 +28,7 @@ type Router struct {
func (r *Router) ServeTCP(conn WriteCloser) {
// FIXME -- Check if ProxyProtocol changes the first bytes of the request
if r.catchAllNoTLS != nil && len(r.routingTable) == 0 && r.httpsHandler == nil {
if r.catchAllNoTLS != nil && len(r.routingTable) == 0 {
r.catchAllNoTLS.ServeTCP(conn)
return
}
@ -184,6 +184,7 @@ func clientHelloServerName(br *bufio.Reader) (string, bool, string) {
}
return "", false, ""
}
const recordTypeHandshake = 0x16
if hdr[0] != recordTypeHandshake {
// log.Errorf("Error not tls")
@ -196,12 +197,14 @@ func clientHelloServerName(br *bufio.Reader) (string, bool, string) {
log.Errorf("Error while Peeking hello: %s", err)
return "", false, getPeeked(br)
}
recLen := int(hdr[3])<<8 | int(hdr[4]) // ignoring version in hdr[1:3]
helloBytes, err := br.Peek(recordHeaderLen + recLen)
if err != nil {
log.Errorf("Error while Hello: %s", err)
return "", true, getPeeked(br)
}
sni := ""
server := tls.Server(sniSniffConn{r: bytes.NewReader(helloBytes)}, &tls.Config{
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
@ -210,6 +213,7 @@ func clientHelloServerName(br *bufio.Reader) (string, bool, string) {
},
})
_ = server.Handshake()
return sni, true, getPeeked(br)
}

View file

@ -15,6 +15,9 @@ import (
"github.com/sirupsen/logrus"
)
// DefaultTLSOptions the default TLS options.
var DefaultTLSOptions = Options{}
// Manager is the TLS option/store/configuration factory
type Manager struct {
storesConfig map[string]Store
@ -27,7 +30,12 @@ type Manager struct {
// NewManager creates a new Manager
func NewManager() *Manager {
return &Manager{}
return &Manager{
stores: map[string]*CertificateStore{},
configs: map[string]Options{
"default": DefaultTLSOptions,
},
}
}
// UpdateConfigs updates the TLS* configuration options

View file

@ -18,6 +18,7 @@ type Prometheus struct {
AddEntryPointsLabels bool `description:"Enable metrics on entry points." json:"addEntryPointsLabels,omitempty" toml:"addEntryPointsLabels,omitempty" yaml:"addEntryPointsLabels,omitempty" export:"true"`
AddServicesLabels bool `description:"Enable metrics on services." json:"addServicesLabels,omitempty" toml:"addServicesLabels,omitempty" yaml:"addServicesLabels,omitempty" export:"true"`
EntryPoint string `description:"EntryPoint" export:"true" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty"`
ManualRouting bool `description:"Manual routing" json:"manualRouting,omitempty" toml:"manualRouting,omitempty" yaml:"manualRouting,omitempty"`
}
// SetDefaults sets the default values.

View file

@ -69,7 +69,7 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
return nil, fmt.Errorf("failed to load TLS keypair: %v", err)
}
} else {
return nil, fmt.Errorf("tls cert is a file, but tls key is not")
return nil, fmt.Errorf("TLS cert is a file, but tls key is not")
}
} else {
if errKeyIsFile != nil {
@ -83,11 +83,10 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
}
}
TLSConfig := &tls.Config{
return &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caPool,
InsecureSkipVerify: clientTLS.InsecureSkipVerify,
ClientAuth: clientAuth,
}
return TLSConfig, nil
}, nil
}

View file

@ -115,7 +115,7 @@ module.exports = function (ctx) {
supportIE: false,
build: {
publicPath: process.env.APP_PUBLIC_PATH || '/dashboard',
publicPath: process.env.APP_PUBLIC_PATH || '',
env: process.env.APP_ENV === 'development'
? { // staging:
APP_ENV: JSON.stringify(process.env.APP_ENV),

View file

@ -3,7 +3,7 @@
<q-scroll-area :thumb-style="appThumbStyle" style="height:100%;">
<q-card-section>
<div class="row items-start no-wrap">
<div class="col">
<div class="col" v-if="data.type">
<div class="text-subtitle2">TYPE</div>
<q-chip
dense

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" viewBox="0 0 72 72">
<circle cx="36" cy="36" r="36" fill="#dfdfdf"/>
<path d="m20.79 54.774c-4.1802-0.36038-7.3744-1.6213-9.6977-3.8282-2.3638-2.2454-3.378-5.1728-2.9777-8.5946 0.46062-3.9378 2.3319-6.7185 5.6126-8.3408 2.5286-1.2503 5.0511-1.7259 9.1591-1.7269 2.9187-0.0012 5.3644 0.23777 9.0946 0.88675 0.92236 0.16048 1.7209 0.29185 1.7746 0.29196 0.07803 0 0.1399-0.29356 0.30914-1.4672 0.30621-2.1235 0.36109-2.8562 0.27767-3.7069-0.0971-0.99016-0.22383-1.4618-0.59895-2.2291-0.96784-1.9798-3.2653-3.1911-6.9547-3.6667-1.0551-0.13602-3.4903-0.15505-4.5473-0.0356-1.7709 0.20021-2.7591 0.45532-4.963 1.2811-1.2894 0.48314-1.2953 0.48463-1.8096 0.45444-0.88418-0.05181-1.4878-0.45212-1.8749-1.2434-0.15944-0.32586-0.18748-0.46038-0.18748-0.89945 0-0.65913 0.17166-1.009 0.76939-1.5682 1.3404-1.2539 3.596-2.3217 5.8726-2.7802 1.4577-0.29355 2.1753-0.35527 4.0958-0.35232 1.9873 0.0052 3.258 0.11526 5.024 0.44365 2.9374 0.54614 5.1678 1.5088 6.8497 2.9563 0.49904 0.42948 1.2515 1.2748 1.574 1.7681 0.09855 0.15078 0.20008 0.27415 0.2256 0.27415 0.02554 0 0.18857-0.18117 0.36231-0.40254 2.5467-3.2452 6.8359-5.082 11.861-5.0794 3.5543 0 6.8581 0.901 9.3207 2.5369 1.9533 1.2976 3.5402 3.2965 4.1812 5.267 0.36807 1.1314 0.42403 1.5495 0.42742 3.1928 0.0021 1.0225-0.02612 1.7122-0.08315 2.0318-0.57634 3.2298-1.9492 5.4674-4.332 7.0607-1.917 1.2818-4.3245 2.0318-7.4732 2.328-1.585 0.14912-4.4841 0.13504-6.3622-0.03097-1.5939-0.14085-3.5511-0.3887-4.386-0.55536-0.69888-0.13953-3.0921-0.53913-3.1118-0.51957-0.0087 0.0088-0.12256 0.72447-0.25302 1.5908-0.20832 1.3833-0.23783 1.7285-0.2422 2.833-0.0047 1.123 0.01128 1.32 0.14678 1.8383 0.43569 1.6665 1.3958 2.7813 3.1378 3.6431 1.7136 0.8478 3.51 1.2147 6.2243 1.2712 1.2017 0.02528 1.8171 0.0077 2.5689-0.07192 1.8225-0.19325 2.653-0.40528 5.0744-1.2954 0.60308-0.22174 1.2186-0.42349 1.3677-0.44836 0.31967-0.0533 0.91586 0.05677 1.2633 0.23329 0.62718 0.31853 1.1541 1.168 1.1508 1.8552-0.0036 0.80285-0.27442 1.2891-1.0693 1.9215-1.9005 1.5121-4.3271 2.4394-7.2238 2.7606-1.0329 0.11455-3.3912 0.13142-4.5304 0.0325-3.208-0.27863-5.4352-0.82731-7.4706-1.8403-1.5882-0.79041-2.7999-1.771-3.705-2.9982-0.22238-0.30156-0.41127-0.55579-0.41977-0.56509-0.0085-0.0093-0.15631 0.16492-0.32849 0.38701-0.76108 0.98176-1.9249 2.037-3.0623 2.7765-1.642 1.0676-3.9992 1.8821-6.3844 2.206-0.71132 0.09655-3.0714 0.17621-3.6765 0.12405zm4.0958-4.5958c1.9474-0.43158 3.6827-1.4044 4.9426-2.7705 1.1492-1.2461 2.0917-3.1129 2.6105-5.1701 0.2924-1.1597 0.77845-4.3872 0.67071-4.4538-0.04252-0.02632-0.29939-0.06785-0.57075-0.09242-0.27137-0.02477-1.0449-0.11589-1.7189-0.20305-4.0037-0.51781-6.4599-0.719-7.8368-0.6418-2.6178 0.14664-3.6851 0.37518-5.2568 1.1258-0.80521 0.38453-1.361 0.80183-1.8007 1.352-0.63087 0.78938-1.0117 1.5738-1.2771 2.6308-0.19315 0.76915-0.21286 2.7831-0.03359 3.432 0.30199 1.093 0.66048 1.7117 1.4666 2.5308 1.1038 1.1216 2.5589 1.8699 4.3639 2.2441 0.92293 0.19134 0.89562 0.18948 2.4409 0.16621 1.1213-0.01703 1.5408-0.04851 1.9995-0.15006zm25.51-15.133c1.6286-0.1437 2.6301-0.39655 3.8605-0.97459 1.0233-0.48081 1.6181-0.9722 2.1809-1.8019 0.82864-1.2215 1.1513-2.3775 1.143-4.0951-0.0059-1.2242-0.10967-1.7334-0.53228-2.6123-0.94425-1.9636-3.1624-3.3701-6.0716-3.85-0.72595-0.11971-2.6744-0.09845-3.4185 0.03717-1.4315 0.26119-2.6416 0.73727-3.7088 1.4591-1.9717 1.3336-3.3325 3.3439-4.0902 6.0426-0.29764 1.06-0.37698 1.4669-0.64234 3.2941l-0.24478 1.6855 0.32789 0.03611c0.99033 0.10981 3.2235 0.38021 4.5204 0.54739 0.81593 0.10517 1.8173 0.2174 2.2253 0.24944 1.1794 0.09258 3.2939 0.08396 4.4505-0.01807z" fill="#37abc8"/>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB