1
0
Fork 0

feat(docker): add rate limit labels.

This commit is contained in:
Fernandez Ludovic 2017-12-18 18:06:12 +01:00 committed by Traefiker
parent c30ebe5f90
commit 942614dd23
8 changed files with 226 additions and 16 deletions

View file

@ -37,20 +37,23 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
"getStickinessCookieName": getFuncStringLabel(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName), "getStickinessCookieName": getFuncStringLabel(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
// Frontend functions // Frontend functions
"getBackend": getBackend, "getBackend": getBackend,
"getPriority": getFuncStringLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriority), "getPriority": getFuncStringLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
"getPassHostHeader": getFuncStringLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader), "getPassHostHeader": getFuncStringLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
"getPassTLSCert": getFuncBoolLabel(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert), "getPassTLSCert": getFuncBoolLabel(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
"getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints), "getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints),
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic), "getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
"getWhitelistSourceRange": getFuncSliceStringLabel(label.TraefikFrontendWhitelistSourceRange), "getWhitelistSourceRange": getFuncSliceStringLabel(label.TraefikFrontendWhitelistSourceRange),
"getFrontendRule": p.getFrontendRule, "getFrontendRule": p.getFrontendRule,
"hasRedirect": hasRedirect, "hasRedirect": hasRedirect,
"getRedirectEntryPoint": getFuncStringLabel(label.TraefikFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint), "getRedirectEntryPoint": getFuncStringLabel(label.TraefikFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint),
"getRedirectRegex": getFuncStringLabel(label.TraefikFrontendRedirectRegex, ""), "getRedirectRegex": getFuncStringLabel(label.TraefikFrontendRedirectRegex, ""),
"getRedirectReplacement": getFuncStringLabel(label.TraefikFrontendRedirectReplacement, ""), "getRedirectReplacement": getFuncStringLabel(label.TraefikFrontendRedirectReplacement, ""),
"hasErrorPages": hasErrorPages, "hasErrorPages": hasErrorPages,
"getErrorPages": getErrorPages, "getErrorPages": getErrorPages,
"hasRateLimits": hasFunc(label.TraefikFrontendRateLimitExtractorFunc),
"getRateLimitsExtractorFunc": getFuncStringLabel(label.TraefikFrontendRateLimitExtractorFunc, ""),
"getRateLimits": getRateLimits,
// Headers // Headers
"hasRequestHeaders": hasFunc(label.TraefikFrontendRequestHeaders), "hasRequestHeaders": hasFunc(label.TraefikFrontendRequestHeaders),
"getRequestHeaders": getFuncMapLabel(label.TraefikFrontendRequestHeaders), "getRequestHeaders": getFuncMapLabel(label.TraefikFrontendRequestHeaders),
@ -118,6 +121,8 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
"getServiceResponseHeaders": getFuncServiceMapLabel(label.SuffixFrontendResponseHeaders), "getServiceResponseHeaders": getFuncServiceMapLabel(label.SuffixFrontendResponseHeaders),
"hasServiceErrorPages": hasServiceErrorPages, "hasServiceErrorPages": hasServiceErrorPages,
"getServiceErrorPages": getServiceErrorPages, "getServiceErrorPages": getServiceErrorPages,
"hasServiceRateLimits": hasFuncServiceLabel(label.SuffixFrontendRateLimitExtractorFunc),
"getServiceRateLimits": getServiceRateLimits,
} }
// filter containers // filter containers
filteredContainers := fun.Filter(func(container dockerData) bool { filteredContainers := fun.Filter(func(container dockerData) bool {

View file

@ -182,6 +182,11 @@ func getErrorPages(container dockerData) map[string]*types.ErrorPage {
return label.ParseErrorPages(container.Labels, prefix, label.RegexpFrontendErrorPage) return label.ParseErrorPages(container.Labels, prefix, label.RegexpFrontendErrorPage)
} }
func getRateLimits(container dockerData) map[string]*types.Rate {
prefix := label.Prefix + label.BaseFrontendRateLimit
return label.ParseRateSets(container.Labels, prefix, label.RegexpFrontendRateLimit)
}
// Label functions // Label functions
func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 { func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 {

View file

@ -4,7 +4,9 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"testing" "testing"
"time"
"github.com/containous/flaeg"
"github.com/containous/traefik/provider/label" "github.com/containous/traefik/provider/label"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
docker "github.com/docker/docker/api/types" docker "github.com/docker/docker/api/types"
@ -236,6 +238,65 @@ func TestDockerBuildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
containers: []docker.ContainerJSON{
containerJSON(
name("test1"),
labels(map[string]string{
label.TraefikBackend: "foobar",
label.TraefikFrontendRateLimitExtractorFunc: "client.ip",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12",
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6",
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9",
}),
ports(nat.PortMap{
"80/tcp": {},
}),
withNetwork("bridge", ipv4("127.0.0.1")),
),
},
expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test1-docker-localhost-0": {
EntryPoints: []string{},
BasicAuth: []string{},
PassHostHeader: true,
Backend: "backend-foobar",
Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost",
},
},
RateLimit: &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,
},
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-foobar": {
Servers: map[string]types.Server{
"server-test1": {
URL: "http://127.0.0.1:80",
Weight: 0,
},
},
},
},
},
} }
for caseID, test := range testCases { for caseID, test := range testCases {

View file

@ -108,6 +108,11 @@ func getServiceErrorPages(container dockerData, serviceName string) map[string]*
return label.ParseErrorPages(serviceLabels, label.BaseFrontendErrorPage, label.RegexpBaseFrontendErrorPage) return label.ParseErrorPages(serviceLabels, label.BaseFrontendErrorPage, label.RegexpBaseFrontendErrorPage)
} }
func getServiceRateLimits(container dockerData, serviceName string) map[string]*types.Rate {
serviceLabels := getServiceLabels(container, serviceName)
return label.ParseRateSets(serviceLabels, label.BaseFrontendRateLimit, label.RegexpBaseFrontendRateLimit)
}
// Service label functions // Service label functions
func getFuncServiceMapLabel(labelSuffix string) func(container dockerData, serviceName string) map[string]string { func getFuncServiceMapLabel(labelSuffix string) func(container dockerData, serviceName string) map[string]string {

View file

@ -7,6 +7,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/containous/flaeg"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
) )
@ -44,6 +45,12 @@ var (
// RegexpFrontendErrorPage used to extract error pages from label // RegexpFrontendErrorPage used to extract error pages from label
RegexpFrontendErrorPage = regexp.MustCompile(`^traefik\.frontend\.errors\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`) RegexpFrontendErrorPage = regexp.MustCompile(`^traefik\.frontend\.errors\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`)
// RegexpBaseFrontendRateLimit used to extract rate limits from service's label
RegexpBaseFrontendRateLimit = regexp.MustCompile(`^frontend\.rateLimit\.rateSet\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`)
// RegexpFrontendRateLimit used to extract rate limits from label
RegexpFrontendRateLimit = regexp.MustCompile(`^traefik\.frontend\.rateLimit\.rateSet\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`)
) )
// ServicePropertyValues is a map of services properties // ServicePropertyValues is a map of services properties
@ -295,6 +302,58 @@ func ParseErrorPages(labels map[string]string, labelPrefix string, labelRegex *r
return errorPages return errorPages
} }
// ParseRateSets parse rate limits to create Rate struct
func ParseRateSets(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.Rate {
rateSets := make(map[string]*types.Rate)
for lblName, rawValue := range labels {
if strings.HasPrefix(lblName, labelPrefix) && len(rawValue) > 0 {
submatch := labelRegex.FindStringSubmatch(lblName)
if len(submatch) != 3 {
log.Errorf("Invalid rate limit label: %s, sub-match: %v", lblName, submatch)
continue
}
limitName := submatch[1]
ep, ok := rateSets[limitName]
if !ok {
ep = &types.Rate{}
rateSets[limitName] = ep
}
switch submatch[2] {
case "period":
var d flaeg.Duration
err := d.Set(rawValue)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Period = d
case "average":
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Average = value
case "burst":
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
continue
}
ep.Burst = value
default:
log.Errorf("Invalid rate limit label: %s", lblName)
continue
}
}
}
return rateSets
}
// IsEnabled Check if a container is enabled in Træfik // IsEnabled Check if a container is enabled in Træfik
func IsEnabled(labels map[string]string, exposedByDefault bool) bool { func IsEnabled(labels map[string]string, exposedByDefault bool) bool {
return GetBoolValue(labels, TraefikEnable, exposedByDefault) return GetBoolValue(labels, TraefikEnable, exposedByDefault)

View file

@ -2,7 +2,9 @@ package label
import ( import (
"testing" "testing"
"time"
"github.com/containous/flaeg"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -1081,3 +1083,46 @@ func TestParseErrorPages(t *testing.T) {
}) })
} }
} }
func TestParseRateSets(t *testing.T) {
testCases := []struct {
desc string
labels map[string]string
expected map[string]*types.Rate
}{
{
desc: "2 rate limits",
labels: map[string]string{
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod: "6",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst: "18",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod: "3",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst: "9",
},
expected: 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,
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rateSets := ParseRateSets(test.labels, Prefix+BaseFrontendRateLimit, RegexpFrontendRateLimit)
assert.EqualValues(t, test.expected, rateSets)
})
}
}

View file

@ -50,6 +50,7 @@ const (
SuffixFrontendPassHostHeader = "frontend.passHostHeader" SuffixFrontendPassHostHeader = "frontend.passHostHeader"
SuffixFrontendPassTLSCert = "frontend.passTLSCert" SuffixFrontendPassTLSCert = "frontend.passTLSCert"
SuffixFrontendPriority = "frontend.priority" SuffixFrontendPriority = "frontend.priority"
SuffixFrontendRateLimitExtractorFunc = "frontend.rateLimit.extractorFunc"
SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint" SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint"
SuffixFrontendRedirectRegex = "frontend.redirect.regex" SuffixFrontendRedirectRegex = "frontend.redirect.regex"
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement" SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
@ -83,11 +84,12 @@ const (
TraefikFrontendPassHostHeader = Prefix + SuffixFrontendPassHostHeader TraefikFrontendPassHostHeader = Prefix + SuffixFrontendPassHostHeader
TraefikFrontendPassTLSCert = Prefix + SuffixFrontendPassTLSCert TraefikFrontendPassTLSCert = Prefix + SuffixFrontendPassTLSCert
TraefikFrontendPriority = Prefix + SuffixFrontendPriority TraefikFrontendPriority = Prefix + SuffixFrontendPriority
TraefikFrontendRule = Prefix + SuffixFrontendRule TraefikFrontendRateLimitExtractorFunc = Prefix + SuffixFrontendRateLimitExtractorFunc
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType
TraefikFrontendRedirectEntryPoint = Prefix + SuffixFrontendRedirectEntryPoint TraefikFrontendRedirectEntryPoint = Prefix + SuffixFrontendRedirectEntryPoint
TraefikFrontendRedirectRegex = Prefix + SuffixFrontendRedirectRegex TraefikFrontendRedirectRegex = Prefix + SuffixFrontendRedirectRegex
TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement
TraefikFrontendRule = Prefix + SuffixFrontendRule
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType
TraefikFrontendValue = Prefix + SuffixFrontendValue TraefikFrontendValue = Prefix + SuffixFrontendValue
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders
@ -114,4 +116,8 @@ const (
SuffixErrorPageBackend = "backend" SuffixErrorPageBackend = "backend"
SuffixErrorPageQuery = "query" SuffixErrorPageQuery = "query"
SuffixErrorPageStatus = "status" SuffixErrorPageStatus = "status"
BaseFrontendRateLimit = "frontend.rateLimit.rateSet."
SuffixRateLimitPeriod = "period"
SuffixRateLimitAverage = "average"
SuffixRateLimitBurst = "burst"
) )

View file

@ -95,6 +95,18 @@
{{end}} {{end}}
{{end}} {{end}}
{{ if hasServiceRateLimits $container $serviceName }}
[frontends."frontend-{{getServiceBackend $container $serviceName}}".rateLimit]
extractorFunc = "{{ getRateLimitsExtractorFunc $container $serviceName }}"
[frontends."frontend-{{getServiceBackend $container $serviceName}}".rateLimit.rateSet]
{{ range $limitName, $rateLimit := getServiceRateLimits $container $serviceName }}
[frontends."frontend-{{getServiceBackend $container $serviceName}}".rateLimit.rateSet.{{ $limitName }}]
period = "{{ $rateLimit.Period }}"
average = {{ $rateLimit.Average }}
burst = {{ $rateLimit.Burst }}
{{end}}
{{end}}
[frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"] [frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"]
rule = "{{getServiceFrontendRule $container $serviceName}}" rule = "{{getServiceFrontendRule $container $serviceName}}"
@ -155,6 +167,18 @@
{{end}} {{end}}
{{end}} {{end}}
{{ if hasRateLimits $container }}
[frontends."frontend-{{$frontend}}".rateLimit]
extractorFunc = "{{ getRateLimitsExtractorFunc $container }}"
[frontends."frontend-{{$frontend}}".rateLimit.rateSet]
{{ range $limitName, $rateLimit := getRateLimits $container }}
[frontends."frontend-{{$frontend}}".rateLimit.rateSet.{{ $limitName }}]
period = "{{ $rateLimit.Period }}"
average = {{ $rateLimit.Average }}
burst = {{ $rateLimit.Burst }}
{{end}}
{{end}}
[frontends."frontend-{{$frontend}}".headers] [frontends."frontend-{{$frontend}}".headers]
{{if hasSSLRedirectHeaders $container}} {{if hasSSLRedirectHeaders $container}}
SSLRedirect = {{getSSLRedirectHeaders $container}} SSLRedirect = {{getSSLRedirectHeaders $container}}