2017-12-02 21:29:34 +03:00
package mesos
import (
"fmt"
"math"
"strconv"
"strings"
"text/template"
"time"
"github.com/BurntSushi/ty/fun"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types"
"github.com/mesosphere/mesos-dns/records"
"github.com/mesosphere/mesos-dns/records/state"
)
func ( p * Provider ) buildConfiguration ( ) * types . Configuration {
var mesosFuncMap = template . FuncMap {
"getBackend" : getBackend ,
"getPort" : p . getPort ,
"getHost" : p . getHost ,
"getWeight" : getFuncApplicationStringValue ( label . TraefikWeight , label . DefaultWeight ) ,
"getDomain" : getFuncStringValue ( label . TraefikDomain , p . Domain ) ,
"getProtocol" : getFuncApplicationStringValue ( label . TraefikProtocol , label . DefaultProtocol ) ,
"getPassHostHeader" : getFuncStringValue ( label . TraefikFrontendPassHostHeader , label . DefaultPassHostHeader ) ,
"getPriority" : getFuncStringValue ( label . TraefikFrontendPriority , label . DefaultFrontendPriority ) ,
"getEntryPoints" : getFuncSliceStringValue ( label . TraefikFrontendEntryPoints ) ,
"getFrontendRule" : p . getFrontendRule ,
"getFrontendBackend" : getFrontendBackend ,
"getID" : getID ,
"getFrontEndName" : getFrontEndName ,
}
rg := records . NewRecordGenerator ( time . Duration ( p . StateTimeoutSecond ) * time . Second )
st , err := rg . FindMaster ( p . Masters ... )
if err != nil {
log . Errorf ( "Failed to create a client for Mesos, error: %v" , err )
return nil
}
tasks := taskRecords ( st )
// filter tasks
filteredTasks := fun . Filter ( func ( task state . Task ) bool {
return taskFilter ( task , p . ExposedByDefault )
} , tasks ) . ( [ ] state . Task )
uniqueApps := make ( map [ string ] state . Task )
for _ , value := range filteredTasks {
if _ , ok := uniqueApps [ value . DiscoveryInfo . Name ] ; ! ok {
uniqueApps [ value . DiscoveryInfo . Name ] = value
}
}
var filteredApps [ ] state . Task
for _ , value := range uniqueApps {
filteredApps = append ( filteredApps , value )
}
templateObjects := struct {
Applications [ ] state . Task
Tasks [ ] state . Task
Domain string
} {
Applications : filteredApps ,
Tasks : filteredTasks ,
Domain : p . Domain ,
}
configuration , err := p . GetConfiguration ( "templates/mesos.tmpl" , mesosFuncMap , templateObjects )
if err != nil {
log . Error ( err )
}
return configuration
}
func taskRecords ( st state . State ) [ ] state . Task {
var tasks [ ] state . Task
for _ , f := range st . Frameworks {
for _ , task := range f . Tasks {
for _ , slave := range st . Slaves {
if task . SlaveID == slave . ID {
2017-12-20 17:47:15 +03:00
task . SlaveIP = slave . PID . Host
2017-12-02 21:29:34 +03:00
}
}
// only do running and discoverable tasks
if task . State == "TASK_RUNNING" {
tasks = append ( tasks , task )
}
}
}
return tasks
}
func taskFilter ( task state . Task , exposedByDefaultFlag bool ) bool {
if len ( task . DiscoveryInfo . Ports . DiscoveryPorts ) == 0 {
log . Debugf ( "Filtering Mesos task without port %s" , task . Name )
return false
}
if ! isEnabled ( task , exposedByDefaultFlag ) {
log . Debugf ( "Filtering disabled Mesos task %s" , task . DiscoveryInfo . Name )
return false
}
// filter indeterminable task port
portIndexLabel := getStringValue ( task , label . TraefikPortIndex , "" )
portValueLabel := getStringValue ( task , label . TraefikPort , "" )
if portIndexLabel != "" && portValueLabel != "" {
log . Debugf ( "Filtering Mesos task %s specifying both %q' and %q labels" , task . Name , label . TraefikPortIndex , label . TraefikPort )
return false
}
if portIndexLabel != "" {
index , err := strconv . Atoi ( portIndexLabel )
if err != nil || index < 0 || index > len ( task . DiscoveryInfo . Ports . DiscoveryPorts ) - 1 {
log . Debugf ( "Filtering Mesos task %s with unexpected value for %q label" , task . Name , label . TraefikPortIndex )
return false
}
}
if portValueLabel != "" {
port , err := strconv . Atoi ( portValueLabel )
if err != nil {
log . Debugf ( "Filtering Mesos task %s with unexpected value for %q label" , task . Name , label . TraefikPort )
return false
}
var foundPort bool
for _ , exposedPort := range task . DiscoveryInfo . Ports . DiscoveryPorts {
if port == exposedPort . Number {
foundPort = true
break
}
}
if ! foundPort {
log . Debugf ( "Filtering Mesos task %s without a matching port for %q label" , task . Name , label . TraefikPort )
return false
}
}
//filter healthChecks
if task . Statuses != nil && len ( task . Statuses ) > 0 && task . Statuses [ 0 ] . Healthy != nil && ! * task . Statuses [ 0 ] . Healthy {
log . Debugf ( "Filtering Mesos task %s with bad healthCheck" , task . DiscoveryInfo . Name )
return false
}
return true
}
func getID ( task state . Task ) string {
return provider . Normalize ( task . ID )
}
func getBackend ( task state . Task , apps [ ] state . Task ) string {
application , err := getApplication ( task , apps )
if err != nil {
log . Error ( err )
return ""
}
return getFrontendBackend ( application )
}
func getFrontendBackend ( task state . Task ) string {
if value := getStringValue ( task , label . TraefikBackend , "" ) ; len ( value ) > 0 {
return value
}
return "-" + provider . Normalize ( task . DiscoveryInfo . Name )
}
func getFrontEndName ( task state . Task ) string {
return provider . Normalize ( task . ID )
}
func ( p * Provider ) getPort ( task state . Task , applications [ ] state . Task ) string {
application , err := getApplication ( task , applications )
if err != nil {
log . Error ( err )
return ""
}
plv := getIntValue ( application , label . TraefikPortIndex , math . MinInt32 , len ( task . DiscoveryInfo . Ports . DiscoveryPorts ) - 1 )
if plv >= 0 {
return strconv . Itoa ( task . DiscoveryInfo . Ports . DiscoveryPorts [ plv ] . Number )
}
if pv := getStringValue ( application , label . TraefikPort , "" ) ; len ( pv ) > 0 {
return pv
}
for _ , port := range task . DiscoveryInfo . Ports . DiscoveryPorts {
return strconv . Itoa ( port . Number )
}
return ""
}
// getFrontendRule returns the frontend rule for the specified application, using
// it's label. It returns a default one (Host) if the label is not present.
func ( p * Provider ) getFrontendRule ( task state . Task ) string {
if v := getStringValue ( task , label . TraefikFrontendRule , "" ) ; len ( v ) > 0 {
return v
}
return "Host:" + strings . ToLower ( strings . Replace ( p . getSubDomain ( task . DiscoveryInfo . Name ) , "_" , "-" , - 1 ) ) + "." + p . Domain
}
func ( p * Provider ) getHost ( task state . Task ) string {
return task . IP ( strings . Split ( p . IPSources , "," ) ... )
}
func ( p * Provider ) getSubDomain ( name string ) string {
if p . GroupsAsSubDomains {
splitedName := strings . Split ( strings . TrimPrefix ( name , "/" ) , "/" )
provider . ReverseStringSlice ( & splitedName )
reverseName := strings . Join ( splitedName , "." )
return reverseName
}
return strings . Replace ( strings . TrimPrefix ( name , "/" ) , "/" , "-" , - 1 )
}
// Label functions
func getFuncApplicationStringValue ( labelName string , defaultValue string ) func ( task state . Task , applications [ ] state . Task ) string {
return func ( task state . Task , applications [ ] state . Task ) string {
app , err := getApplication ( task , applications )
if err == nil {
return getStringValue ( app , labelName , defaultValue )
}
log . Error ( err )
return defaultValue
}
}
func getFuncStringValue ( labelName string , defaultValue string ) func ( task state . Task ) string {
return func ( task state . Task ) string {
return getStringValue ( task , labelName , defaultValue )
}
}
func getFuncSliceStringValue ( labelName string ) func ( task state . Task ) [ ] string {
return func ( task state . Task ) [ ] string {
return getSliceStringValue ( task , labelName )
}
}
func getStringValue ( task state . Task , labelName string , defaultValue string ) string {
for _ , lbl := range task . Labels {
if lbl . Key == labelName {
return lbl . Value
}
}
return defaultValue
}
func getBoolValue ( task state . Task , labelName string , defaultValue bool ) bool {
for _ , lbl := range task . Labels {
if lbl . Key == labelName {
v , err := strconv . ParseBool ( lbl . Value )
if err == nil {
return v
}
}
}
return defaultValue
}
func getIntValue ( task state . Task , labelName string , defaultValue int , maxValue int ) int {
for _ , lbl := range task . Labels {
if lbl . Key == labelName {
value , err := strconv . Atoi ( lbl . Value )
if err == nil {
if value <= maxValue {
return value
}
log . Warnf ( "The value %q for %q exceed the max authorized value %q, falling back to %v." , lbl . Value , labelName , maxValue , defaultValue )
} else {
log . Warnf ( "Unable to parse %q: %q, falling back to %v. %v" , labelName , lbl . Value , defaultValue , err )
}
}
}
return defaultValue
}
func getSliceStringValue ( task state . Task , labelName string ) [ ] string {
for _ , lbl := range task . Labels {
if lbl . Key == labelName {
return label . SplitAndTrimString ( lbl . Value , "," )
}
}
return nil
}
func getApplication ( task state . Task , apps [ ] state . Task ) ( state . Task , error ) {
for _ , app := range apps {
if app . DiscoveryInfo . Name == task . DiscoveryInfo . Name {
return app , nil
}
}
return state . Task { } , fmt . Errorf ( "unable to get Mesos application from task %s" , task . DiscoveryInfo . Name )
}
func isEnabled ( task state . Task , exposedByDefault bool ) bool {
return getBoolValue ( task , label . TraefikEnable , exposedByDefault )
}