2019-11-14 16:40:05 +01:00
package server
import (
2020-02-03 17:56:04 +01:00
"context"
2019-11-14 16:40:05 +01:00
"encoding/json"
"reflect"
"time"
"github.com/eapache/channels"
"github.com/sirupsen/logrus"
2020-09-16 15:46:04 +02:00
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/provider"
"github.com/traefik/traefik/v2/pkg/safe"
2021-07-13 14:14:35 +02:00
"github.com/traefik/traefik/v2/pkg/tls"
2019-11-14 16:40:05 +01:00
)
// ConfigurationWatcher watches configuration changes.
type ConfigurationWatcher struct {
provider provider . Provider
2020-03-09 11:12:05 +01:00
defaultEntryPoints [ ] string
2020-03-05 12:46:05 +01:00
2019-11-14 16:40:05 +01:00
providersThrottleDuration time . Duration
currentConfigurations safe . Safe
configurationChan chan dynamic . Message
configurationValidatedChan chan dynamic . Message
providerConfigUpdateMap map [ string ] chan dynamic . Message
2021-02-25 17:20:04 +01:00
requiredProvider string
2019-11-14 16:40:05 +01:00
configurationListeners [ ] func ( dynamic . Configuration )
routinesPool * safe . Pool
}
// NewConfigurationWatcher creates a new ConfigurationWatcher.
2020-03-05 12:46:05 +01:00
func NewConfigurationWatcher (
routinesPool * safe . Pool ,
pvd provider . Provider ,
providersThrottleDuration time . Duration ,
2020-03-09 11:12:05 +01:00
defaultEntryPoints [ ] string ,
2021-02-25 17:20:04 +01:00
requiredProvider string ,
2020-03-05 12:46:05 +01:00
) * ConfigurationWatcher {
2019-11-14 16:40:05 +01:00
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 ,
2020-03-09 11:12:05 +01:00
defaultEntryPoints : defaultEntryPoints ,
2021-02-25 17:20:04 +01:00
requiredProvider : requiredProvider ,
2019-11-14 16:40:05 +01:00
}
currentConfigurations := make ( dynamic . Configurations )
watcher . currentConfigurations . Set ( currentConfigurations )
return watcher
}
// Start the configuration watcher.
func ( c * ConfigurationWatcher ) Start ( ) {
2020-02-03 17:56:04 +01:00
c . routinesPool . GoCtx ( c . listenProviders )
c . routinesPool . GoCtx ( c . listenConfigurations )
2019-11-14 16:40:05 +01:00
c . startProvider ( )
}
// Stop the configuration watcher.
func ( c * ConfigurationWatcher ) Stop ( ) {
close ( c . configurationChan )
close ( c . configurationValidatedChan )
}
2020-05-11 12:06:07 +02:00
// AddListener adds a new listener function used when new configuration is provided.
2019-11-14 16:40:05 +01:00
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 ( )
2022-01-24 05:08:05 -05:00
logger . Infof ( "Starting provider %T" , c . provider )
2019-11-14 16:40:05 +01:00
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).
2020-02-03 17:56:04 +01:00
func ( c * ConfigurationWatcher ) listenProviders ( ctx context . Context ) {
2019-11-14 16:40:05 +01:00
for {
select {
2020-02-03 17:56:04 +01:00
case <- ctx . Done ( ) :
2019-11-14 16:40:05 +01:00
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 )
}
}
}
2020-02-03 17:56:04 +01:00
func ( c * ConfigurationWatcher ) listenConfigurations ( ctx context . Context ) {
2019-11-14 16:40:05 +01:00
for {
select {
2020-02-03 17:56:04 +01:00
case <- ctx . Done ( ) :
2019-11-14 16:40:05 +01:00
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 )
2020-03-09 11:12:05 +01:00
conf := mergeConfiguration ( newConfigurations , c . defaultEntryPoints )
2020-03-05 12:46:05 +01:00
conf = applyModel ( conf )
2019-11-14 16:40:05 +01:00
2021-02-25 17:20:04 +01:00
// We wait for first configuration of the require provider before applying configurations.
if _ , ok := newConfigurations [ c . requiredProvider ] ; c . requiredProvider == "" || ok {
for _ , listener := range c . configurationListeners {
listener ( conf )
}
2019-11-14 16:40:05 +01:00
}
}
func ( c * ConfigurationWatcher ) preLoadConfiguration ( configMsg dynamic . Message ) {
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
2021-07-13 14:14:35 +02:00
if copyConf . TLS . Options != nil {
cleanedOptions := make ( map [ string ] tls . Options , len ( copyConf . TLS . Options ) )
for name , option := range copyConf . TLS . Options {
option . ClientAuth . CAFiles = [ ] tls . FileOrContent { }
cleanedOptions [ name ] = option
}
copyConf . TLS . Options = cleanedOptions
}
2020-02-05 11:46:03 -06:00
for k := range copyConf . TLS . Stores {
st := copyConf . TLS . Stores [ k ]
st . DefaultCertificate = nil
copyConf . TLS . Stores [ k ] = st
2019-11-14 16:40:05 +01:00
}
}
2021-07-13 14:14:35 +02:00
if copyConf . HTTP != nil {
for _ , transport := range copyConf . HTTP . ServersTransports {
transport . Certificates = tls . Certificates { }
transport . RootCAs = [ ] tls . FileOrContent { }
}
}
2019-11-14 16:40:05 +01:00
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
}
providerConfigUpdateCh , ok := c . providerConfigUpdateMap [ configMsg . ProviderName ]
if ! ok {
providerConfigUpdateCh = make ( chan dynamic . Message )
c . providerConfigUpdateMap [ configMsg . ProviderName ] = providerConfigUpdateCh
2020-02-03 17:56:04 +01:00
c . routinesPool . GoCtx ( func ( ctxPool context . Context ) {
c . throttleProviderConfigReload ( ctxPool , c . providersThrottleDuration , c . configurationValidatedChan , providerConfigUpdateCh )
2019-11-14 16:40:05 +01:00
} )
}
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.
2020-02-03 17:56:04 +01:00
func ( c * ConfigurationWatcher ) throttleProviderConfigReload ( ctx context . Context , throttle time . Duration , publish chan <- dynamic . Message , in <- chan dynamic . Message ) {
2019-11-14 16:40:05 +01:00
ring := channels . NewRingChannel ( 1 )
defer ring . Close ( )
2020-02-03 17:56:04 +01:00
c . routinesPool . GoCtx ( func ( ctxPool context . Context ) {
2019-11-14 16:40:05 +01:00
for {
select {
2020-02-03 17:56:04 +01:00
case <- ctxPool . Done ( ) :
2019-11-14 16:40:05 +01:00
return
case nextConfig := <- ring . Out ( ) :
if config , ok := nextConfig . ( dynamic . Message ) ; ok {
publish <- config
time . Sleep ( throttle )
}
}
}
} )
2020-02-10 20:40:06 +00:00
var previousConfig dynamic . Message
2019-11-14 16:40:05 +01:00
for {
select {
2020-02-03 17:56:04 +01:00
case <- ctx . Done ( ) :
2019-11-14 16:40:05 +01:00
return
case nextConfig := <- in :
2020-02-10 20:40:06 +00:00
if reflect . DeepEqual ( previousConfig , nextConfig ) {
logger := log . WithoutContext ( ) . WithField ( log . ProviderName , nextConfig . ProviderName )
2022-01-20 12:36:08 +01:00
logger . Debug ( "Skipping same configuration" )
2020-02-10 20:40:06 +00:00
continue
}
2020-07-02 11:18:04 +02:00
previousConfig = * nextConfig . DeepCopy ( )
ring . In ( ) <- * nextConfig . DeepCopy ( )
2019-11-14 16:40:05 +01:00
}
}
}
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 { }
}
2020-04-16 16:18:04 +02:00
if conf . UDP == nil {
conf . UDP = & dynamic . UDPConfiguration { }
}
2019-11-14 16:40:05 +01:00
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
2021-06-11 15:30:05 +02:00
tcpEmpty := conf . TCP . Routers == nil && conf . TCP . Services == nil && conf . TCP . Middlewares == nil
2020-04-16 16:18:04 +02:00
udpEmpty := conf . UDP . Routers == nil && conf . UDP . Services == nil
2019-11-14 16:40:05 +01:00
2020-04-16 16:18:04 +02:00
return httpEmpty && tlsEmpty && tcpEmpty && udpEmpty
2019-11-14 16:40:05 +01:00
}