traefik/pkg/provider/marathon/marathon.go

222 lines
7.3 KiB
Go
Raw Normal View History

package marathon
2015-09-12 13:10:03 +00:00
2015-09-09 20:39:08 +00:00
import (
"context"
"fmt"
"net"
"net/http"
2018-03-26 13:32:04 +00:00
"net/url"
"text/template"
"time"
2019-02-04 15:38:08 +00:00
"github.com/cenkalti/backoff"
2019-03-15 08:42:03 +00:00
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/job"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/provider"
"github.com/containous/traefik/pkg/safe"
2019-03-18 10:30:07 +00:00
"github.com/containous/traefik/pkg/types"
2015-09-12 13:10:03 +00:00
"github.com/gambol99/go-marathon"
2018-01-22 11:16:03 +00:00
"github.com/sirupsen/logrus"
2015-09-09 20:39:08 +00:00
)
const (
// DefaultTemplateRule The default template for the default rule.
DefaultTemplateRule = "Host(`{{ normalize .Name }}`)"
traceMaxScanTokenSize = 1024 * 1024
2017-08-18 01:08:03 +00:00
marathonEventIDs = marathon.EventIDApplications |
marathon.EventIDAddHealthCheck |
marathon.EventIDDeploymentSuccess |
marathon.EventIDDeploymentFailed |
marathon.EventIDDeploymentInfo |
marathon.EventIDDeploymentStepSuccess |
marathon.EventIDDeploymentStepFailed
)
// TaskState denotes the Mesos state a task can have.
type TaskState string
const (
taskStateRunning TaskState = "TASK_RUNNING"
taskStateStaging TaskState = "TASK_STAGING"
)
var _ provider.Provider = (*Provider)(nil)
// Provider holds configuration of the provider.
type Provider struct {
provider.Constrainer `description:"List of constraints used to filter out some containers." export:"true"`
2019-03-27 14:02:06 +00:00
Trace bool `description:"Display additional provider logs." export:"true"`
Watch bool `description:"Watch provider." export:"true"`
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon." export:"true"`
DefaultRule string `description:"Default rule."`
ExposedByDefault bool `description:"Expose Marathon apps by default." export:"true"`
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header." export:"true"`
FilterMarathonConstraints bool `description:"Enable use of Marathon constraints in constraint filtering." export:"true"`
TLS *types.ClientTLS `description:"Enable TLS support." export:"true"`
DialerTimeout types.Duration `description:"Set a dialer timeout for Marathon." export:"true"`
ResponseHeaderTimeout types.Duration `description:"Set a response header timeout for Marathon." export:"true"`
TLSHandshakeTimeout types.Duration `description:"Set a TLS handshake timeout for Marathon." export:"true"`
KeepAlive types.Duration `description:"Set a TCP Keep Alive time." export:"true"`
2017-11-21 09:48:04 +00:00
ForceTaskHostname bool `description:"Force to use the task's hostname." export:"true"`
Basic *Basic `description:"Enable basic authentication." export:"true"`
RespectReadinessChecks bool `description:"Filter out tasks with non-successful readiness checks during deployments." export:"true"`
2017-11-21 09:48:04 +00:00
readyChecker *readinessChecker
marathonClient marathon.Marathon
defaultRuleTpl *template.Template
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.Watch = true
p.Endpoint = "http://127.0.0.1:8080"
p.ExposedByDefault = true
p.DialerTimeout = types.Duration(5 * time.Second)
p.ResponseHeaderTimeout = types.Duration(60 * time.Second)
p.TLSHandshakeTimeout = types.Duration(5 * time.Second)
p.KeepAlive = types.Duration(10 * time.Second)
p.DefaultRule = DefaultTemplateRule
}
// Basic holds basic authentication specific configurations
type Basic struct {
HTTPBasicAuthUser string `description:"Basic authentication User."`
HTTPBasicPassword string `description:"Basic authentication Password."`
}
// Init the provider
func (p *Provider) Init() error {
fm := template.FuncMap{
"strsToItfs": func(values []string) []interface{} {
var r []interface{}
for _, v := range values {
r = append(r, v)
}
return r
},
}
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, fm)
if err != nil {
return fmt.Errorf("error while parsing default rule: %v", err)
}
p.defaultRuleTpl = defaultRuleTpl
2019-03-27 14:02:06 +00:00
return nil
}
// Provide allows the marathon provider to provide configurations to traefik
// using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- config.Message, pool *safe.Pool) error {
ctx := log.With(context.Background(), log.Str(log.ProviderName, "marathon"))
logger := log.FromContext(ctx)
operation := func() error {
confg := marathon.NewDefaultConfig()
confg.URL = p.Endpoint
confg.EventsTransport = marathon.EventsTransportSSE
if p.Trace {
confg.LogOutput = log.CustomWriterLevel(logrus.DebugLevel, traceMaxScanTokenSize)
}
if p.Basic != nil {
confg.HTTPBasicAuthUser = p.Basic.HTTPBasicAuthUser
confg.HTTPBasicPassword = p.Basic.HTTPBasicPassword
}
2017-08-18 01:08:03 +00:00
var rc *readinessChecker
if p.RespectReadinessChecks {
logger.Debug("Enabling Marathon readiness checker")
2017-08-18 01:08:03 +00:00
rc = defaultReadinessChecker(p.Trace)
}
p.readyChecker = rc
if len(p.DCOSToken) > 0 {
confg.DCOSToken = p.DCOSToken
}
2019-03-18 10:30:07 +00:00
TLSConfig, err := p.TLS.CreateTLSConfig(ctx)
if err != nil {
return err
}
confg.HTTPClient = &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
KeepAlive: time.Duration(p.KeepAlive),
Timeout: time.Duration(p.DialerTimeout),
}).DialContext,
ResponseHeaderTimeout: time.Duration(p.ResponseHeaderTimeout),
TLSHandshakeTimeout: time.Duration(p.TLSHandshakeTimeout),
TLSClientConfig: TLSConfig,
},
}
client, err := marathon.NewClient(confg)
if err != nil {
logger.Errorf("Failed to create a client for marathon, error: %s", err)
return err
}
p.marathonClient = client
if p.Watch {
2017-08-18 01:08:03 +00:00
update, err := client.AddEventsListener(marathonEventIDs)
if err != nil {
logger.Errorf("Failed to register for events, %s", err)
return err
}
pool.Go(func(stop chan bool) {
defer close(update)
for {
select {
case <-stop:
return
case event := <-update:
logger.Debugf("Received provider event %s", event)
2018-03-26 13:32:04 +00:00
conf := p.getConfigurations(ctx)
if conf != nil {
configurationChan <- config.Message{
ProviderName: "marathon",
Configuration: conf,
}
}
2015-09-09 21:09:16 +00:00
}
}
})
2015-09-09 21:09:16 +00:00
}
2018-03-26 13:32:04 +00:00
configuration := p.getConfigurations(ctx)
configurationChan <- config.Message{
ProviderName: "marathon",
Configuration: configuration,
}
return nil
2015-09-09 20:39:08 +00:00
}
notify := func(err error, time time.Duration) {
logger.Errorf("Provider connection error %+v, retrying in %s", err, time)
}
2016-12-08 12:32:12 +00:00
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
if err != nil {
logger.Errorf("Cannot connect to Provider server: %+v", err)
}
return nil
2015-09-09 20:39:08 +00:00
}
2018-03-26 13:32:04 +00:00
func (p *Provider) getConfigurations(ctx context.Context) *config.Configuration {
2018-03-26 13:32:04 +00:00
applications, err := p.getApplications()
if err != nil {
log.FromContext(ctx).Errorf("Failed to retrieve Marathon applications: %v", err)
2018-03-26 13:32:04 +00:00
return nil
}
return p.buildConfiguration(ctx, applications)
2018-03-26 13:32:04 +00:00
}
func (p *Provider) getApplications() (*marathon.Applications, error) {
v := url.Values{}
v.Add("embed", "apps.tasks")
v.Add("embed", "apps.deployments")
v.Add("embed", "apps.readiness")
return p.marathonClient.Applications(v)
}