2017-04-17 10:50:02 +00:00
package marathon
2015-09-12 13:10:03 +00:00
2015-09-09 20:39:08 +00:00
import (
2015-11-01 15:35:01 +00:00
"errors"
2017-04-20 20:05:21 +00:00
"fmt"
2016-12-30 08:21:13 +00:00
"math"
2016-10-05 15:42:58 +00:00
"net"
2016-12-30 08:21:13 +00:00
"net/http"
2015-11-13 10:50:32 +00:00
"net/url"
2017-08-21 08:46:03 +00:00
"regexp"
2015-09-24 15:16:13 +00:00
"strconv"
2016-02-01 15:08:58 +00:00
"strings"
2015-09-24 15:16:13 +00:00
"text/template"
2016-08-13 16:55:15 +00:00
"time"
2015-09-10 20:54:37 +00:00
"github.com/BurntSushi/ty/fun"
2017-07-07 21:48:28 +00:00
"github.com/Sirupsen/logrus"
2016-09-19 17:08:39 +00:00
"github.com/cenk/backoff"
2017-03-27 09:51:53 +00:00
"github.com/containous/flaeg"
2016-09-19 17:08:39 +00:00
"github.com/containous/traefik/job"
2016-09-23 16:27:01 +00:00
"github.com/containous/traefik/log"
2017-04-17 10:50:02 +00:00
"github.com/containous/traefik/provider"
2016-03-31 16:57:08 +00:00
"github.com/containous/traefik/safe"
2016-02-24 15:43:39 +00:00
"github.com/containous/traefik/types"
2015-09-12 13:10:03 +00:00
"github.com/gambol99/go-marathon"
2015-09-09 20:39:08 +00:00
)
2017-04-20 20:05:21 +00:00
const (
2017-07-10 14:58:12 +00:00
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
2017-07-17 11:42:48 +00:00
)
// TaskState denotes the Mesos state a task can have.
type TaskState string
const (
taskStateRunning TaskState = "TASK_RUNNING"
taskStateStaging TaskState = "TASK_STAGING"
2017-04-20 20:05:21 +00:00
)
2017-04-17 10:50:02 +00:00
var _ provider . Provider = ( * Provider ) ( nil )
2017-08-21 08:46:03 +00:00
// Regexp used to extract the name of the service and the name of the property for this service
// All properties are under the format traefik.<servicename>.frontend.*= except the port/portIndex/weight/protocol/backend directly after traefik.<servicename>.
var servicesPropertiesRegexp = regexp . MustCompile ( ` ^traefik\.(?P<service_name>.+?)\.(?P<property_name>port|portIndex|weight|protocol|backend|frontend\.(.*))$ ` )
2017-04-17 10:50:02 +00:00
// Provider holds configuration of the provider.
type Provider struct {
provider . BaseProvider
2017-11-21 09:48:04 +00:00
Endpoint string ` description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon" export:"true" `
Domain string ` description:"Default domain used" export:"true" `
ExposedByDefault bool ` description:"Expose Marathon apps by default" export:"true" `
GroupsAsSubDomains bool ` description:"Convert Marathon groups to subdomains" export:"true" `
DCOSToken string ` description:"DCOSToken for DCOS environment, This will override the Authorization header" export:"true" `
MarathonLBCompatibility bool ` description:"Add compatibility with marathon-lb labels" export:"true" `
FilterMarathonConstraints bool ` description:"Enable use of Marathon constraints in constraint filtering" export:"true" `
TLS * types . ClientTLS ` description:"Enable Docker TLS support" export:"true" `
DialerTimeout flaeg . Duration ` description:"Set a non-default connection timeout for Marathon" export:"true" `
KeepAlive flaeg . Duration ` description:"Set a non-default TCP Keep Alive time in seconds" export:"true" `
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" `
readyChecker * readinessChecker
marathonClient marathon . Marathon
2015-11-13 10:50:32 +00:00
}
2017-04-17 10:50:02 +00:00
// Basic holds basic authentication specific configurations
type Basic struct {
2017-05-11 17:07:45 +00:00
HTTPBasicAuthUser string ` description:"Basic authentication User" `
HTTPBasicPassword string ` description:"Basic authentication Password" `
2016-01-18 10:52:18 +00:00
}
2017-04-17 10:50:02 +00:00
// Provide allows the marathon provider to provide configurations to traefik
2015-11-01 18:29:47 +00:00
// using the given configuration channel.
2017-04-17 10:50:02 +00:00
func ( p * Provider ) Provide ( configurationChan chan <- types . ConfigMessage , pool * safe . Pool , constraints types . Constraints ) error {
p . Constraints = append ( p . Constraints , constraints ... )
2016-04-15 16:59:51 +00:00
operation := func ( ) error {
config := marathon . NewDefaultConfig ( )
2017-04-17 10:50:02 +00:00
config . URL = p . Endpoint
2016-04-15 16:59:51 +00:00
config . EventsTransport = marathon . EventsTransportSSE
2017-07-07 21:48:28 +00:00
if p . Trace {
config . LogOutput = log . CustomWriterLevel ( logrus . DebugLevel , traceMaxScanTokenSize )
}
2017-04-17 10:50:02 +00:00
if p . Basic != nil {
config . HTTPBasicAuthUser = p . Basic . HTTPBasicAuthUser
config . HTTPBasicPassword = p . Basic . HTTPBasicPassword
2016-04-15 16:59:51 +00:00
}
2017-08-18 01:08:03 +00:00
var rc * readinessChecker
if p . RespectReadinessChecks {
log . Debug ( "Enabling Marathon readiness checker" )
rc = defaultReadinessChecker ( p . Trace )
}
p . readyChecker = rc
2017-04-17 10:50:02 +00:00
if len ( p . DCOSToken ) > 0 {
config . DCOSToken = p . DCOSToken
2016-06-18 12:51:52 +00:00
}
2017-04-17 10:50:02 +00:00
TLSConfig , err := p . TLS . CreateTLSConfig ( )
2016-06-27 14:14:56 +00:00
if err != nil {
return err
}
2016-04-15 16:59:51 +00:00
config . HTTPClient = & http . Client {
Transport : & http . Transport {
2016-10-05 15:42:58 +00:00
DialContext : ( & net . Dialer {
2017-04-17 10:50:02 +00:00
KeepAlive : time . Duration ( p . KeepAlive ) ,
Timeout : time . Duration ( p . DialerTimeout ) ,
2016-10-05 15:42:58 +00:00
} ) . DialContext ,
2016-11-22 15:11:28 +00:00
TLSClientConfig : TLSConfig ,
2016-04-15 16:59:51 +00:00
} ,
}
client , err := marathon . NewClient ( config )
if err != nil {
log . Errorf ( "Failed to create a client for marathon, error: %s" , err )
return err
}
2017-04-17 10:50:02 +00:00
p . marathonClient = client
2017-01-06 15:26:50 +00:00
2017-04-17 10:50:02 +00:00
if p . Watch {
2017-08-18 01:08:03 +00:00
update , err := client . AddEventsListener ( marathonEventIDs )
2017-01-06 15:26:50 +00:00
if err != nil {
2016-04-15 16:59:51 +00:00
log . Errorf ( "Failed to register for events, %s" , err )
return err
}
2016-04-13 18:36:23 +00:00
pool . Go ( func ( stop chan bool ) {
2016-04-19 10:00:22 +00:00
defer close ( update )
2015-09-24 15:16:13 +00:00
for {
2016-04-13 18:36:23 +00:00
select {
case <- stop :
return
case event := <- update :
2017-08-18 01:08:03 +00:00
log . Debugf ( "Received provider event %s" , event )
2017-04-17 10:50:02 +00:00
configuration := p . loadMarathonConfig ( )
2016-04-13 18:36:23 +00:00
if configuration != nil {
configurationChan <- types . ConfigMessage {
ProviderName : "marathon" ,
Configuration : configuration ,
}
2015-11-13 10:50:32 +00:00
}
2015-09-09 21:09:16 +00:00
}
2015-09-24 15:16:13 +00:00
}
2016-03-31 16:57:08 +00:00
} )
2015-09-09 21:09:16 +00:00
}
2017-04-17 10:50:02 +00:00
configuration := p . loadMarathonConfig ( )
2016-04-19 10:00:22 +00:00
configurationChan <- types . ConfigMessage {
ProviderName : "marathon" ,
Configuration : configuration ,
}
2016-04-15 16:59:51 +00:00
return nil
2015-09-09 20:39:08 +00:00
}
2015-09-24 15:16:13 +00:00
2016-04-15 16:59:51 +00:00
notify := func ( err error , time time . Duration ) {
2017-04-17 10:50:02 +00:00
log . Errorf ( "Provider connection error %+v, retrying in %s" , err , time )
2016-04-15 16:59:51 +00:00
}
2016-12-08 12:32:12 +00:00
err := backoff . RetryNotify ( safe . OperationWithRecover ( operation ) , job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , notify )
2016-04-15 16:59:51 +00:00
if err != nil {
2017-04-17 10:50:02 +00:00
log . Errorf ( "Cannot connect to Provider server %+v" , err )
2015-11-13 10:50:32 +00:00
}
2015-10-01 10:04:25 +00:00
return nil
2015-09-09 20:39:08 +00:00
}
2017-04-17 10:50:02 +00:00
func ( p * Provider ) loadMarathonConfig ( ) * types . Configuration {
2015-10-08 19:21:51 +00:00
var MarathonFuncMap = template . FuncMap {
2017-04-17 10:50:02 +00:00
"getBackend" : p . getBackend ,
"getBackendServer" : p . getBackendServer ,
"getPort" : p . getPort ,
"getWeight" : p . getWeight ,
"getDomain" : p . getDomain ,
2017-05-31 21:46:20 +00:00
"getSubDomain" : p . getSubDomain ,
2017-04-17 10:50:02 +00:00
"getProtocol" : p . getProtocol ,
"getPassHostHeader" : p . getPassHostHeader ,
"getPriority" : p . getPriority ,
"getEntryPoints" : p . getEntryPoints ,
"getFrontendRule" : p . getFrontendRule ,
2017-08-21 08:46:03 +00:00
"getFrontendName" : p . getFrontendName ,
2017-04-17 10:50:02 +00:00
"hasCircuitBreakerLabels" : p . hasCircuitBreakerLabels ,
"hasLoadBalancerLabels" : p . hasLoadBalancerLabels ,
"hasMaxConnLabels" : p . hasMaxConnLabels ,
"getMaxConnExtractorFunc" : p . getMaxConnExtractorFunc ,
"getMaxConnAmount" : p . getMaxConnAmount ,
"getLoadBalancerMethod" : p . getLoadBalancerMethod ,
"getCircuitBreakerExpression" : p . getCircuitBreakerExpression ,
2017-10-16 15:38:03 +00:00
"getSticky" : p . getSticky ,
2017-10-10 09:10:02 +00:00
"hasStickinessLabel" : p . hasStickinessLabel ,
2017-10-16 15:38:03 +00:00
"getStickinessCookieName" : p . getStickinessCookieName ,
2017-03-15 18:16:06 +00:00
"hasHealthCheckLabels" : p . hasHealthCheckLabels ,
"getHealthCheckPath" : p . getHealthCheckPath ,
"getHealthCheckInterval" : p . getHealthCheckInterval ,
2017-08-21 08:46:03 +00:00
"hasServices" : p . hasServices ,
"getServiceNames" : p . getServiceNames ,
"getServiceNameSuffix" : p . getServiceNameSuffix ,
2017-06-28 00:22:17 +00:00
"getBasicAuth" : p . getBasicAuth ,
2017-04-17 10:50:02 +00:00
}
2017-05-22 21:21:15 +00:00
v := url . Values { }
v . Add ( "embed" , "apps.tasks" )
2017-08-18 01:08:03 +00:00
v . Add ( "embed" , "apps.deployments" )
v . Add ( "embed" , "apps.readiness" )
2017-05-22 21:21:15 +00:00
applications , err := p . marathonClient . Applications ( v )
2015-09-12 13:10:03 +00:00
if err != nil {
2017-05-22 21:21:15 +00:00
log . Errorf ( "Failed to retrieve Marathon applications: %s" , err )
2015-09-09 20:39:08 +00:00
return nil
}
2015-09-10 20:54:37 +00:00
2017-05-22 21:21:15 +00:00
filteredApps := fun . Filter ( p . applicationFilter , applications . Apps ) . ( [ ] marathon . Application )
2017-07-19 12:41:45 +00:00
for i , app := range filteredApps {
filteredApps [ i ] . Tasks = fun . Filter ( func ( task * marathon . Task ) bool {
2017-08-21 08:46:03 +00:00
filtered := p . taskFilter ( * task , app )
if filtered {
p . logIllegalServices ( * task , app )
}
return filtered
2017-05-22 21:21:15 +00:00
} , app . Tasks ) . ( [ ] * marathon . Task )
2015-09-09 20:39:08 +00:00
}
templateObjects := struct {
Applications [ ] marathon . Application
Domain string
} {
2015-09-10 20:54:37 +00:00
filteredApps ,
2017-04-17 10:50:02 +00:00
p . Domain ,
2015-09-09 20:39:08 +00:00
}
2017-04-17 10:50:02 +00:00
configuration , err := p . GetConfiguration ( "templates/marathon.tmpl" , MarathonFuncMap , templateObjects )
2015-09-09 20:39:08 +00:00
if err != nil {
2017-05-22 21:21:15 +00:00
log . Errorf ( "failed to render Marathon configuration template: %s" , err )
2015-09-09 20:39:08 +00:00
}
2015-11-13 10:50:32 +00:00
return configuration
}
2015-09-09 20:39:08 +00:00
2017-05-22 21:21:15 +00:00
func ( p * Provider ) applicationFilter ( app marathon . Application ) bool {
// Filter disabled application.
if ! isApplicationEnabled ( app , p . ExposedByDefault ) {
log . Debugf ( "Filtering disabled Marathon application %s" , app . ID )
2017-04-20 20:05:21 +00:00
return false
}
// Filter by constraints.
2017-08-21 08:46:03 +00:00
label , _ := p . getAppLabel ( app , types . LabelTags )
2016-09-23 21:05:11 +00:00
constraintTags := strings . Split ( label , "," )
2017-04-17 10:50:02 +00:00
if p . MarathonLBCompatibility {
2017-08-21 08:46:03 +00:00
if label , ok := p . getAppLabel ( app , "HAPROXY_GROUP" ) ; ok {
2016-10-06 15:42:19 +00:00
constraintTags = append ( constraintTags , label )
}
}
2017-11-21 09:48:04 +00:00
if p . FilterMarathonConstraints && app . Constraints != nil {
for _ , constraintParts := range * app . Constraints {
constraintTags = append ( constraintTags , strings . Join ( constraintParts , ":" ) )
}
}
2017-04-17 10:50:02 +00:00
if ok , failingConstraint := p . MatchConstraints ( constraintTags ) ; ! ok {
2016-09-23 21:05:11 +00:00
if failingConstraint != nil {
2017-05-22 21:21:15 +00:00
log . Debugf ( "Filtering Marathon application %v pruned by '%v' constraint" , app . ID , failingConstraint . String ( ) )
2016-09-20 08:44:32 +00:00
}
2016-09-23 21:05:11 +00:00
return false
2016-09-20 08:44:32 +00:00
}
2016-03-21 09:37:02 +00:00
2017-05-22 21:21:15 +00:00
return true
}
func ( p * Provider ) taskFilter ( task marathon . Task , application marathon . Application ) bool {
2017-07-17 11:42:48 +00:00
if task . State != string ( taskStateRunning ) {
2017-05-22 21:21:15 +00:00
return false
}
2017-04-20 20:05:21 +00:00
// Filter task with existing, bad health check results.
2015-11-13 10:50:32 +00:00
if application . HasHealthChecks ( ) {
if task . HasHealthCheckResults ( ) {
2016-02-09 22:10:24 +00:00
for _ , healthcheck := range task . HealthCheckResults {
2015-11-13 10:50:32 +00:00
if ! healthcheck . Alive {
2017-04-20 20:05:21 +00:00
log . Debugf ( "Filtering Marathon task %s from application %s with bad health check" , task . ID , application . ID )
2015-11-13 10:50:32 +00:00
return false
}
}
}
2015-09-09 20:39:08 +00:00
}
2017-04-20 20:05:21 +00:00
2017-08-18 01:08:03 +00:00
if ready := p . readyChecker . Do ( task , application ) ; ! ready {
log . Infof ( "Filtering unready task %s from application %s" , task . ID , application . ID )
return false
}
2015-11-13 10:50:32 +00:00
return true
}
2015-09-09 20:39:08 +00:00
2016-03-21 09:37:02 +00:00
func isApplicationEnabled ( application marathon . Application , exposedByDefault bool ) bool {
2017-07-10 14:58:12 +00:00
return exposedByDefault && ( * application . Labels ) [ types . LabelEnable ] != "false" || ( * application . Labels ) [ types . LabelEnable ] == "true"
2016-03-21 09:37:02 +00:00
}
2017-08-21 08:46:03 +00:00
// logIllegalServices logs illegal service configurations.
// While we cannot filter on the service level, they will eventually get
// rejected once the server configuration is rendered.
func ( p * Provider ) logIllegalServices ( task marathon . Task , application marathon . Application ) {
for _ , serviceName := range p . getServiceNames ( application ) {
// Check for illegal/missing ports.
if _ , err := p . processPorts ( application , task , serviceName ) ; err != nil {
log . Warnf ( "%s has an illegal configuration: no proper port available" , identifier ( application , task , serviceName ) )
continue
}
// Check for illegal port label combinations.
_ , hasPortLabel := p . getLabel ( application , types . LabelPort , serviceName )
_ , hasPortIndexLabel := p . getLabel ( application , types . LabelPortIndex , serviceName )
if hasPortLabel && hasPortIndexLabel {
log . Warnf ( "%s has both port and port index specified; port will take precedence" , identifier ( application , task , serviceName ) )
}
}
}
//servicePropertyValues is a map of services properties
//an example value is: weight=42
type servicePropertyValues map [ string ] string
//serviceProperties is a map of service properties per service, which we can get with label[serviceName][propertyName]. It yields a property value.
type serviceProperties map [ string ] servicePropertyValues
//hasServices checks if there are service-defining labels for the given application
func ( p * Provider ) hasServices ( application marathon . Application ) bool {
return len ( extractServiceProperties ( application . Labels ) ) > 0
}
//extractServiceProperties extracts the service labels for the given application
func extractServiceProperties ( labels * map [ string ] string ) serviceProperties {
v := make ( serviceProperties )
if labels != nil {
for label , value := range * labels {
matches := servicesPropertiesRegexp . FindStringSubmatch ( label )
if matches == nil {
continue
}
// According to the regex, match index 1 is "service_name" and match index 2 is the "property_name"
serviceName := matches [ 1 ]
propertyName := matches [ 2 ]
if _ , ok := v [ serviceName ] ; ! ok {
v [ serviceName ] = make ( servicePropertyValues )
}
v [ serviceName ] [ propertyName ] = value
}
}
return v
}
//getServiceProperty returns the property for a service label searching in all labels of the given application
func getServiceProperty ( application marathon . Application , serviceName string , property string ) ( string , bool ) {
value , ok := extractServiceProperties ( application . Labels ) [ serviceName ] [ property ]
return value , ok
}
//getServiceNames returns a list of service names for a given application
//An empty name "" will be added if no service specific properties exist, as an indication that there are no sub-services, but only main application
func ( p * Provider ) getServiceNames ( application marathon . Application ) [ ] string {
labelServiceProperties := extractServiceProperties ( application . Labels )
var names [ ] string
for k := range labelServiceProperties {
names = append ( names , k )
}
if len ( names ) == 0 {
names = append ( names , "" )
}
return names
}
func ( p * Provider ) getServiceNameSuffix ( serviceName string ) string {
if len ( serviceName ) > 0 {
serviceName = strings . Replace ( serviceName , "/" , "-" , - 1 )
serviceName = strings . Replace ( serviceName , "." , "-" , - 1 )
return "-service-" + serviceName
}
return ""
}
//getAppLabel is a convenience function to get application label, when no serviceName is available
//it is identical to calling getLabel(application, label, "")
func ( p * Provider ) getAppLabel ( application marathon . Application , label string ) ( string , bool ) {
return p . getLabel ( application , label , "" )
}
//getLabel returns a string value of a corresponding `label` argument
// If serviceName is non-empty, we look for a service label. If none exists or serviceName is empty, we look for an application label.
func ( p * Provider ) getLabel ( application marathon . Application , label string , serviceName string ) ( string , bool ) {
if len ( serviceName ) > 0 {
property := strings . TrimPrefix ( label , types . LabelPrefix )
if value , ok := getServiceProperty ( application , serviceName , property ) ; ok {
return value , true
}
}
2016-06-18 12:51:52 +00:00
for key , value := range * application . Labels {
2015-10-23 07:49:19 +00:00
if key == label {
2017-04-20 20:06:27 +00:00
return value , true
2015-10-23 07:49:19 +00:00
}
}
2017-04-20 20:06:27 +00:00
return "" , false
2015-10-23 07:49:19 +00:00
}
2017-08-21 08:46:03 +00:00
func ( p * Provider ) getPort ( task marathon . Task , application marathon . Application , serviceName string ) string {
port , err := p . processPorts ( application , task , serviceName )
2017-04-20 20:05:21 +00:00
if err != nil {
2017-08-21 08:46:03 +00:00
log . Errorf ( "Unable to process ports for %s: %s" , identifier ( application , task , serviceName ) , err )
2017-04-20 20:05:21 +00:00
return ""
2015-12-05 18:59:01 +00:00
}
2017-04-20 20:05:21 +00:00
return strconv . Itoa ( port )
2015-11-13 10:50:32 +00:00
}
2017-08-21 08:46:03 +00:00
func ( p * Provider ) getWeight ( application marathon . Application , serviceName string ) string {
if label , ok := p . getLabel ( application , types . LabelWeight , serviceName ) ; ok {
2015-11-13 10:50:32 +00:00
return label
}
return "0"
}
2017-04-17 10:50:02 +00:00
func ( p * Provider ) getDomain ( application marathon . Application ) string {
2017-08-21 08:46:03 +00:00
if label , ok := p . getAppLabel ( application , types . LabelDomain ) ; ok {
2015-11-13 10:50:32 +00:00
return label
}
2017-04-17 10:50:02 +00:00
return p . Domain
2015-11-13 10:50:32 +00:00
}
2017-08-21 08:46:03 +00:00
func ( p * Provider ) getProtocol ( application marathon . Application , serviceName string ) string {
if label , ok := p . getLabel ( application , types . LabelProtocol , serviceName ) ; ok {
2015-11-13 10:50:32 +00:00
return label
}
return "http"
}
2017-10-16 15:38:03 +00:00
func ( p * Provider ) getSticky ( application marathon . Application ) string {
if sticky , ok := p . getAppLabel ( application , types . LabelBackendLoadbalancerSticky ) ; ok {
2017-10-12 15:50:03 +00:00
log . Warnf ( "Deprecated configuration found: %s. Please use %s." , types . LabelBackendLoadbalancerSticky , types . LabelBackendLoadbalancerStickiness )
2017-10-16 15:38:03 +00:00
return sticky
2016-05-13 14:22:11 +00:00
}
2017-10-16 15:38:03 +00:00
return "false"
}
2017-10-10 09:10:02 +00:00
2017-10-16 15:38:03 +00:00
func ( p * Provider ) hasStickinessLabel ( application marathon . Application ) bool {
labelStickiness , okStickiness := p . getAppLabel ( application , types . LabelBackendLoadbalancerStickiness )
return okStickiness && len ( labelStickiness ) > 0 && strings . EqualFold ( strings . TrimSpace ( labelStickiness ) , "true" )
2017-10-10 09:10:02 +00:00
}
func ( p * Provider ) getStickinessCookieName ( application marathon . Application ) string {
if label , ok := p . getAppLabel ( application , types . LabelBackendLoadbalancerStickinessCookieName ) ; ok {
return label
}
return ""
2016-05-13 14:22:11 +00:00
}
2017-08-21 08:46:03 +00:00
func ( p * Provider ) getPassHostHeader ( application marathon . Application , serviceName string ) string {
if passHostHeader , ok := p . getLabel ( application , types . LabelFrontendPassHostHeader , serviceName ) ; ok {
2015-11-13 10:50:32 +00:00
return passHostHeader
}
2016-05-10 11:43:24 +00:00
return "true"
2015-10-23 07:49:19 +00:00
}
2017-08-21 08:46:03 +00:00
func ( p * Provider ) getPriority ( application marathon . Application , serviceName string ) string {
if priority , ok := p . getLabel ( application , types . LabelFrontendPriority , serviceName ) ; ok {
2016-06-06 20:30:23 +00:00
return priority
}
return "0"
}
2017-08-21 08:46:03 +00:00
func ( p * Provider ) getEntryPoints ( application marathon . Application , serviceName string ) [ ] string {
if entryPoints , ok := p . getLabel ( application , types . LabelFrontendEntryPoints , serviceName ) ; ok {
2016-02-01 15:08:58 +00:00
return strings . Split ( entryPoints , "," )
}
return [ ] string { }
}
2015-11-13 10:50:32 +00:00
// getFrontendRule returns the frontend rule for the specified application, using
2017-08-21 08:46:03 +00:00
// its label. If service is provided, it will look for serviceName label before generic one.
// It returns a default one (Host) if the label is not present.
func ( p * Provider ) getFrontendRule ( application marathon . Application , serviceName string ) string {
if label , ok := p . getLabel ( application , types . LabelFrontendRule , serviceName ) ; ok {
2015-10-23 07:49:19 +00:00
return label
}
2017-04-17 10:50:02 +00:00
if p . MarathonLBCompatibility {
2017-08-21 08:46:03 +00:00
if label , ok := p . getAppLabel ( application , "HAPROXY_0_VHOST" ) ; ok {
2016-10-06 15:42:19 +00:00
return "Host:" + label
}
}
2017-08-21 08:46:03 +00:00
if len ( serviceName ) > 0 {
return "Host:" + strings . ToLower ( provider . Normalize ( serviceName ) ) + "." + p . getSubDomain ( application . ID ) + "." + p . Domain
}
2017-04-17 10:50:02 +00:00
return "Host:" + p . getSubDomain ( application . ID ) + "." + p . Domain
2015-10-23 07:49:19 +00:00
}
2016-01-20 18:55:10 +00:00
2017-08-21 08:46:03 +00:00
func ( p * Provider ) getBackend ( application marathon . Application , serviceName string ) string {
if label , ok := p . getLabel ( application , types . LabelBackend , serviceName ) ; ok {
2016-01-20 18:55:10 +00:00
return label
}
2017-08-21 08:46:03 +00:00
return strings . Replace ( application . ID , "/" , "-" , - 1 ) + p . getServiceNameSuffix ( serviceName )
}
func ( p * Provider ) getFrontendName ( application marathon . Application , serviceName string ) string {
appName := strings . Replace ( application . ID , "/" , "-" , - 1 )
return "frontend" + appName + p . getServiceNameSuffix ( serviceName )
2016-01-20 18:55:10 +00:00
}
2016-05-31 21:23:23 +00:00
2017-04-17 10:50:02 +00:00
func ( p * Provider ) getSubDomain ( name string ) string {
if p . GroupsAsSubDomains {
2016-06-01 14:47:39 +00:00
splitedName := strings . Split ( strings . TrimPrefix ( name , "/" ) , "/" )
2017-04-17 10:50:02 +00:00
provider . ReverseStringSlice ( & splitedName )
2016-06-01 14:47:39 +00:00
reverseName := strings . Join ( splitedName , "." )
return reverseName
}
return strings . Replace ( strings . TrimPrefix ( name , "/" ) , "/" , "-" , - 1 )
2016-05-31 21:23:23 +00:00
}
2016-08-13 16:55:15 +00:00
2017-04-17 10:50:02 +00:00
func ( p * Provider ) hasCircuitBreakerLabels ( application marathon . Application ) bool {
2017-08-21 08:46:03 +00:00
_ , ok := p . getAppLabel ( application , types . LabelBackendCircuitbreakerExpression )
2017-04-20 20:06:27 +00:00
return ok
2016-08-13 16:55:15 +00:00
}
2017-04-17 10:50:02 +00:00
func ( p * Provider ) hasLoadBalancerLabels ( application marathon . Application ) bool {
2017-08-21 08:46:03 +00:00
_ , errMethod := p . getAppLabel ( application , types . LabelBackendLoadbalancerMethod )
_ , errSticky := p . getAppLabel ( application , types . LabelBackendLoadbalancerSticky )
2017-04-20 20:06:27 +00:00
return errMethod || errSticky
2016-08-13 16:55:15 +00:00
}
2017-04-17 10:50:02 +00:00
func ( p * Provider ) hasMaxConnLabels ( application marathon . Application ) bool {
2017-08-21 08:46:03 +00:00
if _ , ok := p . getAppLabel ( application , types . LabelBackendMaxconnAmount ) ; ! ok {
2016-08-13 16:55:15 +00:00
return false
}
2017-08-21 08:46:03 +00:00
_ , ok := p . getAppLabel ( application , types . LabelBackendMaxconnExtractorfunc )
2017-04-20 20:06:27 +00:00
return ok
2016-08-13 16:55:15 +00:00
}
2017-04-17 10:50:02 +00:00
func ( p * Provider ) getMaxConnAmount ( application marathon . Application ) int64 {
2017-08-21 08:46:03 +00:00
if label , ok := p . getAppLabel ( application , types . LabelBackendMaxconnAmount ) ; ok {
2016-08-13 16:55:15 +00:00
i , errConv := strconv . ParseInt ( label , 10 , 64 )
if errConv != nil {
log . Errorf ( "Unable to parse traefik.backend.maxconn.amount %s" , label )
return math . MaxInt64
}
return i
}
return math . MaxInt64
}
2017-04-17 10:50:02 +00:00
func ( p * Provider ) getMaxConnExtractorFunc ( application marathon . Application ) string {
2017-08-21 08:46:03 +00:00
if label , ok := p . getAppLabel ( application , types . LabelBackendMaxconnExtractorfunc ) ; ok {
2016-08-13 16:55:15 +00:00
return label
}
return "request.host"
}
2017-04-17 10:50:02 +00:00
func ( p * Provider ) getLoadBalancerMethod ( application marathon . Application ) string {
2017-08-21 08:46:03 +00:00
if label , ok := p . getAppLabel ( application , types . LabelBackendLoadbalancerMethod ) ; ok {
2016-08-13 16:55:15 +00:00
return label
}
return "wrr"
}
2017-04-17 10:50:02 +00:00
func ( p * Provider ) getCircuitBreakerExpression ( application marathon . Application ) string {
2017-08-21 08:46:03 +00:00
if label , ok := p . getAppLabel ( application , types . LabelBackendCircuitbreakerExpression ) ; ok {
2016-08-13 16:55:15 +00:00
return label
}
return "NetworkErrorRatio() > 1"
}
2017-01-06 15:26:50 +00:00
2017-03-15 18:16:06 +00:00
func ( p * Provider ) hasHealthCheckLabels ( application marathon . Application ) bool {
return p . getHealthCheckPath ( application ) != ""
}
func ( p * Provider ) getHealthCheckPath ( application marathon . Application ) string {
2017-08-21 08:46:03 +00:00
if label , ok := p . getAppLabel ( application , types . LabelBackendHealthcheckPath ) ; ok {
2017-03-15 18:16:06 +00:00
return label
}
return ""
}
func ( p * Provider ) getHealthCheckInterval ( application marathon . Application ) string {
2017-08-21 08:46:03 +00:00
if label , ok := p . getAppLabel ( application , types . LabelBackendHealthcheckInterval ) ; ok {
2017-03-15 18:16:06 +00:00
return label
}
return ""
}
2017-08-21 08:46:03 +00:00
func ( p * Provider ) getBasicAuth ( application marathon . Application , serviceName string ) [ ] string {
if basicAuth , ok := p . getLabel ( application , types . LabelFrontendAuthBasic , serviceName ) ; ok {
2017-06-28 00:22:17 +00:00
return strings . Split ( basicAuth , "," )
}
return [ ] string { }
}
2017-08-21 08:46:03 +00:00
// processPorts returns the configured port.
// An explicitly specified port is preferred. If none is specified, it selects
// one of the available port. The first such found port is returned unless an
// optional index is provided.
func ( p * Provider ) processPorts ( application marathon . Application , task marathon . Task , serviceName string ) ( int , error ) {
if portLabel , ok := p . getLabel ( application , types . LabelPort , serviceName ) ; ok {
2017-04-20 20:05:21 +00:00
port , err := strconv . Atoi ( portLabel )
switch {
case err != nil :
2017-08-21 08:46:03 +00:00
return 0 , fmt . Errorf ( "failed to parse port label %q: %s" , portLabel , err )
2017-04-20 20:05:21 +00:00
case port <= 0 :
return 0 , fmt . Errorf ( "explicitly specified port %d must be larger than zero" , port )
}
return port , nil
}
ports := retrieveAvailablePorts ( application , task )
if len ( ports ) == 0 {
return 0 , errors . New ( "no port found" )
}
portIndex := 0
2017-08-21 08:46:03 +00:00
if portIndexLabel , ok := p . getLabel ( application , types . LabelPortIndex , serviceName ) ; ok {
2017-04-20 20:05:21 +00:00
var err error
2017-04-22 20:07:27 +00:00
portIndex , err = parseIndex ( portIndexLabel , len ( ports ) )
if err != nil {
return 0 , fmt . Errorf ( "cannot use port index to select from %d ports: %s" , len ( ports ) , err )
2017-04-20 20:05:21 +00:00
}
}
return ports [ portIndex ] , nil
}
func retrieveAvailablePorts ( application marathon . Application , task marathon . Task ) [ ] int {
2017-01-06 15:26:50 +00:00
// Using default port configuration
if task . Ports != nil && len ( task . Ports ) > 0 {
return task . Ports
}
// Using port definition if available
if application . PortDefinitions != nil && len ( * application . PortDefinitions ) > 0 {
2017-02-02 20:07:44 +00:00
var ports [ ] int
2017-01-06 15:26:50 +00:00
for _ , def := range * application . PortDefinitions {
if def . Port != nil {
ports = append ( ports , * def . Port )
}
}
return ports
}
// If using IP-per-task using this port definition
if application . IPAddressPerTask != nil && len ( * ( ( * application . IPAddressPerTask ) . Discovery ) . Ports ) > 0 {
2017-02-02 20:07:44 +00:00
var ports [ ] int
2017-01-06 15:26:50 +00:00
for _ , def := range * ( ( * application . IPAddressPerTask ) . Discovery ) . Ports {
ports = append ( ports , def . Number )
}
return ports
}
return [ ] int { }
}
2017-05-22 21:21:15 +00:00
func ( p * Provider ) getBackendServer ( task marathon . Task , application marathon . Application ) string {
2017-04-21 14:06:14 +00:00
numTaskIPAddresses := len ( task . IPAddresses )
2017-03-26 19:59:08 +00:00
switch {
case application . IPAddressPerTask == nil || p . ForceTaskHostname :
return task . Host
2017-04-21 14:06:14 +00:00
case numTaskIPAddresses == 0 :
log . Errorf ( "Missing IP address for Marathon application %s on task %s" , application . ID , task . ID )
2017-01-06 15:26:50 +00:00
return ""
2017-04-21 14:06:14 +00:00
case numTaskIPAddresses == 1 :
2017-01-06 15:26:50 +00:00
return task . IPAddresses [ 0 ] . IPAddress
2017-03-26 19:59:08 +00:00
default :
2017-08-21 08:46:03 +00:00
ipAddressIdxStr , ok := p . getAppLabel ( application , "traefik.ipAddressIdx" )
2017-04-20 20:06:27 +00:00
if ! ok {
2017-04-21 14:06:14 +00:00
log . Errorf ( "Found %d task IP addresses but missing IP address index for Marathon application %s on task %s" , numTaskIPAddresses , application . ID , task . ID )
2017-01-06 15:26:50 +00:00
return ""
}
2017-04-22 20:07:27 +00:00
ipAddressIdx , err := parseIndex ( ipAddressIdxStr , numTaskIPAddresses )
2017-01-06 15:26:50 +00:00
if err != nil {
2017-04-21 14:06:14 +00:00
log . Errorf ( "Cannot use IP address index to select from %d task IP addresses for Marathon application %s on task %s: %s" , numTaskIPAddresses , application . ID , task . ID , err )
2017-01-06 15:26:50 +00:00
return ""
}
2017-04-21 14:06:14 +00:00
2017-01-06 15:26:50 +00:00
return task . IPAddresses [ ipAddressIdx ] . IPAddress
}
}
2017-04-22 20:07:27 +00:00
func parseIndex ( index string , length int ) ( int , error ) {
parsed , err := strconv . Atoi ( index )
switch {
case err != nil :
2017-08-21 08:46:03 +00:00
return 0 , fmt . Errorf ( "failed to parse index %q: %s" , index , err )
2017-04-22 20:07:27 +00:00
case parsed < 0 , parsed > length - 1 :
return 0 , fmt . Errorf ( "index %d must be within range (0, %d)" , parsed , length - 1 )
}
return parsed , nil
}
2017-08-21 08:46:03 +00:00
func identifier ( app marathon . Application , task marathon . Task , serviceName string ) string {
id := fmt . Sprintf ( "Marathon task %s from application %s" , task . ID , app . ID )
if serviceName != "" {
id += fmt . Sprintf ( " (service: %s)" , serviceName )
}
return id
}