diff --git a/configuration/configuration.go b/configuration/configuration.go index 10c3557d9..c93ecb1b7 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -230,6 +230,15 @@ func (gc *GlobalConfiguration) SetEffectiveConfiguration(configFile string) { } } + if gc.Mesos != nil { + if len(gc.Mesos.Filename) != 0 && gc.Mesos.TemplateVersion != 2 { + log.Warn("Template version 1 is deprecated, please use version 2, see TemplateVersion.") + gc.Mesos.TemplateVersion = 1 + } else { + gc.Mesos.TemplateVersion = 2 + } + } + if gc.Eureka != nil { if gc.Eureka.Delay != 0 { log.Warn("Delay has been deprecated -- please use RefreshSeconds") diff --git a/provider/ecs/config_test.go b/provider/ecs/config_test.go index d9af4de67..95c745c82 100644 --- a/provider/ecs/config_test.go +++ b/provider/ecs/config_test.go @@ -351,11 +351,11 @@ func TestBuildConfiguration(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - provider := &Provider{} + p := &Provider{} services := fakeLoadTraefikLabels(test.services) - got, err := provider.buildConfiguration(services) + got, err := p.buildConfiguration(services) assert.Equal(t, test.err, err) // , err.Error() assert.Equal(t, test.expected, got, test.desc) }) diff --git a/provider/mesos/config.go b/provider/mesos/config.go index 43795edbd..34f484b8d 100644 --- a/provider/mesos/config.go +++ b/provider/mesos/config.go @@ -7,7 +7,6 @@ import ( "strings" "text/template" - "github.com/BurntSushi/ty/fun" "github.com/containous/traefik/log" "github.com/containous/traefik/provider" "github.com/containous/traefik/provider/label" @@ -15,82 +14,63 @@ import ( "github.com/mesosphere/mesos-dns/records/state" ) -func (p *Provider) buildConfiguration(tasks []state.Task) *types.Configuration { +type taskData struct { + state.Task + TraefikLabels map[string]string +} + +func (p *Provider) buildConfigurationV2(tasks []state.Task) *types.Configuration { var mesosFuncMap = template.FuncMap{ - "getDomain": getFuncStringValue(label.TraefikDomain, p.Domain), + "getDomain": label.GetFuncString(label.TraefikDomain, p.Domain), "getID": getID, // Backend functions "getBackendName": getBackendName, - "getCircuitBreaker": getCircuitBreaker, - "getLoadBalancer": getLoadBalancer, - "getMaxConn": getMaxConn, - "getHealthCheck": getHealthCheck, - "getBuffering": getBuffering, + "getCircuitBreaker": label.GetCircuitBreaker, + "getLoadBalancer": label.GetLoadBalancer, + "getMaxConn": label.GetMaxConn, + "getHealthCheck": label.GetHealthCheck, + "getBuffering": label.GetBuffering, "getServers": p.getServers, "getHost": p.getHost, "getServerPort": p.getServerPort, - // TODO Deprecated [breaking] - "getProtocol": getFuncApplicationStringValue(label.TraefikProtocol, label.DefaultProtocol), - // TODO Deprecated [breaking] - "getWeight": getFuncApplicationStringValue(label.TraefikWeight, label.DefaultWeight), - // TODO Deprecated [breaking] replaced by getBackendName - "getBackend": getBackend, - // TODO Deprecated [breaking] - "getPort": p.getPort, - // Frontend functions "getFrontEndName": getFrontendName, - "getEntryPoints": getFuncSliceStringValue(label.TraefikFrontendEntryPoints), - "getBasicAuth": getFuncSliceStringValue(label.TraefikFrontendAuthBasic), - "getPriority": getFuncStringValue(label.TraefikFrontendPriority, label.DefaultFrontendPriority), - "getPassHostHeader": getFuncBoolValue(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool), - "getPassTLSCert": getFuncBoolValue(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), + "getEntryPoints": label.GetFuncSliceString(label.TraefikFrontendEntryPoints), + "getBasicAuth": label.GetFuncSliceString(label.TraefikFrontendAuthBasic), + "getPriority": label.GetFuncInt(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt), + "getPassHostHeader": label.GetFuncBool(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool), + "getPassTLSCert": label.GetFuncBool(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), "getFrontendRule": p.getFrontendRule, - "getRedirect": getRedirect, - "getErrorPages": getErrorPages, - "getRateLimit": getRateLimit, - "getHeaders": getHeaders, - "getWhiteList": getWhiteList, - - // TODO Deprecated [breaking] - "getFrontendBackend": getBackendName, + "getRedirect": label.GetRedirect, + "getErrorPages": label.GetErrorPages, + "getRateLimit": label.GetRateLimit, + "getHeaders": label.GetHeaders, + "getWhiteList": label.GetWhiteList, } // filter tasks - filteredTasks := fun.Filter(func(task state.Task) bool { - return taskFilter(task, p.ExposedByDefault) - }, tasks).([]state.Task) - - // Deprecated - var filteredApps []state.Task - uniqueApps := make(map[string]struct{}) - for _, task := range filteredTasks { - if _, ok := uniqueApps[task.DiscoveryInfo.Name]; !ok { - uniqueApps[task.DiscoveryInfo.Name] = struct{}{} - filteredApps = append(filteredApps, task) + appsTasks := make(map[string][]taskData) + for _, task := range tasks { + data := taskData{ + Task: task, + TraefikLabels: extractLabels(task), } - } - - appsTasks := make(map[string][]state.Task) - for _, task := range filteredTasks { - if _, ok := appsTasks[task.DiscoveryInfo.Name]; !ok { - appsTasks[task.DiscoveryInfo.Name] = []state.Task{task} - } else { - appsTasks[task.DiscoveryInfo.Name] = append(appsTasks[task.DiscoveryInfo.Name], task) + if taskFilter(data, p.ExposedByDefault) { + if _, ok := appsTasks[task.DiscoveryInfo.Name]; !ok { + appsTasks[task.DiscoveryInfo.Name] = []taskData{data} + } else { + appsTasks[task.DiscoveryInfo.Name] = append(appsTasks[task.DiscoveryInfo.Name], data) + } } } templateObjects := struct { - ApplicationsTasks map[string][]state.Task - Applications []state.Task // Deprecated - Tasks []state.Task // Deprecated + ApplicationsTasks map[string][]taskData Domain string }{ ApplicationsTasks: appsTasks, - Applications: filteredApps, // Deprecated - Tasks: filteredTasks, // Deprecated Domain: p.Domain, } @@ -98,10 +78,11 @@ func (p *Provider) buildConfiguration(tasks []state.Task) *types.Configuration { if err != nil { log.Error(err) } + return configuration } -func taskFilter(task state.Task, exposedByDefaultFlag bool) bool { +func taskFilter(task taskData, exposedByDefaultFlag bool) bool { if len(task.DiscoveryInfo.Ports.DiscoveryPorts) == 0 { log.Debugf("Filtering Mesos task without port %s", task.Name) return false @@ -113,8 +94,8 @@ func taskFilter(task state.Task, exposedByDefaultFlag bool) bool { } // filter indeterminable task port - portIndexLabel := getStringValue(task, label.TraefikPortIndex, "") - portValueLabel := getStringValue(task, label.TraefikPort, "") + portIndexLabel := label.GetStringValue(task.TraefikLabels, label.TraefikPortIndex, "") + portValueLabel := label.GetStringValue(task.TraefikLabels, 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 @@ -156,84 +137,19 @@ func taskFilter(task state.Task, exposedByDefaultFlag bool) bool { return true } -func getID(task state.Task) string { +func getID(task taskData) string { return provider.Normalize(task.ID) } -// Deprecated -func getBackend(task state.Task, apps []state.Task) string { - _, err := getApplication(task, apps) - if err != nil { - log.Error(err) - return "" - } - return getBackendName(task) +func getBackendName(task taskData) string { + return label.GetStringValue(task.TraefikLabels, label.TraefikBackend, provider.Normalize(task.DiscoveryInfo.Name)) } -func getBackendName(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 { +func getFrontendName(task taskData) string { // TODO task.ID -> task.Name + task.ID return provider.Normalize(task.ID) } -func (p *Provider) getServerPort(task state.Task) string { - plv := getIntValue(task, 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(task, label.TraefikPort, ""); len(pv) > 0 { - return pv - } - - for _, port := range task.DiscoveryInfo.Ports.DiscoveryPorts { - return strconv.Itoa(port.Number) - } - return "" -} - -// Deprecated -func (p *Provider) getPort(task state.Task, applications []state.Task) string { - _, err := getApplication(task, applications) - if err != nil { - log.Error(err) - return "" - } - - plv := getIntValue(task, 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(task, 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, "/"), "/") @@ -244,78 +160,16 @@ func (p *Provider) getSubDomain(name string) string { return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1) } -func getCircuitBreaker(task state.Task) *types.CircuitBreaker { - circuitBreaker := getStringValue(task, label.TraefikBackendCircuitBreakerExpression, "") - if len(circuitBreaker) == 0 { - return nil +// 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 taskData) string { + if v := label.GetStringValue(task.TraefikLabels, label.TraefikFrontendRule, ""); len(v) > 0 { + return v } - return &types.CircuitBreaker{Expression: circuitBreaker} + return "Host:" + strings.ToLower(strings.Replace(p.getSubDomain(task.DiscoveryInfo.Name), "_", "-", -1)) + "." + p.Domain } -func getLoadBalancer(task state.Task) *types.LoadBalancer { - if !hasPrefix(task, label.TraefikBackendLoadBalancer) { - return nil - } - - method := getStringValue(task, label.TraefikBackendLoadBalancerMethod, label.DefaultBackendLoadBalancerMethod) - - lb := &types.LoadBalancer{ - Method: method, - } - - if getBoolValue(task, label.TraefikBackendLoadBalancerStickiness, false) { - cookieName := getStringValue(task, label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName) - lb.Stickiness = &types.Stickiness{CookieName: cookieName} - } - - return lb -} - -func getMaxConn(task state.Task) *types.MaxConn { - amount := getInt64Value(task, label.TraefikBackendMaxConnAmount, math.MinInt64) - extractorFunc := getStringValue(task, label.TraefikBackendMaxConnExtractorFunc, label.DefaultBackendMaxconnExtractorFunc) - - if amount == math.MinInt64 || len(extractorFunc) == 0 { - return nil - } - - return &types.MaxConn{ - Amount: amount, - ExtractorFunc: extractorFunc, - } -} - -func getHealthCheck(task state.Task) *types.HealthCheck { - path := getStringValue(task, label.TraefikBackendHealthCheckPath, "") - if len(path) == 0 { - return nil - } - - port := getIntValue(task, label.TraefikBackendHealthCheckPort, label.DefaultBackendHealthCheckPort, math.MaxInt32) - interval := getStringValue(task, label.TraefikBackendHealthCheckInterval, "") - - return &types.HealthCheck{ - Path: path, - Port: port, - Interval: interval, - } -} - -func getBuffering(task state.Task) *types.Buffering { - if !hasPrefix(task, label.TraefikBackendBuffering) { - return nil - } - - return &types.Buffering{ - MaxRequestBodyBytes: getInt64Value(task, label.TraefikBackendBufferingMaxRequestBodyBytes, 0), - MaxResponseBodyBytes: getInt64Value(task, label.TraefikBackendBufferingMaxResponseBodyBytes, 0), - MemRequestBodyBytes: getInt64Value(task, label.TraefikBackendBufferingMemRequestBodyBytes, 0), - MemResponseBodyBytes: getInt64Value(task, label.TraefikBackendBufferingMemResponseBodyBytes, 0), - RetryExpression: getStringValue(task, label.TraefikBackendBufferingRetryExpression, ""), - } -} - -func (p *Provider) getServers(tasks []state.Task) map[string]types.Server { +func (p *Provider) getServers(tasks []taskData) map[string]types.Server { var servers map[string]types.Server for _, task := range tasks { @@ -323,236 +177,56 @@ func (p *Provider) getServers(tasks []state.Task) map[string]types.Server { servers = make(map[string]types.Server) } - protocol := getStringValue(task, label.TraefikProtocol, label.DefaultProtocol) + protocol := label.GetStringValue(task.TraefikLabels, label.TraefikProtocol, label.DefaultProtocol) host := p.getHost(task) port := p.getServerPort(task) serverName := "server-" + getID(task) servers[serverName] = types.Server{ URL: fmt.Sprintf("%s://%s:%s", protocol, host, port), - Weight: getIntValue(task, label.TraefikWeight, label.DefaultWeightInt, math.MaxInt32), + Weight: getIntValue(task.TraefikLabels, label.TraefikWeight, label.DefaultWeightInt, math.MaxInt32), } } return servers } -func getWhiteList(task state.Task) *types.WhiteList { - ranges := getSliceStringValue(task, label.TraefikFrontendWhiteListSourceRange) - if len(ranges) > 0 { - return &types.WhiteList{ - SourceRange: ranges, - UseXForwardedFor: getBoolValue(task, label.TraefikFrontendWhiteListUseXForwardedFor, false), - } - } - - return nil +func (p *Provider) getHost(task taskData) string { + return task.IP(strings.Split(p.IPSources, ",")...) } -func getRedirect(task state.Task) *types.Redirect { - permanent := getBoolValue(task, label.TraefikFrontendRedirectPermanent, false) - - if hasLabel(task, label.TraefikFrontendRedirectEntryPoint) { - return &types.Redirect{ - EntryPoint: getStringValue(task, label.TraefikFrontendRedirectEntryPoint, ""), - Permanent: permanent, - } +func (p *Provider) getServerPort(task taskData) string { + plv := getIntValue(task.TraefikLabels, label.TraefikPortIndex, math.MinInt32, len(task.DiscoveryInfo.Ports.DiscoveryPorts)-1) + if plv >= 0 { + return strconv.Itoa(task.DiscoveryInfo.Ports.DiscoveryPorts[plv].Number) } - if hasLabel(task, label.TraefikFrontendRedirectRegex) && - hasLabel(task, label.TraefikFrontendRedirectReplacement) { - return &types.Redirect{ - Regex: getStringValue(task, label.TraefikFrontendRedirectRegex, ""), - Replacement: getStringValue(task, label.TraefikFrontendRedirectReplacement, ""), - Permanent: permanent, - } + if pv := label.GetStringValue(task.TraefikLabels, label.TraefikPort, ""); len(pv) > 0 { + return pv } - return nil + for _, port := range task.DiscoveryInfo.Ports.DiscoveryPorts { + return strconv.Itoa(port.Number) + } + return "" } -func getErrorPages(task state.Task) map[string]*types.ErrorPage { - prefix := label.Prefix + label.BaseFrontendErrorPage - labels := taskLabelsToMap(task) - return label.ParseErrorPages(labels, prefix, label.RegexpFrontendErrorPage) -} - -func getRateLimit(task state.Task) *types.RateLimit { - extractorFunc := getStringValue(task, label.TraefikFrontendRateLimitExtractorFunc, "") - if len(extractorFunc) == 0 { - return nil - } - - labels := taskLabelsToMap(task) - prefix := label.Prefix + label.BaseFrontendRateLimit - limits := label.ParseRateSets(labels, prefix, label.RegexpFrontendRateLimit) - - return &types.RateLimit{ - ExtractorFunc: extractorFunc, - RateSet: limits, - } -} - -func getHeaders(task state.Task) *types.Headers { - labels := taskLabelsToMap(task) - - headers := &types.Headers{ - CustomRequestHeaders: label.GetMapValue(labels, label.TraefikFrontendRequestHeaders), - CustomResponseHeaders: label.GetMapValue(labels, label.TraefikFrontendResponseHeaders), - SSLProxyHeaders: label.GetMapValue(labels, label.TraefikFrontendSSLProxyHeaders), - AllowedHosts: label.GetSliceStringValue(labels, label.TraefikFrontendAllowedHosts), - HostsProxyHeaders: label.GetSliceStringValue(labels, label.TraefikFrontendHostsProxyHeaders), - STSSeconds: label.GetInt64Value(labels, label.TraefikFrontendSTSSeconds, 0), - SSLRedirect: label.GetBoolValue(labels, label.TraefikFrontendSSLRedirect, false), - SSLTemporaryRedirect: label.GetBoolValue(labels, label.TraefikFrontendSSLTemporaryRedirect, false), - STSIncludeSubdomains: label.GetBoolValue(labels, label.TraefikFrontendSTSIncludeSubdomains, false), - STSPreload: label.GetBoolValue(labels, label.TraefikFrontendSTSPreload, false), - ForceSTSHeader: label.GetBoolValue(labels, label.TraefikFrontendForceSTSHeader, false), - FrameDeny: label.GetBoolValue(labels, label.TraefikFrontendFrameDeny, false), - ContentTypeNosniff: label.GetBoolValue(labels, label.TraefikFrontendContentTypeNosniff, false), - BrowserXSSFilter: label.GetBoolValue(labels, label.TraefikFrontendBrowserXSSFilter, false), - IsDevelopment: label.GetBoolValue(labels, label.TraefikFrontendIsDevelopment, false), - SSLHost: label.GetStringValue(labels, label.TraefikFrontendSSLHost, ""), - CustomFrameOptionsValue: label.GetStringValue(labels, label.TraefikFrontendCustomFrameOptionsValue, ""), - ContentSecurityPolicy: label.GetStringValue(labels, label.TraefikFrontendContentSecurityPolicy, ""), - PublicKey: label.GetStringValue(labels, label.TraefikFrontendPublicKey, ""), - ReferrerPolicy: label.GetStringValue(labels, label.TraefikFrontendReferrerPolicy, ""), - CustomBrowserXSSValue: label.GetStringValue(labels, label.TraefikFrontendCustomBrowserXSSValue, ""), - } - - if !headers.HasSecureHeadersDefined() && !headers.HasCustomHeadersDefined() { - return nil - } - - return headers -} - -func isEnabled(task state.Task, exposedByDefault bool) bool { - return getBoolValue(task, label.TraefikEnable, exposedByDefault) +func isEnabled(task taskData, exposedByDefault bool) bool { + return label.GetBoolValue(task.TraefikLabels, label.TraefikEnable, exposedByDefault) } // Label functions -// Deprecated -func getFuncApplicationStringValue(labelName string, defaultValue string) func(task state.Task, applications []state.Task) string { - return func(task state.Task, applications []state.Task) string { - _, err := getApplication(task, applications) - if err != nil { - log.Error(err) - return defaultValue - } - - return getStringValue(task, labelName, defaultValue) - } -} - -func getFuncStringValue(labelName string, defaultValue string) func(task state.Task) string { - return func(task state.Task) string { - return getStringValue(task, labelName, defaultValue) - } -} - -func getFuncBoolValue(labelName string, defaultValue bool) func(task state.Task) bool { - return func(task state.Task) bool { - return getBoolValue(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 && len(lbl.Value) > 0 { - return lbl.Value - } +func getIntValue(labels map[string]string, labelName string, defaultValue int, maxValue int) int { + value := label.GetIntValue(labels, labelName, defaultValue) + if value <= maxValue { + return value } + log.Warnf("The value %q for %q exceed the max authorized value %q, falling back to %v.", value, labelName, maxValue, defaultValue) 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 -} - -// Deprecated -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 hasPrefix(task state.Task, prefix string) bool { - for _, lbl := range task.Labels { - if strings.HasPrefix(lbl.Key, prefix) { - return true - } - } - return false -} - -func getInt64Value(task state.Task, labelName string, defaultValue int64) int64 { - for _, lbl := range task.Labels { - if lbl.Key == labelName { - value, err := strconv.ParseInt(lbl.Value, 10, 64) - if err != nil { - log.Warnf("Unable to parse %q: %q, falling back to %v. %v", labelName, lbl.Value, defaultValue, err) - } - return value - } - } - return defaultValue -} - -func hasLabel(task state.Task, label string) bool { - for _, lbl := range task.Labels { - if lbl.Key == label { - return true - } - } - return false -} - -func taskLabelsToMap(task state.Task) map[string]string { +func extractLabels(task state.Task) map[string]string { labels := make(map[string]string) for _, lbl := range task.Labels { labels[lbl.Key] = lbl.Value diff --git a/provider/mesos/config_root.go b/provider/mesos/config_root.go new file mode 100644 index 000000000..487c366ea --- /dev/null +++ b/provider/mesos/config_root.go @@ -0,0 +1,13 @@ +package mesos + +import ( + "github.com/containous/traefik/types" + "github.com/mesosphere/mesos-dns/records/state" +) + +func (p *Provider) buildConfiguration(tasks []state.Task) *types.Configuration { + if p.TemplateVersion == 1 { + return p.buildConfigurationV1(tasks) + } + return p.buildConfigurationV2(tasks) +} diff --git a/provider/mesos/config_test.go b/provider/mesos/config_test.go index 730f336b3..335858723 100644 --- a/provider/mesos/config_test.go +++ b/provider/mesos/config_test.go @@ -7,7 +7,6 @@ import ( "github.com/containous/flaeg" "github.com/containous/traefik/provider/label" "github.com/containous/traefik/types" - "github.com/mesos/mesos-go/upid" "github.com/mesosphere/mesos-dns/records/state" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -15,7 +14,7 @@ import ( func TestBuildConfiguration(t *testing.T) { p := &Provider{ - Domain: "docker.localhost", + Domain: "mesos.localhost", ExposedByDefault: true, IPSources: "host", } @@ -70,7 +69,7 @@ func TestBuildConfiguration(t *testing.T) { PassHostHeader: true, Routes: map[string]types.Route{ "route-host-ID1": { - Rule: "Host:name1.docker.localhost", + Rule: "Host:name1.mesos.localhost", }, }, }, @@ -81,7 +80,7 @@ func TestBuildConfiguration(t *testing.T) { PassHostHeader: true, Routes: map[string]types.Route{ "route-host-ID3": { - Rule: "Host:name2.docker.localhost", + Rule: "Host:name2.mesos.localhost", }, }, }, @@ -333,8 +332,9 @@ func TestBuildConfiguration(t *testing.T) { for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { + t.Parallel() - actualConfig := p.buildConfiguration(test.tasks) + actualConfig := p.buildConfigurationV2(test.tasks) require.NotNil(t, actualConfig) assert.Equal(t, test.expectedBackends, actualConfig.Backends) @@ -346,25 +346,25 @@ func TestBuildConfiguration(t *testing.T) { func TestTaskFilter(t *testing.T) { testCases := []struct { desc string - mesosTask state.Task + mesosTask taskData exposedByDefault bool expected bool }{ { desc: "no task", - mesosTask: state.Task{}, + mesosTask: taskData{}, exposedByDefault: true, expected: false, }, { desc: "task not healthy", - mesosTask: aTask("test", withStatus(withState("TASK_RUNNING"))), + mesosTask: aTaskData("test", withStatus(withState("TASK_RUNNING"))), exposedByDefault: true, expected: false, }, { desc: "exposedByDefault false and traefik.enable false", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withDefaultStatus(), withLabel(label.TraefikEnable, "false"), withInfo("test", withPorts(withPortTCP(80, "WEB"))), @@ -374,7 +374,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "traefik.enable = true", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withDefaultStatus(), withLabel(label.TraefikEnable, "true"), withInfo("test", withPorts(withPortTCP(80, "WEB"))), @@ -384,7 +384,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "exposedByDefault true and traefik.enable true", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withDefaultStatus(), withLabel(label.TraefikEnable, "true"), withInfo("test", withPorts(withPortTCP(80, "WEB"))), @@ -394,7 +394,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "exposedByDefault true and traefik.enable false", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withDefaultStatus(), withLabel(label.TraefikEnable, "false"), withInfo("test", withPorts(withPortTCP(80, "WEB"))), @@ -404,7 +404,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "traefik.portIndex and traefik.port both set", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withDefaultStatus(), withLabel(label.TraefikEnable, "true"), withLabel(label.TraefikPortIndex, "1"), @@ -416,7 +416,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "valid traefik.portIndex", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withDefaultStatus(), withLabel(label.TraefikEnable, "true"), withLabel(label.TraefikPortIndex, "1"), @@ -430,7 +430,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "default to first port index", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withDefaultStatus(), withLabel(label.TraefikEnable, "true"), withInfo("test", withPorts( @@ -443,7 +443,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "traefik.portIndex and discoveryPorts don't correspond", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withDefaultStatus(), withLabel(label.TraefikEnable, "true"), withLabel(label.TraefikPortIndex, "1"), @@ -454,7 +454,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "traefik.portIndex and discoveryPorts correspond", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withDefaultStatus(), withLabel(label.TraefikEnable, "true"), withLabel(label.TraefikPortIndex, "0"), @@ -465,7 +465,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "traefik.port is not an integer", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withDefaultStatus(), withLabel(label.TraefikEnable, "true"), withLabel(label.TraefikPort, "TRAEFIK"), @@ -476,7 +476,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "traefik.port is not the same as discovery.port", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withDefaultStatus(), withLabel(label.TraefikEnable, "true"), withLabel(label.TraefikPort, "443"), @@ -487,7 +487,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "traefik.port is the same as discovery.port", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withDefaultStatus(), withLabel(label.TraefikEnable, "true"), withLabel(label.TraefikPort, "80"), @@ -498,7 +498,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "healthy nil", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withStatus( withState("TASK_RUNNING"), ), @@ -511,7 +511,7 @@ func TestTaskFilter(t *testing.T) { }, { desc: "healthy false", - mesosTask: aTask("test", + mesosTask: aTaskData("test", withStatus( withState("TASK_RUNNING"), withHealthy(false), @@ -542,35 +542,6 @@ func TestTaskFilter(t *testing.T) { } } -func TestTaskRecords(t *testing.T) { - var task = state.Task{ - SlaveID: "s_id", - State: "TASK_RUNNING", - } - var framework = state.Framework{ - Tasks: []state.Task{task}, - } - var slave = state.Slave{ - ID: "s_id", - Hostname: "127.0.0.1", - } - slave.PID.UPID = &upid.UPID{} - slave.PID.Host = slave.Hostname - - var taskState = state.State{ - Slaves: []state.Slave{slave}, - Frameworks: []state.Framework{framework}, - } - - var p = taskRecords(taskState) - if len(p) == 0 { - t.Fatal("No task") - } - if p[0].SlaveIP != slave.Hostname { - t.Fatalf("The SlaveIP (%s) should be set with the slave hostname (%s)", p[0].SlaveID, slave.Hostname) - } -} - func TestGetSubDomain(t *testing.T) { providerGroups := &Provider{GroupsAsSubDomains: true} providerNoGroups := &Provider{GroupsAsSubDomains: false} @@ -604,296 +575,23 @@ func TestGetSubDomain(t *testing.T) { } } -func TestGetCircuitBreaker(t *testing.T) { - testCases := []struct { - desc string - task state.Task - expected *types.CircuitBreaker - }{ - { - desc: "should return nil when no CB labels", - task: aTask("ID1", - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - { - desc: "should return a struct CB when CB labels are set", - task: aTask("ID1", - withLabel(label.TraefikBackendCircuitBreakerExpression, "NetworkErrorRatio() > 0.5"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.CircuitBreaker{ - Expression: "NetworkErrorRatio() > 0.5", - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getCircuitBreaker(test.task) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetLoadBalancer(t *testing.T) { - testCases := []struct { - desc string - task state.Task - expected *types.LoadBalancer - }{ - { - desc: "should return nil when no LB labels", - task: aTask("ID1", - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - { - desc: "should return a struct when labels are set", - task: aTask("ID1", - withLabel(label.TraefikBackendLoadBalancerMethod, "drr"), - withLabel(label.TraefikBackendLoadBalancerStickiness, "true"), - withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "foo"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.LoadBalancer{ - Method: "drr", - Stickiness: &types.Stickiness{ - CookieName: "foo", - }, - }, - }, - { - desc: "should return a nil Stickiness when Stickiness is not set", - task: aTask("ID1", - withLabel(label.TraefikBackendLoadBalancerMethod, "drr"), - withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "foo"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.LoadBalancer{ - Method: "drr", - Stickiness: nil, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getLoadBalancer(test.task) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetMaxConn(t *testing.T) { - testCases := []struct { - desc string - task state.Task - expected *types.MaxConn - }{ - { - desc: "should return nil when no max conn labels", - task: aTask("ID1", - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - { - desc: "should return nil when no amount label", - task: aTask("ID1", - withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - { - desc: "should return default when empty extractorFunc label", - task: aTask("ID1", - withLabel(label.TraefikBackendMaxConnExtractorFunc, ""), - withLabel(label.TraefikBackendMaxConnAmount, "666"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.MaxConn{ - ExtractorFunc: "request.host", - Amount: 666, - }, - }, - { - desc: "should return a struct when max conn labels are set", - task: aTask("ID1", - withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"), - withLabel(label.TraefikBackendMaxConnAmount, "666"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.MaxConn{ - ExtractorFunc: "client.ip", - Amount: 666, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getMaxConn(test.task) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetHealthCheck(t *testing.T) { - testCases := []struct { - desc string - task state.Task - expected *types.HealthCheck - }{ - { - desc: "should return nil when no health check labels", - task: aTask("ID1", - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - { - desc: "should return nil when no health check Path label", - task: aTask("ID1", - withLabel(label.TraefikBackendHealthCheckPort, "80"), - withLabel(label.TraefikBackendHealthCheckInterval, "6"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - { - desc: "should return a struct when health check labels are set", - task: aTask("ID1", - withLabel(label.TraefikBackendHealthCheckPath, "/health"), - withLabel(label.TraefikBackendHealthCheckPort, "80"), - withLabel(label.TraefikBackendHealthCheckInterval, "6"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.HealthCheck{ - Path: "/health", - Port: 80, - Interval: "6", - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getHealthCheck(test.task) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetBuffering(t *testing.T) { - testCases := []struct { - desc string - task state.Task - expected *types.Buffering - }{ - { - desc: "should return nil when no buffering labels", - task: aTask("ID1", - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - { - desc: "should return a struct when health check labels are set", - task: aTask("ID1", - withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"), - withLabel(label.TraefikBackendBufferingMemResponseBodyBytes, "2097152"), - withLabel(label.TraefikBackendBufferingMaxRequestBodyBytes, "10485760"), - withLabel(label.TraefikBackendBufferingMemRequestBodyBytes, "2097152"), - withLabel(label.TraefikBackendBufferingRetryExpression, "IsNetworkError() && Attempts() <= 2"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.Buffering{ - MaxResponseBodyBytes: 10485760, - MemResponseBodyBytes: 2097152, - MaxRequestBodyBytes: 10485760, - MemRequestBodyBytes: 2097152, - RetryExpression: "IsNetworkError() && Attempts() <= 2", - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getBuffering(test.task) - - assert.Equal(t, test.expected, actual) - }) - } -} - func TestGetServers(t *testing.T) { testCases := []struct { desc string - tasks []state.Task + tasks []taskData expected map[string]types.Server }{ { desc: "", - tasks: []state.Task{ + tasks: []taskData{ // App 1 - aTask("ID1", + aTaskData("ID1", withIP("10.10.10.10"), withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), withStatus(withHealthy(true), withState("TASK_RUNNING")), ), - aTask("ID2", + aTaskData("ID2", withIP("10.10.10.11"), withLabel(label.TraefikWeight, "18"), withInfo("name1", @@ -901,14 +599,14 @@ func TestGetServers(t *testing.T) { withStatus(withHealthy(true), withState("TASK_RUNNING")), ), // App 2 - aTask("ID3", + aTaskData("ID3", withLabel(label.TraefikWeight, "12"), withIP("20.10.10.10"), withInfo("name2", withPorts(withPort("TCP", 80, "WEB"))), withStatus(withHealthy(true), withState("TASK_RUNNING")), ), - aTask("ID4", + aTaskData("ID4", withLabel(label.TraefikWeight, "6"), withIP("20.10.10.11"), withInfo("name2", @@ -954,396 +652,3 @@ func TestGetServers(t *testing.T) { }) } } - -func TestWhiteList(t *testing.T) { - testCases := []struct { - desc string - task state.Task - expected *types.WhiteList - }{ - { - desc: "should return nil when no white list labels", - task: aTask("ID1", - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - { - desc: "should return a struct when only range", - task: aTask("ID1", - withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.WhiteList{ - SourceRange: []string{ - "10.10.10.10", - }, - UseXForwardedFor: false, - }, - }, - { - desc: "should return a struct when range and UseXForwardedFor", - task: aTask("ID1", - withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"), - withLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.WhiteList{ - SourceRange: []string{ - "10.10.10.10", - }, - UseXForwardedFor: true, - }, - }, - { - desc: "should return nil when only UseXForwardedFor", - task: aTask("ID1", - withLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getWhiteList(test.task) - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetRedirect(t *testing.T) { - testCases := []struct { - desc string - task state.Task - expected *types.Redirect - }{ - - { - desc: "should return nil when no redirect labels", - task: aTask("ID1", - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - { - desc: "should use only entry point tag when mix regex redirect and entry point redirect", - task: aTask("ID1", - withLabel(label.TraefikFrontendRedirectEntryPoint, "https"), - withLabel(label.TraefikFrontendRedirectRegex, "(.*)"), - withLabel(label.TraefikFrontendRedirectReplacement, "$1"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.Redirect{ - EntryPoint: "https", - }, - }, - { - desc: "should return a struct when entry point redirect label", - task: aTask("ID1", - withLabel(label.TraefikFrontendRedirectEntryPoint, "https"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.Redirect{ - EntryPoint: "https", - }, - }, - { - desc: "should return a struct when entry point redirect label (permanent)", - task: aTask("ID1", - withLabel(label.TraefikFrontendRedirectEntryPoint, "https"), - withLabel(label.TraefikFrontendRedirectPermanent, "true"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.Redirect{ - EntryPoint: "https", - Permanent: true, - }, - }, - { - desc: "should return a struct when regex redirect labels", - task: aTask("ID1", - withLabel(label.TraefikFrontendRedirectRegex, "(.*)"), - withLabel(label.TraefikFrontendRedirectReplacement, "$1"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.Redirect{ - Regex: "(.*)", - Replacement: "$1", - }, - }, - { - desc: "should return a struct when regex redirect labels (permanent)", - task: aTask("ID1", - withLabel(label.TraefikFrontendRedirectRegex, "(.*)"), - withLabel(label.TraefikFrontendRedirectReplacement, "$1"), - withLabel(label.TraefikFrontendRedirectPermanent, "true"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.Redirect{ - Regex: "(.*)", - Replacement: "$1", - Permanent: true, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getRedirect(test.task) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetErrorPages(t *testing.T) { - testCases := []struct { - desc string - task state.Task - expected map[string]*types.ErrorPage - }{ - { - desc: "2 errors pages", - task: aTask("ID1", - withIP("10.10.10.10"), - withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageStatus, "404"), - withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageBackend, "foo_backend"), - withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageQuery, "foo_query"), - withLabel(label.Prefix+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageStatus, "500,600"), - withLabel(label.Prefix+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageBackend, "bar_backend"), - withLabel(label.Prefix+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageQuery, "bar_query"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: map[string]*types.ErrorPage{ - "foo": { - Status: []string{"404"}, - Query: "foo_query", - Backend: "foo_backend", - }, - "bar": { - Status: []string{"500", "600"}, - Query: "bar_query", - Backend: "bar_backend", - }, - }, - }, - { - desc: "only status field", - task: aTask("ID1", - withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageStatus, "404"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: map[string]*types.ErrorPage{ - "foo": { - Status: []string{"404"}, - }, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getErrorPages(test.task) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetRateLimit(t *testing.T) { - testCases := []struct { - desc string - task state.Task - expected *types.RateLimit - }{ - { - desc: "should return nil when no rate limit labels", - task: aTask("ID1", - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - { - desc: "should return a struct when rate limit labels are defined", - task: aTask("ID1", - withLabel(label.TraefikFrontendRateLimitExtractorFunc, "client.ip"), - withLabel(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitPeriod, "6"), - withLabel(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitAverage, "12"), - withLabel(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitBurst, "18"), - withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitPeriod, "3"), - withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitAverage, "6"), - withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitBurst, "9"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.RateLimit{ - ExtractorFunc: "client.ip", - RateSet: map[string]*types.Rate{ - "foo": { - Period: flaeg.Duration(6 * time.Second), - Average: 12, - Burst: 18, - }, - "bar": { - Period: flaeg.Duration(3 * time.Second), - Average: 6, - Burst: 9, - }, - }, - }, - }, - { - desc: "should return nil when ExtractorFunc is missing", - task: aTask("ID1", - withLabel(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitPeriod, "6"), - withLabel(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitAverage, "12"), - withLabel(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitBurst, "18"), - withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitPeriod, "3"), - withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitAverage, "6"), - withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitBurst, "9"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getRateLimit(test.task) - - assert.Equal(t, test.expected, actual) - }) - } -} - -func TestGetHeaders(t *testing.T) { - testCases := []struct { - desc string - task state.Task - expected *types.Headers - }{ - { - desc: "should return nil when no custom headers options are set", - task: aTask("ID1", - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: nil, - }, - { - desc: "should return a struct when all custom headers options are set", - task: aTask("ID1", - withLabel(label.TraefikFrontendRequestHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"), - withLabel(label.TraefikFrontendResponseHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"), - withLabel(label.TraefikFrontendSSLProxyHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8"), - withLabel(label.TraefikFrontendAllowedHosts, "foo,bar,bor"), - withLabel(label.TraefikFrontendHostsProxyHeaders, "foo,bar,bor"), - withLabel(label.TraefikFrontendSSLHost, "foo"), - withLabel(label.TraefikFrontendCustomFrameOptionsValue, "foo"), - withLabel(label.TraefikFrontendContentSecurityPolicy, "foo"), - withLabel(label.TraefikFrontendPublicKey, "foo"), - withLabel(label.TraefikFrontendReferrerPolicy, "foo"), - withLabel(label.TraefikFrontendCustomBrowserXSSValue, "foo"), - withLabel(label.TraefikFrontendSTSSeconds, "666"), - withLabel(label.TraefikFrontendSSLRedirect, "true"), - withLabel(label.TraefikFrontendSSLTemporaryRedirect, "true"), - withLabel(label.TraefikFrontendSTSIncludeSubdomains, "true"), - withLabel(label.TraefikFrontendSTSPreload, "true"), - withLabel(label.TraefikFrontendForceSTSHeader, "true"), - withLabel(label.TraefikFrontendFrameDeny, "true"), - withLabel(label.TraefikFrontendContentTypeNosniff, "true"), - withLabel(label.TraefikFrontendBrowserXSSFilter, "true"), - withLabel(label.TraefikFrontendIsDevelopment, "true"), - withIP("10.10.10.10"), - withInfo("name1", withPorts(withPort("TCP", 80, "WEB"))), - withDefaultStatus(), - ), - expected: &types.Headers{ - CustomRequestHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - CustomResponseHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - SSLProxyHeaders: map[string]string{ - "Access-Control-Allow-Methods": "POST,GET,OPTIONS", - "Content-Type": "application/json; charset=utf-8", - }, - AllowedHosts: []string{"foo", "bar", "bor"}, - HostsProxyHeaders: []string{"foo", "bar", "bor"}, - SSLHost: "foo", - CustomFrameOptionsValue: "foo", - ContentSecurityPolicy: "foo", - PublicKey: "foo", - ReferrerPolicy: "foo", - CustomBrowserXSSValue: "foo", - STSSeconds: 666, - SSLRedirect: true, - SSLTemporaryRedirect: true, - STSIncludeSubdomains: true, - STSPreload: true, - ForceSTSHeader: true, - FrameDeny: true, - ContentTypeNosniff: true, - BrowserXSSFilter: true, - IsDevelopment: true, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - actual := getHeaders(test.task) - - assert.Equal(t, test.expected, actual) - }) - } -} diff --git a/provider/mesos/deprecated_config.go b/provider/mesos/deprecated_config.go new file mode 100644 index 000000000..f4469496f --- /dev/null +++ b/provider/mesos/deprecated_config.go @@ -0,0 +1,327 @@ +package mesos + +import ( + "fmt" + "math" + "strconv" + "strings" + "text/template" + + "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/state" +) + +func (p *Provider) buildConfigurationV1(tasks []state.Task) *types.Configuration { + var mesosFuncMap = template.FuncMap{ + "getDomain": getFuncStringValueV1(label.TraefikDomain, p.Domain), + "getID": getIDV1, + + // Backend functions + "getBackendName": getBackendNameV1, + "getHost": p.getHostV1, + "getProtocol": getFuncApplicationStringValueV1(label.TraefikProtocol, label.DefaultProtocol), + "getWeight": getFuncApplicationIntValueV1(label.TraefikWeight, label.DefaultWeightInt), + "getBackend": getBackendV1, + "getPort": p.getPort, + + // Frontend functions + "getFrontendBackend": getBackendNameV1, + "getFrontEndName": getFrontendNameV1, + "getEntryPoints": getFuncSliceStringValueV1(label.TraefikFrontendEntryPoints), + "getBasicAuth": getFuncSliceStringValueV1(label.TraefikFrontendAuthBasic), + "getPriority": getFuncIntValueV1(label.TraefikFrontendPriority, label.DefaultFrontendPriorityInt), + "getPassHostHeader": getFuncBoolValueV1(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeaderBool), + "getFrontendRule": p.getFrontendRuleV1, + } + + // filter tasks + filteredTasks := fun.Filter(func(task state.Task) bool { + return taskFilterV1(task, p.ExposedByDefault) + }, tasks).([]state.Task) + + // Deprecated + var filteredApps []state.Task + uniqueApps := make(map[string]struct{}) + for _, task := range filteredTasks { + if _, ok := uniqueApps[task.DiscoveryInfo.Name]; !ok { + uniqueApps[task.DiscoveryInfo.Name] = struct{}{} + filteredApps = append(filteredApps, task) + } + } + + appsTasks := make(map[string][]state.Task) + for _, task := range filteredTasks { + if _, ok := appsTasks[task.DiscoveryInfo.Name]; !ok { + appsTasks[task.DiscoveryInfo.Name] = []state.Task{task} + } else { + appsTasks[task.DiscoveryInfo.Name] = append(appsTasks[task.DiscoveryInfo.Name], task) + } + } + + templateObjects := struct { + ApplicationsTasks map[string][]state.Task + Applications []state.Task // Deprecated + Tasks []state.Task // Deprecated + Domain string + }{ + ApplicationsTasks: appsTasks, + Applications: filteredApps, // Deprecated + Tasks: filteredTasks, // Deprecated + Domain: p.Domain, + } + + configuration, err := p.GetConfiguration("templates/mesos-v1.tmpl", mesosFuncMap, templateObjects) + if err != nil { + log.Error(err) + } + return configuration +} + +// Deprecated +func taskFilterV1(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 !isEnabledV1(task, exposedByDefaultFlag) { + log.Debugf("Filtering disabled Mesos task %s", task.DiscoveryInfo.Name) + return false + } + + // filter indeterminable task port + portIndexLabel := getStringValueV1(task, label.TraefikPortIndex, "") + portValueLabel := getStringValueV1(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 +} + +// Deprecated +func getIDV1(task state.Task) string { + return provider.Normalize(task.ID) +} + +// Deprecated +func getBackendV1(task state.Task, apps []state.Task) string { + _, err := getApplicationV1(task, apps) + if err != nil { + log.Error(err) + return "" + } + return getBackendNameV1(task) +} + +// Deprecated +func getBackendNameV1(task state.Task) string { + if value := getStringValueV1(task, label.TraefikBackend, ""); len(value) > 0 { + return value + } + return provider.Normalize(task.DiscoveryInfo.Name) +} + +// Deprecated +func getFrontendNameV1(task state.Task) string { + // TODO task.ID -> task.Name + task.ID + return provider.Normalize(task.ID) +} + +// Deprecated +func (p *Provider) getPort(task state.Task, applications []state.Task) string { + _, err := getApplicationV1(task, applications) + if err != nil { + log.Error(err) + return "" + } + + plv := getIntValueV1(task, label.TraefikPortIndex, math.MinInt32, len(task.DiscoveryInfo.Ports.DiscoveryPorts)-1) + if plv >= 0 { + return strconv.Itoa(task.DiscoveryInfo.Ports.DiscoveryPorts[plv].Number) + } + + if pv := getStringValueV1(task, label.TraefikPort, ""); len(pv) > 0 { + return pv + } + + for _, port := range task.DiscoveryInfo.Ports.DiscoveryPorts { + return strconv.Itoa(port.Number) + } + return "" +} + +// getFrontendRuleV1 returns the frontend rule for the specified application, using +// it's label. It returns a default one (Host) if the label is not present. +// Deprecated +func (p *Provider) getFrontendRuleV1(task state.Task) string { + if v := getStringValueV1(task, label.TraefikFrontendRule, ""); len(v) > 0 { + return v + } + return "Host:" + strings.ToLower(strings.Replace(p.getSubDomain(task.DiscoveryInfo.Name), "_", "-", -1)) + "." + p.Domain +} + +// Deprecated +func (p *Provider) getHostV1(task state.Task) string { + return task.IP(strings.Split(p.IPSources, ",")...) +} + +// Deprecated +func isEnabledV1(task state.Task, exposedByDefault bool) bool { + return getBoolValueV1(task, label.TraefikEnable, exposedByDefault) +} + +// Label functions + +// Deprecated +func getFuncApplicationStringValueV1(labelName string, defaultValue string) func(task state.Task, applications []state.Task) string { + return func(task state.Task, applications []state.Task) string { + _, err := getApplicationV1(task, applications) + if err != nil { + log.Error(err) + return defaultValue + } + + return getStringValueV1(task, labelName, defaultValue) + } +} + +// Deprecated +func getFuncApplicationIntValueV1(labelName string, defaultValue int) func(task state.Task, applications []state.Task) int { + return func(task state.Task, applications []state.Task) int { + _, err := getApplicationV1(task, applications) + if err != nil { + log.Error(err) + return defaultValue + } + + return getIntValueV1(task, labelName, defaultValue, math.MaxInt64) + } +} + +// Deprecated +func getFuncStringValueV1(labelName string, defaultValue string) func(task state.Task) string { + return func(task state.Task) string { + return getStringValueV1(task, labelName, defaultValue) + } +} + +// Deprecated +func getFuncBoolValueV1(labelName string, defaultValue bool) func(task state.Task) bool { + return func(task state.Task) bool { + return getBoolValueV1(task, labelName, defaultValue) + } +} + +// Deprecated +func getFuncIntValueV1(labelName string, defaultValue int) func(task state.Task) int { + return func(task state.Task) int { + return getIntValueV1(task, labelName, defaultValue, math.MaxInt64) + } +} + +// Deprecated +func getFuncSliceStringValueV1(labelName string) func(task state.Task) []string { + return func(task state.Task) []string { + return getSliceStringValueV1(task, labelName) + } +} + +// Deprecated +func getStringValueV1(task state.Task, labelName string, defaultValue string) string { + for _, lbl := range task.Labels { + if lbl.Key == labelName && len(lbl.Value) > 0 { + return lbl.Value + } + } + return defaultValue +} + +// Deprecated +func getBoolValueV1(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 +} + +// Deprecated +func getIntValueV1(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 +} + +// Deprecated +func getSliceStringValueV1(task state.Task, labelName string) []string { + for _, lbl := range task.Labels { + if lbl.Key == labelName { + return label.SplitAndTrimString(lbl.Value, ",") + } + } + return nil +} + +// Deprecated +func getApplicationV1(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) +} diff --git a/provider/mesos/deprecated_config_test.go b/provider/mesos/deprecated_config_test.go new file mode 100644 index 000000000..1366b8587 --- /dev/null +++ b/provider/mesos/deprecated_config_test.go @@ -0,0 +1,432 @@ +package mesos + +import ( + "testing" + + "github.com/containous/traefik/provider/label" + "github.com/containous/traefik/types" + "github.com/mesosphere/mesos-dns/records/state" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBuildConfigurationV1(t *testing.T) { + p := &Provider{ + Domain: "mesos.localhost", + ExposedByDefault: true, + IPSources: "host", + } + + testCases := []struct { + desc string + tasks []state.Task + expectedFrontends map[string]*types.Frontend + expectedBackends map[string]*types.Backend + }{ + { + desc: "when no tasks", + tasks: []state.Task{}, + expectedFrontends: map[string]*types.Frontend{}, + expectedBackends: map[string]*types.Backend{}, + }, + { + desc: "2 applications with 2 tasks", + tasks: []state.Task{ + // App 1 + aTask("ID1", + withIP("10.10.10.10"), + withInfo("name1", + withPorts(withPort("TCP", 80, "WEB"))), + withStatus(withHealthy(true), withState("TASK_RUNNING")), + ), + aTask("ID2", + withIP("10.10.10.11"), + withInfo("name1", + withPorts(withPort("TCP", 81, "WEB"))), + withStatus(withHealthy(true), withState("TASK_RUNNING")), + ), + // App 2 + aTask("ID3", + withIP("20.10.10.10"), + withInfo("name2", + withPorts(withPort("TCP", 80, "WEB"))), + withStatus(withHealthy(true), withState("TASK_RUNNING")), + ), + aTask("ID4", + withIP("20.10.10.11"), + withInfo("name2", + withPorts(withPort("TCP", 81, "WEB"))), + withStatus(withHealthy(true), withState("TASK_RUNNING")), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-ID1": { + Backend: "backend-name1", + EntryPoints: []string{}, + PassHostHeader: true, + Routes: map[string]types.Route{ + "route-host-ID1": { + Rule: "Host:name1.mesos.localhost", + }, + }, + }, + "frontend-ID3": { + Backend: "backend-name2", + EntryPoints: []string{}, + PassHostHeader: true, + Routes: map[string]types.Route{ + "route-host-ID3": { + Rule: "Host:name2.mesos.localhost", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-name1": { + Servers: map[string]types.Server{ + "server-ID1": { + URL: "http://10.10.10.10:80", + Weight: 0, + }, + "server-ID2": { + URL: "http://10.10.10.11:81", + Weight: 0, + }, + }, + }, + "backend-name2": { + Servers: map[string]types.Server{ + "server-ID3": { + URL: "http://20.10.10.10:80", + Weight: 0, + }, + "server-ID4": { + URL: "http://20.10.10.11:81", + Weight: 0, + }, + }, + }, + }, + }, + { + desc: "with all labels", + tasks: []state.Task{ + aTask("ID1", + withLabel(label.TraefikPort, "666"), + withLabel(label.TraefikProtocol, "https"), + withLabel(label.TraefikWeight, "12"), + + withLabel(label.TraefikBackend, "foobar"), + + withLabel(label.TraefikBackendCircuitBreakerExpression, "NetworkErrorRatio() > 0.5"), + withLabel(label.TraefikBackendHealthCheckPath, "/health"), + withLabel(label.TraefikBackendHealthCheckPort, "880"), + withLabel(label.TraefikBackendHealthCheckInterval, "6"), + withLabel(label.TraefikBackendLoadBalancerMethod, "drr"), + withLabel(label.TraefikBackendLoadBalancerStickiness, "true"), + withLabel(label.TraefikBackendLoadBalancerStickinessCookieName, "chocolate"), + withLabel(label.TraefikBackendMaxConnAmount, "666"), + withLabel(label.TraefikBackendMaxConnExtractorFunc, "client.ip"), + withLabel(label.TraefikBackendBufferingMaxResponseBodyBytes, "10485760"), + withLabel(label.TraefikBackendBufferingMemResponseBodyBytes, "2097152"), + withLabel(label.TraefikBackendBufferingMaxRequestBodyBytes, "10485760"), + withLabel(label.TraefikBackendBufferingMemRequestBodyBytes, "2097152"), + withLabel(label.TraefikBackendBufferingRetryExpression, "IsNetworkError() && Attempts() <= 2"), + + withLabel(label.TraefikFrontendAuthBasic, "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/,test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"), + withLabel(label.TraefikFrontendEntryPoints, "http,https"), + withLabel(label.TraefikFrontendPassHostHeader, "true"), + withLabel(label.TraefikFrontendPassTLSCert, "true"), + withLabel(label.TraefikFrontendPriority, "666"), + withLabel(label.TraefikFrontendRedirectEntryPoint, "https"), + withLabel(label.TraefikFrontendRedirectRegex, "nope"), + withLabel(label.TraefikFrontendRedirectReplacement, "nope"), + withLabel(label.TraefikFrontendRedirectPermanent, "true"), + withLabel(label.TraefikFrontendRule, "Host:traefik.io"), + withLabel(label.TraefikFrontendWhiteListSourceRange, "10.10.10.10"), + withLabel(label.TraefikFrontendWhiteListUseXForwardedFor, "true"), + + withLabel(label.TraefikFrontendRequestHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type:application/json; charset=utf-8"), + withLabel(label.TraefikFrontendResponseHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type:application/json; charset=utf-8"), + withLabel(label.TraefikFrontendSSLProxyHeaders, "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type:application/json; charset=utf-8"), + withLabel(label.TraefikFrontendAllowedHosts, "foo,bar,bor"), + withLabel(label.TraefikFrontendHostsProxyHeaders, "foo,bar,bor"), + withLabel(label.TraefikFrontendSSLHost, "foo"), + withLabel(label.TraefikFrontendCustomFrameOptionsValue, "foo"), + withLabel(label.TraefikFrontendContentSecurityPolicy, "foo"), + withLabel(label.TraefikFrontendPublicKey, "foo"), + withLabel(label.TraefikFrontendReferrerPolicy, "foo"), + withLabel(label.TraefikFrontendCustomBrowserXSSValue, "foo"), + withLabel(label.TraefikFrontendSTSSeconds, "666"), + withLabel(label.TraefikFrontendSSLRedirect, "true"), + withLabel(label.TraefikFrontendSSLTemporaryRedirect, "true"), + withLabel(label.TraefikFrontendSTSIncludeSubdomains, "true"), + withLabel(label.TraefikFrontendSTSPreload, "true"), + withLabel(label.TraefikFrontendForceSTSHeader, "true"), + withLabel(label.TraefikFrontendFrameDeny, "true"), + withLabel(label.TraefikFrontendContentTypeNosniff, "true"), + withLabel(label.TraefikFrontendBrowserXSSFilter, "true"), + withLabel(label.TraefikFrontendIsDevelopment, "true"), + + withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageStatus, "404"), + withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageBackend, "foobar"), + withLabel(label.Prefix+label.BaseFrontendErrorPage+"foo."+label.SuffixErrorPageQuery, "foo_query"), + withLabel(label.Prefix+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageStatus, "500,600"), + withLabel(label.Prefix+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageBackend, "foobar"), + withLabel(label.Prefix+label.BaseFrontendErrorPage+"bar."+label.SuffixErrorPageQuery, "bar_query"), + + withLabel(label.TraefikFrontendRateLimitExtractorFunc, "client.ip"), + withLabel(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitPeriod, "6"), + withLabel(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitAverage, "12"), + withLabel(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitBurst, "18"), + withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitPeriod, "3"), + withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitAverage, "6"), + withLabel(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitBurst, "9"), + withIP("10.10.10.10"), + withInfo("name1", withPorts( + withPortTCP(80, "n"), + withPortTCP(666, "n"))), + withStatus(withHealthy(true), withState("TASK_RUNNING")), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-ID1": { + EntryPoints: []string{ + "http", + "https", + }, + Backend: "backend-foobar", + Routes: map[string]types.Route{ + "route-host-ID1": { + Rule: "Host:traefik.io", + }, + }, + PassHostHeader: true, + Priority: 666, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-foobar": { + Servers: map[string]types.Server{ + "server-ID1": { + URL: "https://10.10.10.10:666", + Weight: 12, + }, + }, + }, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actualConfig := p.buildConfigurationV1(test.tasks) + + require.NotNil(t, actualConfig) + assert.Equal(t, test.expectedBackends, actualConfig.Backends) + assert.Equal(t, test.expectedFrontends, actualConfig.Frontends) + }) + } +} + +func TestTaskFilterV1(t *testing.T) { + testCases := []struct { + desc string + mesosTask state.Task + exposedByDefault bool + expected bool + }{ + { + desc: "no task", + mesosTask: state.Task{}, + exposedByDefault: true, + expected: false, + }, + { + desc: "task not healthy", + mesosTask: aTask("test", withStatus(withState("TASK_RUNNING"))), + exposedByDefault: true, + expected: false, + }, + { + desc: "exposedByDefault false and traefik.enable false", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "false"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: false, + expected: false, + }, + { + desc: "traefik.enable = true", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: false, + expected: true, + }, + { + desc: "exposedByDefault true and traefik.enable true", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: true, + }, + { + desc: "exposedByDefault true and traefik.enable false", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "false"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: false, + }, + { + desc: "traefik.portIndex and traefik.port both set", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPortIndex, "1"), + withLabel(label.TraefikEnable, "80"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: false, + }, + { + desc: "valid traefik.portIndex", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPortIndex, "1"), + withInfo("test", withPorts( + withPortTCP(80, "WEB"), + withPortTCP(443, "WEB HTTPS"), + )), + ), + exposedByDefault: true, + expected: true, + }, + { + desc: "default to first port index", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withInfo("test", withPorts( + withPortTCP(80, "WEB"), + withPortTCP(443, "WEB HTTPS"), + )), + ), + exposedByDefault: true, + expected: true, + }, + { + desc: "traefik.portIndex and discoveryPorts don't correspond", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPortIndex, "1"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: false, + }, + { + desc: "traefik.portIndex and discoveryPorts correspond", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPortIndex, "0"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: true, + }, + { + desc: "traefik.port is not an integer", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPort, "TRAEFIK"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: false, + }, + { + desc: "traefik.port is not the same as discovery.port", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPort, "443"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: false, + }, + { + desc: "traefik.port is the same as discovery.port", + mesosTask: aTask("test", + withDefaultStatus(), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPort, "80"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: true, + }, + { + desc: "healthy nil", + mesosTask: aTask("test", + withStatus( + withState("TASK_RUNNING"), + ), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPort, "80"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: true, + }, + { + desc: "healthy false", + mesosTask: aTask("test", + withStatus( + withState("TASK_RUNNING"), + withHealthy(false), + ), + withLabel(label.TraefikEnable, "true"), + withLabel(label.TraefikPort, "80"), + withInfo("test", withPorts(withPortTCP(80, "WEB"))), + ), + exposedByDefault: true, + expected: false, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := taskFilterV1(test.mesosTask, test.exposedByDefault) + ok := assert.Equal(t, test.expected, actual) + if !ok { + t.Logf("Statuses : %v", test.mesosTask.Statuses) + t.Logf("Label : %v", test.mesosTask.Labels) + t.Logf("DiscoveryInfo : %v", test.mesosTask.DiscoveryInfo) + t.Fatalf("Expected %v, got %v", test.expected, actual) + } + }) + } +} diff --git a/provider/mesos/mesos_helper_test.go b/provider/mesos/mesos_helper_test.go index f7ac6353a..0f30ab073 100644 --- a/provider/mesos/mesos_helper_test.go +++ b/provider/mesos/mesos_helper_test.go @@ -48,6 +48,14 @@ func TestBuilder(t *testing.T) { assert.Equal(t, expected, result) } +func aTaskData(id string, ops ...func(*state.Task)) taskData { + ts := &state.Task{ID: id} + for _, op := range ops { + op(ts) + } + return taskData{Task: *ts, TraefikLabels: extractLabels(*ts)} +} + func aTask(id string, ops ...func(*state.Task)) state.Task { ts := &state.Task{ID: id} for _, op := range ops { diff --git a/provider/mesos/mesos_test.go b/provider/mesos/mesos_test.go new file mode 100644 index 000000000..1459c27e3 --- /dev/null +++ b/provider/mesos/mesos_test.go @@ -0,0 +1,37 @@ +package mesos + +import ( + "testing" + + "github.com/mesos/mesos-go/upid" + "github.com/mesosphere/mesos-dns/records/state" +) + +func TestTaskRecords(t *testing.T) { + var task = state.Task{ + SlaveID: "s_id", + State: "TASK_RUNNING", + } + var framework = state.Framework{ + Tasks: []state.Task{task}, + } + var slave = state.Slave{ + ID: "s_id", + Hostname: "127.0.0.1", + } + slave.PID.UPID = &upid.UPID{} + slave.PID.Host = slave.Hostname + + var taskState = state.State{ + Slaves: []state.Slave{slave}, + Frameworks: []state.Framework{framework}, + } + + var p = taskRecords(taskState) + if len(p) == 0 { + t.Fatal("No task") + } + if p[0].SlaveIP != slave.Hostname { + t.Fatalf("The SlaveIP (%s) should be set with the slave hostname (%s)", p[0].SlaveID, slave.Hostname) + } +} diff --git a/templates/mesos-v1.tmpl b/templates/mesos-v1.tmpl new file mode 100644 index 000000000..3ec2990d9 --- /dev/null +++ b/templates/mesos-v1.tmpl @@ -0,0 +1,27 @@ +{{$apps := .Applications}} + +[backends] +{{range .Tasks}} + + [backends."backend-{{ getBackend . $apps }}".servers."server-{{ getID . }}"] + url = "{{ getProtocol . $apps }}://{{ getHost . }}:{{ getPort . $apps }}" + weight = {{ getWeight . $apps }} + +{{end}} + +[frontends] +{{range .Applications}} + + [frontends."frontend-{{getFrontEndName . }}"] + backend = "backend-{{ getFrontendBackend . }}" + passHostHeader = {{ getPassHostHeader . }} + priority = {{ getPriority . }} + + entryPoints = [{{range getEntryPoints . }} + "{{.}}", + {{end}}] + + [frontends."frontend-{{ getFrontEndName . }}".routes."route-host-{{ getFrontEndName . }}"] + rule = "{{ getFrontendRule . }}" + +{{end}} diff --git a/templates/mesos.tmpl b/templates/mesos.tmpl index e71cc238e..9ea840d7c 100644 --- a/templates/mesos.tmpl +++ b/templates/mesos.tmpl @@ -5,13 +5,13 @@ [backends."backend-{{ $backendName }}"] - {{ $circuitBreaker := getCircuitBreaker $app }} + {{ $circuitBreaker := getCircuitBreaker $app.TraefikLabels }} {{if $circuitBreaker }} [backends."backend-{{ $backendName }}".circuitBreaker] expression = "{{ $circuitBreaker.Expression }}" {{end}} - {{ $loadBalancer := getLoadBalancer $app }} + {{ $loadBalancer := getLoadBalancer $app.TraefikLabels }} {{if $loadBalancer }} [backends."backend-{{ $backendName }}".loadBalancer] method = "{{ $loadBalancer.Method }}" @@ -22,14 +22,14 @@ {{end}} {{end}} - {{ $maxConn := getMaxConn $app }} + {{ $maxConn := getMaxConn $app.TraefikLabels }} {{if $maxConn }} [backends."backend-{{ $backendName }}".maxConn] extractorFunc = "{{ $maxConn.ExtractorFunc }}" amount = {{ $maxConn.Amount }} {{end}} - {{ $healthCheck := getHealthCheck $app }} + {{ $healthCheck := getHealthCheck $app.TraefikLabels }} {{if $healthCheck }} [backends."backend-{{ $backendName }}".healthCheck] path = "{{ $healthCheck.Path }}" @@ -37,7 +37,7 @@ interval = "{{ $healthCheck.Interval }}" {{end}} - {{ $buffering := getBuffering $app }} + {{ $buffering := getBuffering $app.TraefikLabels }} {{if $buffering }} [backends."backend-{{ $backendName }}".buffering] maxRequestBodyBytes = {{ $buffering.MaxRequestBodyBytes }} @@ -61,19 +61,19 @@ [frontends."frontend-{{ $frontendName }}"] backend = "backend-{{ getBackendName $app }}" - priority = {{ getPriority $app }} - passHostHeader = {{ getPassHostHeader $app }} - passTLSCert = {{ getPassTLSCert $app }} + priority = {{ getPriority $app.TraefikLabels }} + passHostHeader = {{ getPassHostHeader $app.TraefikLabels }} + passTLSCert = {{ getPassTLSCert $app.TraefikLabels }} - entryPoints = [{{range getEntryPoints $app }} + entryPoints = [{{range getEntryPoints $app.TraefikLabels }} "{{.}}", {{end}}] - basicAuth = [{{range getBasicAuth $app }} + basicAuth = [{{range getBasicAuth $app.TraefikLabels }} "{{.}}", {{end}}] - {{ $whitelist := getWhiteList $app }} + {{ $whitelist := getWhiteList $app.TraefikLabels }} {{if $whitelist }} [frontends."frontend-{{ $frontendName }}".whiteList] sourceRange = [{{range $whitelist.SourceRange }} @@ -82,7 +82,7 @@ useXForwardedFor = {{ $whitelist.UseXForwardedFor }} {{end}} - {{ $redirect := getRedirect $app }} + {{ $redirect := getRedirect $app.TraefikLabels }} {{if $redirect }} [frontends."frontend-{{ $frontendName }}".redirect] entryPoint = "{{ $redirect.EntryPoint }}" @@ -91,7 +91,7 @@ permanent = {{ $redirect.Permanent }} {{end}} - {{ $errorPages := getErrorPages $app }} + {{ $errorPages := getErrorPages $app.TraefikLabels }} {{if $errorPages }} [frontends."frontend-{{ $frontendName }}".errors] {{range $pageName, $page := $errorPages }} @@ -104,7 +104,7 @@ {{end}} {{end}} - {{ $rateLimit := getRateLimit $app }} + {{ $rateLimit := getRateLimit $app.TraefikLabels }} {{if $rateLimit }} [frontends."frontend-{{ $frontendName }}".rateLimit] extractorFunc = "{{ $rateLimit.ExtractorFunc }}" @@ -117,7 +117,7 @@ {{end}} {{end}} - {{ $headers := getHeaders $app }} + {{ $headers := getHeaders $app.TraefikLabels }} {{if $headers }} [frontends."frontend-{{ $frontendName }}".headers] SSLRedirect = {{ $headers.SSLRedirect }}