Custom resource definition
Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
This commit is contained in:
parent
cfaf47c8a2
commit
4c060a78cc
1348 changed files with 92364 additions and 55766 deletions
|
@ -1,122 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/containous/traefik/old/provider/label"
|
||||
)
|
||||
|
||||
const (
|
||||
annotationKubernetesIngressClass = "kubernetes.io/ingress.class"
|
||||
annotationKubernetesAuthRealm = "ingress.kubernetes.io/auth-realm"
|
||||
annotationKubernetesAuthType = "ingress.kubernetes.io/auth-type"
|
||||
annotationKubernetesAuthSecret = "ingress.kubernetes.io/auth-secret"
|
||||
annotationKubernetesAuthHeaderField = "ingress.kubernetes.io/auth-header-field"
|
||||
annotationKubernetesAuthForwardResponseHeaders = "ingress.kubernetes.io/auth-response-headers"
|
||||
annotationKubernetesAuthRemoveHeader = "ingress.kubernetes.io/auth-remove-header"
|
||||
annotationKubernetesAuthForwardURL = "ingress.kubernetes.io/auth-url"
|
||||
annotationKubernetesAuthForwardTrustHeaders = "ingress.kubernetes.io/auth-trust-headers"
|
||||
annotationKubernetesAuthForwardTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
|
||||
annotationKubernetesAuthForwardTLSInsecure = "ingress.kubernetes.io/auth-tls-insecure"
|
||||
annotationKubernetesRewriteTarget = "ingress.kubernetes.io/rewrite-target"
|
||||
annotationKubernetesWhiteListSourceRange = "ingress.kubernetes.io/whitelist-source-range"
|
||||
annotationKubernetesWhiteListIPStrategy = "ingress.kubernetes.io/whitelist-ipstrategy"
|
||||
annotationKubernetesWhiteListIPStrategyDepth = "ingress.kubernetes.io/whitelist-ipstrategy-depth"
|
||||
annotationKubernetesWhiteListIPStrategyExcludedIPs = "ingress.kubernetes.io/whitelist-ipstrategy-excluded-ips"
|
||||
annotationKubernetesPreserveHost = "ingress.kubernetes.io/preserve-host"
|
||||
annotationKubernetesPassTLSCert = "ingress.kubernetes.io/pass-tls-cert" // Deprecated
|
||||
annotationKubernetesPassTLSClientCert = "ingress.kubernetes.io/pass-client-tls-cert"
|
||||
annotationKubernetesFrontendEntryPoints = "ingress.kubernetes.io/frontend-entry-points"
|
||||
annotationKubernetesPriority = "ingress.kubernetes.io/priority"
|
||||
annotationKubernetesCircuitBreakerExpression = "ingress.kubernetes.io/circuit-breaker-expression"
|
||||
annotationKubernetesLoadBalancerMethod = "ingress.kubernetes.io/load-balancer-method"
|
||||
annotationKubernetesAffinity = "ingress.kubernetes.io/affinity"
|
||||
annotationKubernetesSessionCookieName = "ingress.kubernetes.io/session-cookie-name"
|
||||
annotationKubernetesRuleType = "ingress.kubernetes.io/rule-type"
|
||||
annotationKubernetesRedirectEntryPoint = "ingress.kubernetes.io/redirect-entry-point"
|
||||
annotationKubernetesRedirectPermanent = "ingress.kubernetes.io/redirect-permanent"
|
||||
annotationKubernetesRedirectRegex = "ingress.kubernetes.io/redirect-regex"
|
||||
annotationKubernetesRedirectReplacement = "ingress.kubernetes.io/redirect-replacement"
|
||||
annotationKubernetesMaxConnAmount = "ingress.kubernetes.io/max-conn-amount"
|
||||
annotationKubernetesMaxConnExtractorFunc = "ingress.kubernetes.io/max-conn-extractor-func"
|
||||
annotationKubernetesRateLimit = "ingress.kubernetes.io/rate-limit"
|
||||
annotationKubernetesErrorPages = "ingress.kubernetes.io/error-pages"
|
||||
annotationKubernetesBuffering = "ingress.kubernetes.io/buffering"
|
||||
annotationKubernetesResponseForwardingFlushInterval = "ingress.kubernetes.io/responseforwarding-flushinterval"
|
||||
annotationKubernetesAppRoot = "ingress.kubernetes.io/app-root"
|
||||
annotationKubernetesServiceWeights = "ingress.kubernetes.io/service-weights"
|
||||
annotationKubernetesRequestModifier = "ingress.kubernetes.io/request-modifier"
|
||||
|
||||
annotationKubernetesSSLForceHost = "ingress.kubernetes.io/ssl-force-host"
|
||||
annotationKubernetesSSLRedirect = "ingress.kubernetes.io/ssl-redirect"
|
||||
annotationKubernetesHSTSMaxAge = "ingress.kubernetes.io/hsts-max-age"
|
||||
annotationKubernetesHSTSIncludeSubdomains = "ingress.kubernetes.io/hsts-include-subdomains"
|
||||
annotationKubernetesCustomRequestHeaders = "ingress.kubernetes.io/custom-request-headers"
|
||||
annotationKubernetesCustomResponseHeaders = "ingress.kubernetes.io/custom-response-headers"
|
||||
annotationKubernetesAllowedHosts = "ingress.kubernetes.io/allowed-hosts"
|
||||
annotationKubernetesProxyHeaders = "ingress.kubernetes.io/proxy-headers"
|
||||
annotationKubernetesSSLTemporaryRedirect = "ingress.kubernetes.io/ssl-temporary-redirect"
|
||||
annotationKubernetesSSLHost = "ingress.kubernetes.io/ssl-host"
|
||||
annotationKubernetesSSLProxyHeaders = "ingress.kubernetes.io/ssl-proxy-headers"
|
||||
annotationKubernetesHSTSPreload = "ingress.kubernetes.io/hsts-preload"
|
||||
annotationKubernetesForceHSTSHeader = "ingress.kubernetes.io/force-hsts"
|
||||
annotationKubernetesFrameDeny = "ingress.kubernetes.io/frame-deny"
|
||||
annotationKubernetesCustomFrameOptionsValue = "ingress.kubernetes.io/custom-frame-options-value"
|
||||
annotationKubernetesContentTypeNosniff = "ingress.kubernetes.io/content-type-nosniff"
|
||||
annotationKubernetesBrowserXSSFilter = "ingress.kubernetes.io/browser-xss-filter"
|
||||
annotationKubernetesCustomBrowserXSSValue = "ingress.kubernetes.io/custom-browser-xss-value"
|
||||
annotationKubernetesContentSecurityPolicy = "ingress.kubernetes.io/content-security-policy"
|
||||
annotationKubernetesPublicKey = "ingress.kubernetes.io/public-key"
|
||||
annotationKubernetesReferrerPolicy = "ingress.kubernetes.io/referrer-policy"
|
||||
annotationKubernetesIsDevelopment = "ingress.kubernetes.io/is-development"
|
||||
annotationKubernetesProtocol = "ingress.kubernetes.io/protocol"
|
||||
)
|
||||
|
||||
func getAnnotationName(annotations map[string]string, name string) string {
|
||||
if _, ok := annotations[name]; ok {
|
||||
return name
|
||||
}
|
||||
|
||||
if _, ok := annotations[label.Prefix+name]; ok {
|
||||
return label.Prefix + name
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func getStringValue(annotations map[string]string, annotation string, defaultValue string) string {
|
||||
annotationName := getAnnotationName(annotations, annotation)
|
||||
return label.GetStringValue(annotations, annotationName, defaultValue)
|
||||
}
|
||||
|
||||
func getStringSafeValue(annotations map[string]string, annotation string, defaultValue string) (string, error) {
|
||||
annotationName := getAnnotationName(annotations, annotation)
|
||||
value := label.GetStringValue(annotations, annotationName, defaultValue)
|
||||
_, err := strconv.Unquote(`"` + value + `"`)
|
||||
return value, err
|
||||
}
|
||||
|
||||
func getBoolValue(annotations map[string]string, annotation string, defaultValue bool) bool {
|
||||
annotationName := getAnnotationName(annotations, annotation)
|
||||
return label.GetBoolValue(annotations, annotationName, defaultValue)
|
||||
}
|
||||
|
||||
func getIntValue(annotations map[string]string, annotation string, defaultValue int) int {
|
||||
annotationName := getAnnotationName(annotations, annotation)
|
||||
return label.GetIntValue(annotations, annotationName, defaultValue)
|
||||
}
|
||||
|
||||
func getInt64Value(annotations map[string]string, annotation string, defaultValue int64) int64 {
|
||||
annotationName := getAnnotationName(annotations, annotation)
|
||||
return label.GetInt64Value(annotations, annotationName, defaultValue)
|
||||
}
|
||||
|
||||
func getSliceStringValue(annotations map[string]string, annotation string) []string {
|
||||
annotationName := getAnnotationName(annotations, annotation)
|
||||
return label.GetSliceStringValue(annotations, annotationName)
|
||||
}
|
||||
|
||||
func getMapValue(annotations map[string]string, annotation string) map[string]string {
|
||||
annotationName := getAnnotationName(annotations, annotation)
|
||||
return label.GetMapValue(annotations, annotationName)
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/old/provider/label"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetAnnotationName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
annotations map[string]string
|
||||
name string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "with standard annotation",
|
||||
name: annotationKubernetesPreserveHost,
|
||||
annotations: map[string]string{
|
||||
annotationKubernetesPreserveHost: "true",
|
||||
},
|
||||
expected: annotationKubernetesPreserveHost,
|
||||
},
|
||||
{
|
||||
desc: "with prefixed annotation",
|
||||
name: annotationKubernetesPreserveHost,
|
||||
annotations: map[string]string{
|
||||
label.Prefix + annotationKubernetesPreserveHost: "true",
|
||||
},
|
||||
expected: label.Prefix + annotationKubernetesPreserveHost,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := getAnnotationName(test.annotations, test.name)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,647 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg/parse"
|
||||
"github.com/containous/traefik/old/provider/label"
|
||||
"github.com/containous/traefik/old/types"
|
||||
"github.com/containous/traefik/tls"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func buildConfiguration(opts ...func(*types.Configuration)) *types.Configuration {
|
||||
conf := &types.Configuration{}
|
||||
for _, opt := range opts {
|
||||
opt(conf)
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
// Backend
|
||||
|
||||
func backends(opts ...func(*types.Backend) string) func(*types.Configuration) {
|
||||
return func(c *types.Configuration) {
|
||||
c.Backends = make(map[string]*types.Backend)
|
||||
for _, opt := range opts {
|
||||
b := &types.Backend{}
|
||||
name := opt(b)
|
||||
c.Backends[name] = b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func backend(name string, opts ...func(*types.Backend)) func(*types.Backend) string {
|
||||
return func(b *types.Backend) string {
|
||||
for _, opt := range opts {
|
||||
opt(b)
|
||||
}
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
func servers(opts ...func(*types.Server) string) func(*types.Backend) {
|
||||
return func(b *types.Backend) {
|
||||
b.Servers = make(map[string]types.Server)
|
||||
for _, opt := range opts {
|
||||
s := &types.Server{}
|
||||
name := opt(s)
|
||||
b.Servers[name] = *s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func server(url string, opts ...func(*types.Server)) func(*types.Server) string {
|
||||
return func(s *types.Server) string {
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
s.URL = url
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
func weight(value int) func(*types.Server) {
|
||||
return func(s *types.Server) {
|
||||
s.Weight = value
|
||||
}
|
||||
}
|
||||
|
||||
func lbMethod(method string) func(*types.Backend) {
|
||||
return func(b *types.Backend) {
|
||||
if b.LoadBalancer == nil {
|
||||
b.LoadBalancer = &types.LoadBalancer{}
|
||||
}
|
||||
b.LoadBalancer.Method = method
|
||||
}
|
||||
}
|
||||
|
||||
func lbStickiness() func(*types.Backend) {
|
||||
return func(b *types.Backend) {
|
||||
if b.LoadBalancer == nil {
|
||||
b.LoadBalancer = &types.LoadBalancer{}
|
||||
}
|
||||
b.LoadBalancer.Stickiness = &types.Stickiness{}
|
||||
}
|
||||
}
|
||||
|
||||
func circuitBreaker(exp string) func(*types.Backend) {
|
||||
return func(b *types.Backend) {
|
||||
b.CircuitBreaker = &types.CircuitBreaker{}
|
||||
b.CircuitBreaker.Expression = exp
|
||||
}
|
||||
}
|
||||
|
||||
func responseForwarding(interval string) func(*types.Backend) {
|
||||
return func(b *types.Backend) {
|
||||
b.ResponseForwarding = &types.ResponseForwarding{}
|
||||
b.ResponseForwarding.FlushInterval = interval
|
||||
}
|
||||
}
|
||||
|
||||
func buffering(opts ...func(*types.Buffering)) func(*types.Backend) {
|
||||
return func(b *types.Backend) {
|
||||
if b.Buffering == nil {
|
||||
b.Buffering = &types.Buffering{}
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(b.Buffering)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func maxRequestBodyBytes(value int64) func(*types.Buffering) {
|
||||
return func(b *types.Buffering) {
|
||||
b.MaxRequestBodyBytes = value
|
||||
}
|
||||
}
|
||||
|
||||
func memRequestBodyBytes(value int64) func(*types.Buffering) {
|
||||
return func(b *types.Buffering) {
|
||||
b.MemRequestBodyBytes = value
|
||||
}
|
||||
}
|
||||
|
||||
func maxResponseBodyBytes(value int64) func(*types.Buffering) {
|
||||
return func(b *types.Buffering) {
|
||||
b.MaxResponseBodyBytes = value
|
||||
}
|
||||
}
|
||||
|
||||
func memResponseBodyBytes(value int64) func(*types.Buffering) {
|
||||
return func(b *types.Buffering) {
|
||||
b.MemResponseBodyBytes = value
|
||||
}
|
||||
}
|
||||
|
||||
func retrying(exp string) func(*types.Buffering) {
|
||||
return func(b *types.Buffering) {
|
||||
b.RetryExpression = exp
|
||||
}
|
||||
}
|
||||
|
||||
func maxConnExtractorFunc(exp string) func(*types.Backend) {
|
||||
return func(b *types.Backend) {
|
||||
if b.MaxConn == nil {
|
||||
b.MaxConn = &types.MaxConn{}
|
||||
}
|
||||
b.MaxConn.ExtractorFunc = exp
|
||||
}
|
||||
}
|
||||
|
||||
func maxConnAmount(value int64) func(*types.Backend) {
|
||||
return func(b *types.Backend) {
|
||||
if b.MaxConn == nil {
|
||||
b.MaxConn = &types.MaxConn{}
|
||||
}
|
||||
b.MaxConn.Amount = value
|
||||
}
|
||||
}
|
||||
|
||||
// Frontend
|
||||
|
||||
func buildFrontends(opts ...func(*types.Frontend) string) map[string]*types.Frontend {
|
||||
fronts := make(map[string]*types.Frontend)
|
||||
for _, opt := range opts {
|
||||
f := &types.Frontend{}
|
||||
name := opt(f)
|
||||
fronts[name] = f
|
||||
}
|
||||
return fronts
|
||||
}
|
||||
|
||||
func frontends(opts ...func(*types.Frontend) string) func(*types.Configuration) {
|
||||
return func(c *types.Configuration) {
|
||||
c.Frontends = make(map[string]*types.Frontend)
|
||||
for _, opt := range opts {
|
||||
f := &types.Frontend{}
|
||||
name := opt(f)
|
||||
c.Frontends[name] = f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func frontend(backend string, opts ...func(*types.Frontend)) func(*types.Frontend) string {
|
||||
return func(f *types.Frontend) string {
|
||||
for _, opt := range opts {
|
||||
opt(f)
|
||||
}
|
||||
// related the function frontendName
|
||||
name := f.Backend
|
||||
f.Backend = backend
|
||||
if len(name) > 0 {
|
||||
return name
|
||||
}
|
||||
return backend
|
||||
}
|
||||
}
|
||||
|
||||
func frontendName(name string) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
// store temporary the frontend name into the backend name
|
||||
f.Backend = name
|
||||
}
|
||||
}
|
||||
func passHostHeader() func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
f.PassHostHeader = true
|
||||
}
|
||||
}
|
||||
|
||||
func entryPoints(eps ...string) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
f.EntryPoints = eps
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func basicAuthDeprecated(auth ...string) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
f.Auth = &types.Auth{Basic: &types.Basic{Users: auth}}
|
||||
}
|
||||
}
|
||||
|
||||
func auth(opt func(*types.Auth)) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
auth := &types.Auth{}
|
||||
opt(auth)
|
||||
f.Auth = auth
|
||||
}
|
||||
}
|
||||
|
||||
func basicAuth(users ...string) func(*types.Auth) {
|
||||
return func(a *types.Auth) {
|
||||
a.Basic = &types.Basic{Users: users}
|
||||
}
|
||||
}
|
||||
|
||||
func forwardAuth(forwardURL string, opts ...func(*types.Forward)) func(*types.Auth) {
|
||||
return func(a *types.Auth) {
|
||||
fwd := &types.Forward{Address: forwardURL}
|
||||
for _, opt := range opts {
|
||||
opt(fwd)
|
||||
}
|
||||
a.Forward = fwd
|
||||
}
|
||||
}
|
||||
|
||||
func fwdAuthResponseHeaders(headers ...string) func(*types.Forward) {
|
||||
return func(f *types.Forward) {
|
||||
f.AuthResponseHeaders = headers
|
||||
}
|
||||
}
|
||||
|
||||
func fwdTrustForwardHeader() func(*types.Forward) {
|
||||
return func(f *types.Forward) {
|
||||
f.TrustForwardHeader = true
|
||||
}
|
||||
}
|
||||
|
||||
func fwdAuthTLS(cert, key string, insecure bool) func(*types.Forward) {
|
||||
return func(f *types.Forward) {
|
||||
f.TLS = &types.ClientTLS{Cert: cert, Key: key, InsecureSkipVerify: insecure}
|
||||
}
|
||||
}
|
||||
|
||||
func whiteListRange(ranges ...string) func(*types.WhiteList) {
|
||||
return func(wl *types.WhiteList) {
|
||||
wl.SourceRange = ranges
|
||||
}
|
||||
}
|
||||
|
||||
func whiteListIPStrategy(depth int, excludedIPs ...string) func(*types.WhiteList) {
|
||||
return func(wl *types.WhiteList) {
|
||||
wl.IPStrategy = &types.IPStrategy{
|
||||
Depth: depth,
|
||||
ExcludedIPs: excludedIPs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func whiteList(opts ...func(*types.WhiteList)) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
if f.WhiteList == nil {
|
||||
f.WhiteList = &types.WhiteList{}
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(f.WhiteList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func priority(value int) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
f.Priority = value
|
||||
}
|
||||
}
|
||||
|
||||
func headers(h *types.Headers) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
f.Headers = h
|
||||
}
|
||||
}
|
||||
|
||||
func redirectEntryPoint(name string) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
if f.Redirect == nil {
|
||||
f.Redirect = &types.Redirect{}
|
||||
}
|
||||
f.Redirect.EntryPoint = name
|
||||
}
|
||||
}
|
||||
|
||||
func redirectRegex(regex, replacement string) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
if f.Redirect == nil {
|
||||
f.Redirect = &types.Redirect{}
|
||||
}
|
||||
f.Redirect.Regex = regex
|
||||
f.Redirect.Replacement = replacement
|
||||
}
|
||||
}
|
||||
|
||||
func errorPage(name string, opts ...func(*types.ErrorPage)) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
if f.Errors == nil {
|
||||
f.Errors = make(map[string]*types.ErrorPage)
|
||||
}
|
||||
|
||||
if len(name) > 0 {
|
||||
f.Errors[name] = &types.ErrorPage{}
|
||||
for _, opt := range opts {
|
||||
opt(f.Errors[name])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func errorStatus(status ...string) func(*types.ErrorPage) {
|
||||
return func(page *types.ErrorPage) {
|
||||
page.Status = status
|
||||
}
|
||||
}
|
||||
|
||||
func errorQuery(query string) func(*types.ErrorPage) {
|
||||
return func(page *types.ErrorPage) {
|
||||
page.Query = query
|
||||
}
|
||||
}
|
||||
|
||||
func errorBackend(backend string) func(*types.ErrorPage) {
|
||||
return func(page *types.ErrorPage) {
|
||||
page.Backend = backend
|
||||
}
|
||||
}
|
||||
|
||||
func rateLimit(opts ...func(*types.RateLimit)) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
if f.RateLimit == nil {
|
||||
f.RateLimit = &types.RateLimit{}
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(f.RateLimit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rateExtractorFunc(exp string) func(*types.RateLimit) {
|
||||
return func(limit *types.RateLimit) {
|
||||
limit.ExtractorFunc = exp
|
||||
}
|
||||
}
|
||||
|
||||
func rateSet(name string, opts ...func(*types.Rate)) func(*types.RateLimit) {
|
||||
return func(limit *types.RateLimit) {
|
||||
if limit.RateSet == nil {
|
||||
limit.RateSet = make(map[string]*types.Rate)
|
||||
}
|
||||
|
||||
if len(name) > 0 {
|
||||
limit.RateSet[name] = &types.Rate{}
|
||||
for _, opt := range opts {
|
||||
opt(limit.RateSet[name])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func limitAverage(avg int64) func(*types.Rate) {
|
||||
return func(rate *types.Rate) {
|
||||
rate.Average = avg
|
||||
}
|
||||
}
|
||||
|
||||
func limitBurst(burst int64) func(*types.Rate) {
|
||||
return func(rate *types.Rate) {
|
||||
rate.Burst = burst
|
||||
}
|
||||
}
|
||||
|
||||
func limitPeriod(period time.Duration) func(*types.Rate) {
|
||||
return func(rate *types.Rate) {
|
||||
rate.Period = parse.Duration(period)
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func passTLSCert() func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
f.PassTLSCert = true
|
||||
}
|
||||
}
|
||||
|
||||
func passTLSClientCert() func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
f.PassTLSClientCert = &types.TLSClientHeaders{
|
||||
PEM: true,
|
||||
Infos: &types.TLSClientCertificateInfos{
|
||||
NotAfter: true,
|
||||
NotBefore: true,
|
||||
Subject: &types.TLSCLientCertificateDNInfos{
|
||||
CommonName: true,
|
||||
Country: true,
|
||||
DomainComponent: true,
|
||||
Locality: true,
|
||||
Organization: true,
|
||||
Province: true,
|
||||
SerialNumber: true,
|
||||
},
|
||||
Issuer: &types.TLSCLientCertificateDNInfos{
|
||||
CommonName: true,
|
||||
Country: true,
|
||||
DomainComponent: true,
|
||||
Locality: true,
|
||||
Organization: true,
|
||||
Province: true,
|
||||
SerialNumber: true,
|
||||
},
|
||||
Sans: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func routes(opts ...func(*types.Route) string) func(*types.Frontend) {
|
||||
return func(f *types.Frontend) {
|
||||
f.Routes = make(map[string]types.Route)
|
||||
for _, opt := range opts {
|
||||
s := &types.Route{}
|
||||
name := opt(s)
|
||||
f.Routes[name] = *s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func route(name string, rule string) func(*types.Route) string {
|
||||
return func(r *types.Route) string {
|
||||
r.Rule = rule
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
func tlsesSection(opts ...func(*tls.Configuration)) func(*types.Configuration) {
|
||||
return func(c *types.Configuration) {
|
||||
for _, opt := range opts {
|
||||
tlsConf := &tls.Configuration{}
|
||||
opt(tlsConf)
|
||||
c.TLS = append(c.TLS, tlsConf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tlsSection(opts ...func(*tls.Configuration)) func(*tls.Configuration) {
|
||||
return func(c *tls.Configuration) {
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tlsEntryPoints(entryPoints ...string) func(*tls.Configuration) {
|
||||
return func(c *tls.Configuration) {
|
||||
c.Stores = entryPoints
|
||||
}
|
||||
}
|
||||
|
||||
func certificate(cert string, key string) func(*tls.Configuration) {
|
||||
return func(c *tls.Configuration) {
|
||||
c.Certificate = &tls.Certificate{
|
||||
CertFile: tls.FileOrContent(cert),
|
||||
KeyFile: tls.FileOrContent(key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test
|
||||
|
||||
func TestBuildConfiguration(t *testing.T) {
|
||||
actual := buildConfiguration(
|
||||
backends(
|
||||
backend("foo/bar",
|
||||
servers(
|
||||
server("http://10.10.0.1:8080", weight(1)),
|
||||
server("http://10.21.0.1:8080", weight(1)),
|
||||
),
|
||||
lbMethod("wrr"),
|
||||
),
|
||||
backend("foo/namedthing",
|
||||
servers(server("https://example.com", weight(1))),
|
||||
lbMethod("wrr"),
|
||||
),
|
||||
backend("bar",
|
||||
servers(
|
||||
server("https://10.15.0.1:8443", weight(1)),
|
||||
server("https://10.15.0.2:9443", weight(1)),
|
||||
),
|
||||
lbMethod("wrr"),
|
||||
),
|
||||
),
|
||||
frontends(
|
||||
frontend("foo/bar",
|
||||
passHostHeader(),
|
||||
routes(
|
||||
route("/bar", "PathPrefix:/bar"),
|
||||
route("foo", "Host:foo"),
|
||||
),
|
||||
),
|
||||
frontend("foo/namedthing",
|
||||
passHostHeader(),
|
||||
routes(
|
||||
route("/namedthing", "PathPrefix:/namedthing"),
|
||||
route("foo", "Host:foo"),
|
||||
),
|
||||
),
|
||||
frontend("bar",
|
||||
passHostHeader(),
|
||||
routes(
|
||||
route("bar", "Host:bar"),
|
||||
),
|
||||
),
|
||||
),
|
||||
tlsesSection(
|
||||
tlsSection(
|
||||
tlsEntryPoints("https"),
|
||||
certificate("certificate", "key"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert.EqualValues(t, sampleConfiguration(), actual)
|
||||
}
|
||||
|
||||
func sampleConfiguration() *types.Configuration {
|
||||
return &types.Configuration{
|
||||
Backends: map[string]*types.Backend{
|
||||
"foo/bar": {
|
||||
Servers: map[string]types.Server{
|
||||
"http://10.10.0.1:8080": {
|
||||
URL: "http://10.10.0.1:8080",
|
||||
Weight: label.DefaultWeight,
|
||||
},
|
||||
"http://10.21.0.1:8080": {
|
||||
URL: "http://10.21.0.1:8080",
|
||||
Weight: label.DefaultWeight,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: &types.LoadBalancer{
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
"foo/namedthing": {
|
||||
Servers: map[string]types.Server{
|
||||
"https://example.com": {
|
||||
URL: "https://example.com",
|
||||
Weight: label.DefaultWeight,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: &types.LoadBalancer{
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
Servers: map[string]types.Server{
|
||||
"https://10.15.0.1:8443": {
|
||||
URL: "https://10.15.0.1:8443",
|
||||
Weight: label.DefaultWeight,
|
||||
},
|
||||
"https://10.15.0.2:9443": {
|
||||
URL: "https://10.15.0.2:9443",
|
||||
Weight: label.DefaultWeight,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: &types.LoadBalancer{
|
||||
Method: "wrr",
|
||||
},
|
||||
},
|
||||
},
|
||||
Frontends: map[string]*types.Frontend{
|
||||
"foo/bar": {
|
||||
Backend: "foo/bar",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"/bar": {
|
||||
Rule: "PathPrefix:/bar",
|
||||
},
|
||||
"foo": {
|
||||
Rule: "Host:foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
"foo/namedthing": {
|
||||
Backend: "foo/namedthing",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"/namedthing": {
|
||||
Rule: "PathPrefix:/namedthing",
|
||||
},
|
||||
"foo": {
|
||||
Rule: "Host:foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
Backend: "bar",
|
||||
PassHostHeader: true,
|
||||
Routes: map[string]types.Route{
|
||||
"bar": {
|
||||
Rule: "Host:bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: []*tls.Configuration{
|
||||
{
|
||||
Stores: []string{"https"},
|
||||
Certificate: &tls.Certificate{
|
||||
CertFile: tls.FileOrContent("certificate"),
|
||||
KeyFile: tls.FileOrContent("key"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
func buildEndpoint(opts ...func(*corev1.Endpoints)) *corev1.Endpoints {
|
||||
e := &corev1.Endpoints{}
|
||||
for _, opt := range opts {
|
||||
opt(e)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func eNamespace(value string) func(*corev1.Endpoints) {
|
||||
return func(i *corev1.Endpoints) {
|
||||
i.Namespace = value
|
||||
}
|
||||
}
|
||||
|
||||
func eName(value string) func(*corev1.Endpoints) {
|
||||
return func(i *corev1.Endpoints) {
|
||||
i.Name = value
|
||||
}
|
||||
}
|
||||
|
||||
func eUID(value types.UID) func(*corev1.Endpoints) {
|
||||
return func(i *corev1.Endpoints) {
|
||||
i.UID = value
|
||||
}
|
||||
}
|
||||
|
||||
func subset(opts ...func(*corev1.EndpointSubset)) func(*corev1.Endpoints) {
|
||||
return func(e *corev1.Endpoints) {
|
||||
s := &corev1.EndpointSubset{}
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
e.Subsets = append(e.Subsets, *s)
|
||||
}
|
||||
}
|
||||
|
||||
func eAddresses(opts ...func(*corev1.EndpointAddress)) func(*corev1.EndpointSubset) {
|
||||
return func(subset *corev1.EndpointSubset) {
|
||||
for _, opt := range opts {
|
||||
a := &corev1.EndpointAddress{}
|
||||
opt(a)
|
||||
subset.Addresses = append(subset.Addresses, *a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func eAddress(ip string) func(*corev1.EndpointAddress) {
|
||||
return func(address *corev1.EndpointAddress) {
|
||||
address.IP = ip
|
||||
}
|
||||
}
|
||||
|
||||
func eAddressWithTargetRef(targetRef, ip string) func(*corev1.EndpointAddress) {
|
||||
return func(address *corev1.EndpointAddress) {
|
||||
address.TargetRef = &corev1.ObjectReference{Name: targetRef}
|
||||
address.IP = ip
|
||||
}
|
||||
}
|
||||
|
||||
func ePorts(opts ...func(port *corev1.EndpointPort)) func(*corev1.EndpointSubset) {
|
||||
return func(spec *corev1.EndpointSubset) {
|
||||
for _, opt := range opts {
|
||||
p := &corev1.EndpointPort{}
|
||||
opt(p)
|
||||
spec.Ports = append(spec.Ports, *p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ePort(port int32, name string) func(*corev1.EndpointPort) {
|
||||
return func(sp *corev1.EndpointPort) {
|
||||
sp.Port = port
|
||||
sp.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
// Test
|
||||
|
||||
func TestBuildEndpoint(t *testing.T) {
|
||||
actual := buildEndpoint(
|
||||
eNamespace("testing"),
|
||||
eName("service3"),
|
||||
eUID("3"),
|
||||
subset(
|
||||
eAddresses(eAddress("10.15.0.1")),
|
||||
ePorts(
|
||||
ePort(8080, "http"),
|
||||
ePort(8443, "https"),
|
||||
),
|
||||
),
|
||||
subset(
|
||||
eAddresses(eAddress("10.15.0.2")),
|
||||
ePorts(
|
||||
ePort(9080, "http"),
|
||||
ePort(9443, "https"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert.EqualValues(t, sampleEndpoint1(), actual)
|
||||
}
|
||||
|
||||
func sampleEndpoint1() *corev1.Endpoints {
|
||||
return &corev1.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "service3",
|
||||
UID: "3",
|
||||
Namespace: "testing",
|
||||
},
|
||||
Subsets: []corev1.EndpointSubset{
|
||||
{
|
||||
Addresses: []corev1.EndpointAddress{
|
||||
{
|
||||
IP: "10.15.0.1",
|
||||
},
|
||||
},
|
||||
Ports: []corev1.EndpointPort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 8080,
|
||||
},
|
||||
{
|
||||
Name: "https",
|
||||
Port: 8443,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Addresses: []corev1.EndpointAddress{
|
||||
{
|
||||
IP: "10.15.0.2",
|
||||
},
|
||||
},
|
||||
Ports: []corev1.EndpointPort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 9080,
|
||||
},
|
||||
{
|
||||
Name: "https",
|
||||
Port: 9443,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
func buildIngress(opts ...func(*extensionsv1beta1.Ingress)) *extensionsv1beta1.Ingress {
|
||||
i := &extensionsv1beta1.Ingress{}
|
||||
for _, opt := range opts {
|
||||
opt(i)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func iNamespace(value string) func(*extensionsv1beta1.Ingress) {
|
||||
return func(i *extensionsv1beta1.Ingress) {
|
||||
i.Namespace = value
|
||||
}
|
||||
}
|
||||
|
||||
func iAnnotation(name string, value string) func(*extensionsv1beta1.Ingress) {
|
||||
return func(i *extensionsv1beta1.Ingress) {
|
||||
if i.Annotations == nil {
|
||||
i.Annotations = make(map[string]string)
|
||||
}
|
||||
i.Annotations[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
func iRules(opts ...func(*extensionsv1beta1.IngressSpec)) func(*extensionsv1beta1.Ingress) {
|
||||
return func(i *extensionsv1beta1.Ingress) {
|
||||
s := &extensionsv1beta1.IngressSpec{}
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
i.Spec = *s
|
||||
}
|
||||
}
|
||||
|
||||
func iSpecBackends(opts ...func(*extensionsv1beta1.IngressSpec)) func(*extensionsv1beta1.Ingress) {
|
||||
return func(i *extensionsv1beta1.Ingress) {
|
||||
s := &extensionsv1beta1.IngressSpec{}
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
i.Spec = *s
|
||||
}
|
||||
}
|
||||
|
||||
func iSpecBackend(opts ...func(*extensionsv1beta1.IngressBackend)) func(*extensionsv1beta1.IngressSpec) {
|
||||
return func(s *extensionsv1beta1.IngressSpec) {
|
||||
p := &extensionsv1beta1.IngressBackend{}
|
||||
for _, opt := range opts {
|
||||
opt(p)
|
||||
}
|
||||
s.Backend = p
|
||||
}
|
||||
}
|
||||
|
||||
func iIngressBackend(name string, port intstr.IntOrString) func(*extensionsv1beta1.IngressBackend) {
|
||||
return func(p *extensionsv1beta1.IngressBackend) {
|
||||
p.ServiceName = name
|
||||
p.ServicePort = port
|
||||
}
|
||||
}
|
||||
|
||||
func iRule(opts ...func(*extensionsv1beta1.IngressRule)) func(*extensionsv1beta1.IngressSpec) {
|
||||
return func(spec *extensionsv1beta1.IngressSpec) {
|
||||
r := &extensionsv1beta1.IngressRule{}
|
||||
for _, opt := range opts {
|
||||
opt(r)
|
||||
}
|
||||
spec.Rules = append(spec.Rules, *r)
|
||||
}
|
||||
}
|
||||
|
||||
func iHost(name string) func(*extensionsv1beta1.IngressRule) {
|
||||
return func(rule *extensionsv1beta1.IngressRule) {
|
||||
rule.Host = name
|
||||
}
|
||||
}
|
||||
|
||||
func iPaths(opts ...func(*extensionsv1beta1.HTTPIngressRuleValue)) func(*extensionsv1beta1.IngressRule) {
|
||||
return func(rule *extensionsv1beta1.IngressRule) {
|
||||
rule.HTTP = &extensionsv1beta1.HTTPIngressRuleValue{}
|
||||
for _, opt := range opts {
|
||||
opt(rule.HTTP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func onePath(opts ...func(*extensionsv1beta1.HTTPIngressPath)) func(*extensionsv1beta1.HTTPIngressRuleValue) {
|
||||
return func(irv *extensionsv1beta1.HTTPIngressRuleValue) {
|
||||
p := &extensionsv1beta1.HTTPIngressPath{}
|
||||
for _, opt := range opts {
|
||||
opt(p)
|
||||
}
|
||||
irv.Paths = append(irv.Paths, *p)
|
||||
}
|
||||
}
|
||||
|
||||
func iPath(name string) func(*extensionsv1beta1.HTTPIngressPath) {
|
||||
return func(p *extensionsv1beta1.HTTPIngressPath) {
|
||||
p.Path = name
|
||||
}
|
||||
}
|
||||
|
||||
func iBackend(name string, port intstr.IntOrString) func(*extensionsv1beta1.HTTPIngressPath) {
|
||||
return func(p *extensionsv1beta1.HTTPIngressPath) {
|
||||
p.Backend = extensionsv1beta1.IngressBackend{
|
||||
ServiceName: name,
|
||||
ServicePort: port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func iTLSes(opts ...func(*extensionsv1beta1.IngressTLS)) func(*extensionsv1beta1.Ingress) {
|
||||
return func(i *extensionsv1beta1.Ingress) {
|
||||
for _, opt := range opts {
|
||||
iTLS := extensionsv1beta1.IngressTLS{}
|
||||
opt(&iTLS)
|
||||
i.Spec.TLS = append(i.Spec.TLS, iTLS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func iTLS(secret string, hosts ...string) func(*extensionsv1beta1.IngressTLS) {
|
||||
return func(i *extensionsv1beta1.IngressTLS) {
|
||||
i.SecretName = secret
|
||||
i.Hosts = hosts
|
||||
}
|
||||
}
|
||||
|
||||
// Test
|
||||
|
||||
func TestBuildIngress(t *testing.T) {
|
||||
i := buildIngress(
|
||||
iNamespace("testing"),
|
||||
iRules(
|
||||
iRule(iHost("foo"), iPaths(
|
||||
onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(80))),
|
||||
onePath(iPath("/namedthing"), iBackend("service4", intstr.FromString("https")))),
|
||||
),
|
||||
iRule(iHost("bar"), iPaths(
|
||||
onePath(iBackend("service3", intstr.FromString("https"))),
|
||||
onePath(iBackend("service2", intstr.FromInt(802))),
|
||||
),
|
||||
),
|
||||
),
|
||||
iTLSes(
|
||||
iTLS("tls-secret", "foo"),
|
||||
),
|
||||
)
|
||||
|
||||
assert.EqualValues(t, sampleIngress(), i)
|
||||
}
|
||||
|
||||
func sampleIngress() *extensionsv1beta1.Ingress {
|
||||
return &extensionsv1beta1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "testing",
|
||||
},
|
||||
Spec: extensionsv1beta1.IngressSpec{
|
||||
Rules: []extensionsv1beta1.IngressRule{
|
||||
{
|
||||
Host: "foo",
|
||||
IngressRuleValue: extensionsv1beta1.IngressRuleValue{
|
||||
HTTP: &extensionsv1beta1.HTTPIngressRuleValue{
|
||||
Paths: []extensionsv1beta1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/bar",
|
||||
Backend: extensionsv1beta1.IngressBackend{
|
||||
ServiceName: "service1",
|
||||
ServicePort: intstr.FromInt(80),
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "/namedthing",
|
||||
Backend: extensionsv1beta1.IngressBackend{
|
||||
ServiceName: "service4",
|
||||
ServicePort: intstr.FromString("https"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Host: "bar",
|
||||
IngressRuleValue: extensionsv1beta1.IngressRuleValue{
|
||||
HTTP: &extensionsv1beta1.HTTPIngressRuleValue{
|
||||
Paths: []extensionsv1beta1.HTTPIngressPath{
|
||||
{
|
||||
Backend: extensionsv1beta1.IngressBackend{
|
||||
ServiceName: "service3",
|
||||
ServicePort: intstr.FromString("https"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Backend: extensionsv1beta1.IngressBackend{
|
||||
ServiceName: "service2",
|
||||
ServicePort: intstr.FromInt(802),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
TLS: []extensionsv1beta1.IngressTLS{
|
||||
{
|
||||
Hosts: []string{"foo"},
|
||||
SecretName: "tls-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,232 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
func buildService(opts ...func(*corev1.Service)) *corev1.Service {
|
||||
s := &corev1.Service{}
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func sNamespace(value string) func(*corev1.Service) {
|
||||
return func(i *corev1.Service) {
|
||||
i.Namespace = value
|
||||
}
|
||||
}
|
||||
|
||||
func sName(value string) func(*corev1.Service) {
|
||||
return func(i *corev1.Service) {
|
||||
i.Name = value
|
||||
}
|
||||
}
|
||||
|
||||
func sUID(value types.UID) func(*corev1.Service) {
|
||||
return func(i *corev1.Service) {
|
||||
i.UID = value
|
||||
}
|
||||
}
|
||||
|
||||
func sAnnotation(name string, value string) func(*corev1.Service) {
|
||||
return func(s *corev1.Service) {
|
||||
if s.Annotations == nil {
|
||||
s.Annotations = make(map[string]string)
|
||||
}
|
||||
s.Annotations[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
func sSpec(opts ...func(*corev1.ServiceSpec)) func(*corev1.Service) {
|
||||
return func(s *corev1.Service) {
|
||||
spec := &corev1.ServiceSpec{}
|
||||
for _, opt := range opts {
|
||||
opt(spec)
|
||||
}
|
||||
s.Spec = *spec
|
||||
}
|
||||
}
|
||||
|
||||
func sLoadBalancerStatus(opts ...func(*corev1.LoadBalancerStatus)) func(service *corev1.Service) {
|
||||
return func(s *corev1.Service) {
|
||||
loadBalancer := &corev1.LoadBalancerStatus{}
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(loadBalancer)
|
||||
}
|
||||
}
|
||||
s.Status = corev1.ServiceStatus{
|
||||
LoadBalancer: *loadBalancer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sLoadBalancerIngress(ip string, hostname string) func(*corev1.LoadBalancerStatus) {
|
||||
return func(status *corev1.LoadBalancerStatus) {
|
||||
ingress := corev1.LoadBalancerIngress{
|
||||
IP: ip,
|
||||
Hostname: hostname,
|
||||
}
|
||||
status.Ingress = append(status.Ingress, ingress)
|
||||
}
|
||||
}
|
||||
|
||||
func clusterIP(ip string) func(*corev1.ServiceSpec) {
|
||||
return func(spec *corev1.ServiceSpec) {
|
||||
spec.ClusterIP = ip
|
||||
}
|
||||
}
|
||||
|
||||
func sType(value corev1.ServiceType) func(*corev1.ServiceSpec) {
|
||||
return func(spec *corev1.ServiceSpec) {
|
||||
spec.Type = value
|
||||
}
|
||||
}
|
||||
|
||||
func sExternalName(name string) func(*corev1.ServiceSpec) {
|
||||
return func(spec *corev1.ServiceSpec) {
|
||||
spec.ExternalName = name
|
||||
}
|
||||
}
|
||||
|
||||
func sPorts(opts ...func(*corev1.ServicePort)) func(*corev1.ServiceSpec) {
|
||||
return func(spec *corev1.ServiceSpec) {
|
||||
for _, opt := range opts {
|
||||
p := &corev1.ServicePort{}
|
||||
opt(p)
|
||||
spec.Ports = append(spec.Ports, *p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sPort(port int32, name string) func(*corev1.ServicePort) {
|
||||
return func(sp *corev1.ServicePort) {
|
||||
sp.Port = port
|
||||
sp.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
// Test
|
||||
|
||||
func TestBuildService(t *testing.T) {
|
||||
actual1 := buildService(
|
||||
sName("service1"),
|
||||
sNamespace("testing"),
|
||||
sUID("1"),
|
||||
sSpec(
|
||||
clusterIP("10.0.0.1"),
|
||||
sPorts(sPort(80, "")),
|
||||
),
|
||||
)
|
||||
|
||||
assert.EqualValues(t, sampleService1(), actual1)
|
||||
|
||||
actual2 := buildService(
|
||||
sName("service2"),
|
||||
sNamespace("testing"),
|
||||
sUID("2"),
|
||||
sSpec(
|
||||
clusterIP("10.0.0.2"),
|
||||
sType("ExternalName"),
|
||||
sExternalName("example.com"),
|
||||
sPorts(
|
||||
sPort(80, "http"),
|
||||
sPort(443, "https"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert.EqualValues(t, sampleService2(), actual2)
|
||||
|
||||
actual3 := buildService(
|
||||
sName("service3"),
|
||||
sNamespace("testing"),
|
||||
sUID("3"),
|
||||
sSpec(
|
||||
clusterIP("10.0.0.3"),
|
||||
sType("ExternalName"),
|
||||
sExternalName("example.com"),
|
||||
sPorts(
|
||||
sPort(8080, "http"),
|
||||
sPort(8443, "https"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert.EqualValues(t, sampleService3(), actual3)
|
||||
}
|
||||
|
||||
func sampleService1() *corev1.Service {
|
||||
return &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "service1",
|
||||
UID: "1",
|
||||
Namespace: "testing",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
ClusterIP: "10.0.0.1",
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func sampleService2() *corev1.Service {
|
||||
return &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "service2",
|
||||
UID: "2",
|
||||
Namespace: "testing",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
ClusterIP: "10.0.0.2",
|
||||
Type: "ExternalName",
|
||||
ExternalName: "example.com",
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
},
|
||||
{
|
||||
Name: "https",
|
||||
Port: 443,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func sampleService3() *corev1.Service {
|
||||
return &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "service3",
|
||||
UID: "3",
|
||||
Namespace: "testing",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
ClusterIP: "10.0.0.3",
|
||||
Type: "ExternalName",
|
||||
ExternalName: "example.com",
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 8080,
|
||||
},
|
||||
{
|
||||
Name: "https",
|
||||
Port: 8443,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,292 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/old/log"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
kubeerror "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
const resyncPeriod = 10 * time.Minute
|
||||
|
||||
type resourceEventHandler struct {
|
||||
ev chan<- interface{}
|
||||
}
|
||||
|
||||
func (reh *resourceEventHandler) OnAdd(obj interface{}) {
|
||||
eventHandlerFunc(reh.ev, obj)
|
||||
}
|
||||
|
||||
func (reh *resourceEventHandler) OnUpdate(oldObj, newObj interface{}) {
|
||||
eventHandlerFunc(reh.ev, newObj)
|
||||
}
|
||||
|
||||
func (reh *resourceEventHandler) OnDelete(obj interface{}) {
|
||||
eventHandlerFunc(reh.ev, obj)
|
||||
}
|
||||
|
||||
// Client is a client for the Provider master.
|
||||
// WatchAll starts the watch of the Provider resources and updates the stores.
|
||||
// The stores can then be accessed via the Get* functions.
|
||||
type Client interface {
|
||||
WatchAll(namespaces Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error)
|
||||
GetIngresses() []*extensionsv1beta1.Ingress
|
||||
GetService(namespace, name string) (*corev1.Service, bool, error)
|
||||
GetSecret(namespace, name string) (*corev1.Secret, bool, error)
|
||||
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
|
||||
UpdateIngressStatus(namespace, name, ip, hostname string) error
|
||||
}
|
||||
|
||||
type clientImpl struct {
|
||||
clientset *kubernetes.Clientset
|
||||
factories map[string]informers.SharedInformerFactory
|
||||
ingressLabelSelector labels.Selector
|
||||
isNamespaceAll bool
|
||||
watchedNamespaces Namespaces
|
||||
}
|
||||
|
||||
func newClientImpl(clientset *kubernetes.Clientset) *clientImpl {
|
||||
return &clientImpl{
|
||||
clientset: clientset,
|
||||
factories: make(map[string]informers.SharedInformerFactory),
|
||||
}
|
||||
}
|
||||
|
||||
// newInClusterClient returns a new Provider client that is expected to run
|
||||
// inside the cluster.
|
||||
func newInClusterClient(endpoint string) (*clientImpl, error) {
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create in-cluster configuration: %s", err)
|
||||
}
|
||||
|
||||
if endpoint != "" {
|
||||
config.Host = endpoint
|
||||
}
|
||||
|
||||
return createClientFromConfig(config)
|
||||
}
|
||||
|
||||
// newExternalClusterClient returns a new Provider client that may run outside
|
||||
// of the cluster.
|
||||
// The endpoint parameter must not be empty.
|
||||
func newExternalClusterClient(endpoint, token, caFilePath string) (*clientImpl, error) {
|
||||
if endpoint == "" {
|
||||
return nil, errors.New("endpoint missing for external cluster client")
|
||||
}
|
||||
|
||||
config := &rest.Config{
|
||||
Host: endpoint,
|
||||
BearerToken: token,
|
||||
}
|
||||
|
||||
if caFilePath != "" {
|
||||
caData, err := ioutil.ReadFile(caFilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read CA file %s: %s", caFilePath, err)
|
||||
}
|
||||
|
||||
config.TLSClientConfig = rest.TLSClientConfig{CAData: caData}
|
||||
}
|
||||
|
||||
return createClientFromConfig(config)
|
||||
}
|
||||
|
||||
func createClientFromConfig(c *rest.Config) (*clientImpl, error) {
|
||||
clientset, err := kubernetes.NewForConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newClientImpl(clientset), nil
|
||||
}
|
||||
|
||||
// WatchAll starts namespace-specific controllers for all relevant kinds.
|
||||
func (c *clientImpl) WatchAll(namespaces Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
eventCh := make(chan interface{}, 1)
|
||||
|
||||
if len(namespaces) == 0 {
|
||||
namespaces = Namespaces{metav1.NamespaceAll}
|
||||
c.isNamespaceAll = true
|
||||
}
|
||||
|
||||
c.watchedNamespaces = namespaces
|
||||
|
||||
eventHandler := c.newResourceEventHandler(eventCh)
|
||||
for _, ns := range namespaces {
|
||||
factory := informers.NewFilteredSharedInformerFactory(c.clientset, resyncPeriod, ns, nil)
|
||||
factory.Extensions().V1beta1().Ingresses().Informer().AddEventHandler(eventHandler)
|
||||
factory.Core().V1().Services().Informer().AddEventHandler(eventHandler)
|
||||
factory.Core().V1().Endpoints().Informer().AddEventHandler(eventHandler)
|
||||
c.factories[ns] = factory
|
||||
}
|
||||
|
||||
for _, ns := range namespaces {
|
||||
c.factories[ns].Start(stopCh)
|
||||
}
|
||||
|
||||
for _, ns := range namespaces {
|
||||
for t, ok := range c.factories[ns].WaitForCacheSync(stopCh) {
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do not wait for the Secrets store to get synced since we cannot rely on
|
||||
// users having granted RBAC permissions for this object.
|
||||
// https://github.com/containous/traefik/issues/1784 should improve the
|
||||
// situation here in the future.
|
||||
for _, ns := range namespaces {
|
||||
c.factories[ns].Core().V1().Secrets().Informer().AddEventHandler(eventHandler)
|
||||
c.factories[ns].Start(stopCh)
|
||||
}
|
||||
|
||||
return eventCh, nil
|
||||
}
|
||||
|
||||
// GetIngresses returns all Ingresses for observed namespaces in the cluster.
|
||||
func (c *clientImpl) GetIngresses() []*extensionsv1beta1.Ingress {
|
||||
var result []*extensionsv1beta1.Ingress
|
||||
for ns, factory := range c.factories {
|
||||
ings, err := factory.Extensions().V1beta1().Ingresses().Lister().List(c.ingressLabelSelector)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list ingresses in namespace %s: %s", ns, err)
|
||||
}
|
||||
result = append(result, ings...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// UpdateIngressStatus updates an Ingress with a provided status.
|
||||
func (c *clientImpl) UpdateIngressStatus(namespace, name, ip, hostname string) error {
|
||||
if !c.isWatchedNamespace(namespace) {
|
||||
return fmt.Errorf("failed to get ingress %s/%s: namespace is not within watched namespaces", namespace, name)
|
||||
}
|
||||
|
||||
ing, err := c.factories[c.lookupNamespace(namespace)].Extensions().V1beta1().Ingresses().Lister().Ingresses(namespace).Get(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get ingress %s/%s: %v", namespace, name, err)
|
||||
}
|
||||
|
||||
if len(ing.Status.LoadBalancer.Ingress) > 0 {
|
||||
if ing.Status.LoadBalancer.Ingress[0].Hostname == hostname && ing.Status.LoadBalancer.Ingress[0].IP == ip {
|
||||
// If status is already set, skip update
|
||||
log.Debugf("Skipping status update on ingress %s/%s", ing.Namespace, ing.Name)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
ingCopy := ing.DeepCopy()
|
||||
ingCopy.Status = extensionsv1beta1.IngressStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{{IP: ip, Hostname: hostname}}}}
|
||||
|
||||
_, err = c.clientset.ExtensionsV1beta1().Ingresses(ingCopy.Namespace).UpdateStatus(ingCopy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update ingress status %s/%s: %v", namespace, name, err)
|
||||
}
|
||||
log.Infof("Updated status on ingress %s/%s", namespace, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetService returns the named service from the given namespace.
|
||||
func (c *clientImpl) GetService(namespace, name string) (*corev1.Service, bool, error) {
|
||||
if !c.isWatchedNamespace(namespace) {
|
||||
return nil, false, fmt.Errorf("failed to get service %s/%s: namespace is not within watched namespaces", namespace, name)
|
||||
}
|
||||
|
||||
service, err := c.factories[c.lookupNamespace(namespace)].Core().V1().Services().Lister().Services(namespace).Get(name)
|
||||
exist, err := translateNotFoundError(err)
|
||||
return service, exist, err
|
||||
}
|
||||
|
||||
// GetEndpoints returns the named endpoints from the given namespace.
|
||||
func (c *clientImpl) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) {
|
||||
if !c.isWatchedNamespace(namespace) {
|
||||
return nil, false, fmt.Errorf("failed to get endpoints %s/%s: namespace is not within watched namespaces", namespace, name)
|
||||
}
|
||||
|
||||
endpoint, err := c.factories[c.lookupNamespace(namespace)].Core().V1().Endpoints().Lister().Endpoints(namespace).Get(name)
|
||||
exist, err := translateNotFoundError(err)
|
||||
return endpoint, exist, err
|
||||
}
|
||||
|
||||
// GetSecret returns the named secret from the given namespace.
|
||||
func (c *clientImpl) GetSecret(namespace, name string) (*corev1.Secret, bool, error) {
|
||||
if !c.isWatchedNamespace(namespace) {
|
||||
return nil, false, fmt.Errorf("failed to get secret %s/%s: namespace is not within watched namespaces", namespace, name)
|
||||
}
|
||||
|
||||
secret, err := c.factories[c.lookupNamespace(namespace)].Core().V1().Secrets().Lister().Secrets(namespace).Get(name)
|
||||
exist, err := translateNotFoundError(err)
|
||||
return secret, exist, err
|
||||
}
|
||||
|
||||
// lookupNamespace returns the lookup namespace key for the given namespace.
|
||||
// When listening on all namespaces, it returns the client-go identifier ("")
|
||||
// for all-namespaces. Otherwise, it returns the given namespace.
|
||||
// The distinction is necessary because we index all informers on the special
|
||||
// identifier iff all-namespaces are requested but receive specific namespace
|
||||
// identifiers from the Kubernetes API, so we have to bridge this gap.
|
||||
func (c *clientImpl) lookupNamespace(ns string) string {
|
||||
if c.isNamespaceAll {
|
||||
return metav1.NamespaceAll
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
func (c *clientImpl) newResourceEventHandler(events chan<- interface{}) cache.ResourceEventHandler {
|
||||
return &cache.FilteringResourceEventHandler{
|
||||
FilterFunc: func(obj interface{}) bool {
|
||||
// Ignore Ingresses that do not match our custom label selector.
|
||||
if ing, ok := obj.(*extensionsv1beta1.Ingress); ok {
|
||||
lbls := labels.Set(ing.GetLabels())
|
||||
return c.ingressLabelSelector.Matches(lbls)
|
||||
}
|
||||
return true
|
||||
},
|
||||
Handler: &resourceEventHandler{ev: events},
|
||||
}
|
||||
}
|
||||
|
||||
// eventHandlerFunc will pass the obj on to the events channel or drop it.
|
||||
// This is so passing the events along won't block in the case of high volume.
|
||||
// The events are only used for signaling anyway so dropping a few is ok.
|
||||
func eventHandlerFunc(events chan<- interface{}, obj interface{}) {
|
||||
select {
|
||||
case events <- obj:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// translateNotFoundError will translate a "not found" error to a boolean return
|
||||
// value which indicates if the resource exists and a nil error.
|
||||
func translateNotFoundError(err error) (bool, error) {
|
||||
if kubeerror.IsNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
// isWatchedNamespace checks to ensure that the namespace is being watched before we request
|
||||
// it to ensure we don't panic by requesting an out-of-watch object
|
||||
func (c *clientImpl) isWatchedNamespace(ns string) bool {
|
||||
if c.isNamespaceAll {
|
||||
return true
|
||||
}
|
||||
for _, watchedNamespace := range c.watchedNamespaces {
|
||||
if watchedNamespace == ns {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
)
|
||||
|
||||
type clientMock struct {
|
||||
ingresses []*extensionsv1beta1.Ingress
|
||||
services []*corev1.Service
|
||||
secrets []*corev1.Secret
|
||||
endpoints []*corev1.Endpoints
|
||||
watchChan chan interface{}
|
||||
|
||||
apiServiceError error
|
||||
apiSecretError error
|
||||
apiEndpointsError error
|
||||
apiIngressStatusError error
|
||||
}
|
||||
|
||||
func (c clientMock) GetIngresses() []*extensionsv1beta1.Ingress {
|
||||
return c.ingresses
|
||||
}
|
||||
|
||||
func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) {
|
||||
if c.apiServiceError != nil {
|
||||
return nil, false, c.apiServiceError
|
||||
}
|
||||
|
||||
for _, service := range c.services {
|
||||
if service.Namespace == namespace && service.Name == name {
|
||||
return service, true, nil
|
||||
}
|
||||
}
|
||||
return nil, false, c.apiServiceError
|
||||
}
|
||||
|
||||
func (c clientMock) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) {
|
||||
if c.apiEndpointsError != nil {
|
||||
return nil, false, c.apiEndpointsError
|
||||
}
|
||||
|
||||
for _, endpoints := range c.endpoints {
|
||||
if endpoints.Namespace == namespace && endpoints.Name == name {
|
||||
return endpoints, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &corev1.Endpoints{}, false, nil
|
||||
}
|
||||
|
||||
func (c clientMock) GetSecret(namespace, name string) (*corev1.Secret, bool, error) {
|
||||
if c.apiSecretError != nil {
|
||||
return nil, false, c.apiSecretError
|
||||
}
|
||||
|
||||
for _, secret := range c.secrets {
|
||||
if secret.Namespace == namespace && secret.Name == name {
|
||||
return secret, true, nil
|
||||
}
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (c clientMock) WatchAll(namespaces Namespaces, stopCh <-chan struct{}) (<-chan interface{}, error) {
|
||||
return c.watchChan, nil
|
||||
}
|
||||
|
||||
func (c clientMock) UpdateIngressStatus(namespace, name, ip, hostname string) error {
|
||||
return c.apiIngressStatusError
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
kubeerror "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func TestTranslateNotFoundError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
err error
|
||||
expectedExists bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
desc: "kubernetes not found error",
|
||||
err: kubeerror.NewNotFound(schema.GroupResource{}, "foo"),
|
||||
expectedExists: false,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
desc: "nil error",
|
||||
err: nil,
|
||||
expectedExists: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
desc: "not a kubernetes not found error",
|
||||
err: fmt.Errorf("bar error"),
|
||||
expectedExists: false,
|
||||
expectedError: fmt.Errorf("bar error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
test := testCase
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
exists, err := translateNotFoundError(test.err)
|
||||
assert.Equal(t, test.expectedExists, exists)
|
||||
assert.Equal(t, test.expectedError, err)
|
||||
})
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,32 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Namespaces holds kubernetes namespaces
|
||||
type Namespaces []string
|
||||
|
||||
// Set adds strings elem into the the parser
|
||||
// it splits str on , and ;
|
||||
func (ns *Namespaces) Set(str string) error {
|
||||
fargs := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
// get function
|
||||
slice := strings.FieldsFunc(str, fargs)
|
||||
*ns = append(*ns, slice...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get []string
|
||||
func (ns *Namespaces) Get() interface{} { return *ns }
|
||||
|
||||
// String return slice in a string
|
||||
func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) }
|
||||
|
||||
// SetValue sets []string into the parser
|
||||
func (ns *Namespaces) SetValue(val interface{}) {
|
||||
*ns = val.(Namespaces)
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const defaultPercentageValuePrecision = 3
|
||||
|
||||
// percentageValue is int64 form of percentage value with 10^-3 precision.
|
||||
type percentageValue int64
|
||||
|
||||
// toFloat64 returns its decimal float64 value.
|
||||
func (v percentageValue) toFloat64() float64 {
|
||||
return float64(v) / (1000 * 100)
|
||||
}
|
||||
|
||||
func (v percentageValue) computeWeight(count int) int {
|
||||
if count == 0 {
|
||||
return 0
|
||||
}
|
||||
return int(float64(v) / float64(count))
|
||||
}
|
||||
|
||||
// String returns its string form of percentage value.
|
||||
func (v percentageValue) String() string {
|
||||
return strconv.FormatFloat(v.toFloat64()*100, 'f', defaultPercentageValuePrecision, 64) + "%"
|
||||
}
|
||||
|
||||
// newPercentageValueFromString tries to read percentage value from string, it can be either "1.1" or "1.1%", "6%".
|
||||
// It will lose the extra precision if there are more digits after decimal point.
|
||||
func newPercentageValueFromString(rawValue string) (percentageValue, error) {
|
||||
if strings.HasSuffix(rawValue, "%") {
|
||||
rawValue = rawValue[:len(rawValue)-1]
|
||||
}
|
||||
value, err := strconv.ParseFloat(rawValue, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return newPercentageValueFromFloat64(value) / 100, nil
|
||||
}
|
||||
|
||||
// newPercentageValueFromFloat64 reads percentage value from float64
|
||||
func newPercentageValueFromFloat64(f float64) percentageValue {
|
||||
return percentageValue(f * (1000 * 100))
|
||||
}
|
|
@ -1,196 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewPercentageValueFromFloat64(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
value float64
|
||||
expectedString string
|
||||
expectedFloat64 float64
|
||||
}{
|
||||
{
|
||||
value: 0.01,
|
||||
expectedString: "1.000%",
|
||||
expectedFloat64: 0.01,
|
||||
},
|
||||
{
|
||||
value: 0.5,
|
||||
expectedString: "50.000%",
|
||||
expectedFloat64: 0.5,
|
||||
},
|
||||
{
|
||||
value: 0.99,
|
||||
expectedString: "99.000%",
|
||||
expectedFloat64: 0.99,
|
||||
},
|
||||
{
|
||||
value: 0.99999,
|
||||
expectedString: "99.999%",
|
||||
expectedFloat64: 0.99999,
|
||||
},
|
||||
{
|
||||
value: -0.99999,
|
||||
expectedString: "-99.999%",
|
||||
expectedFloat64: -0.99999,
|
||||
},
|
||||
{
|
||||
value: -0.9999999,
|
||||
expectedString: "-99.999%",
|
||||
expectedFloat64: -0.99999,
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
expectedString: "0.000%",
|
||||
expectedFloat64: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pvFromFloat64 := newPercentageValueFromFloat64(test.value)
|
||||
|
||||
assert.Equal(t, test.expectedString, pvFromFloat64.String(), "percentage string value mismatched")
|
||||
assert.Equal(t, test.expectedFloat64, pvFromFloat64.toFloat64(), "percentage float64 value mismatched")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPercentageValueFromString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
value string
|
||||
expectError bool
|
||||
expectedString string
|
||||
expectedFloat64 float64
|
||||
}{
|
||||
{
|
||||
value: "1%",
|
||||
expectError: false,
|
||||
expectedString: "1.000%",
|
||||
expectedFloat64: 0.01,
|
||||
},
|
||||
{
|
||||
value: "0.5",
|
||||
expectError: false,
|
||||
expectedString: "0.500%",
|
||||
expectedFloat64: 0.005,
|
||||
},
|
||||
{
|
||||
value: "99%",
|
||||
expectError: false,
|
||||
expectedString: "99.000%",
|
||||
expectedFloat64: 0.99,
|
||||
},
|
||||
{
|
||||
value: "99.9%",
|
||||
expectError: false,
|
||||
expectedString: "99.900%",
|
||||
expectedFloat64: 0.999,
|
||||
},
|
||||
{
|
||||
value: "-99.9%",
|
||||
expectError: false,
|
||||
expectedString: "-99.900%",
|
||||
expectedFloat64: -0.999,
|
||||
},
|
||||
{
|
||||
value: "-99.99999%",
|
||||
expectError: false,
|
||||
expectedString: "-99.999%",
|
||||
expectedFloat64: -0.99999,
|
||||
},
|
||||
{
|
||||
value: "0%",
|
||||
expectError: false,
|
||||
expectedString: "0.000%",
|
||||
expectedFloat64: 0,
|
||||
},
|
||||
{
|
||||
value: "%",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
value: "foo",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
value: "",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pvFromString, err := newPercentageValueFromString(test.value)
|
||||
|
||||
if test.expectError {
|
||||
require.Error(t, err, "expecting error but not happening")
|
||||
} else {
|
||||
require.NoError(t, err, "fail to parse percentage value")
|
||||
|
||||
assert.Equal(t, test.expectedString, pvFromString.String(), "percentage string value mismatched")
|
||||
assert.Equal(t, test.expectedFloat64, pvFromString.toFloat64(), "percentage float64 value mismatched")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPercentageValue(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
stringValue string
|
||||
floatValue float64
|
||||
}{
|
||||
{
|
||||
desc: "percentage",
|
||||
stringValue: "1%",
|
||||
floatValue: 0.01,
|
||||
},
|
||||
{
|
||||
desc: "decimal",
|
||||
stringValue: "0.5",
|
||||
floatValue: 0.005,
|
||||
},
|
||||
{
|
||||
desc: "negative percentage",
|
||||
stringValue: "-99.999%",
|
||||
floatValue: -0.99999,
|
||||
},
|
||||
{
|
||||
desc: "negative decimal",
|
||||
stringValue: "-0.99999",
|
||||
floatValue: -0.0099999,
|
||||
},
|
||||
{
|
||||
desc: "zero",
|
||||
stringValue: "0%",
|
||||
floatValue: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pvFromString, err := newPercentageValueFromString(test.stringValue)
|
||||
require.NoError(t, err, "fail to parse percentage value")
|
||||
|
||||
pvFromFloat64 := newPercentageValueFromFloat64(test.floatValue)
|
||||
|
||||
assert.Equal(t, pvFromString, pvFromFloat64)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/old/provider/label"
|
||||
"gopkg.in/yaml.v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
)
|
||||
|
||||
type weightAllocator interface {
|
||||
getWeight(host, path, serviceName string) int
|
||||
}
|
||||
|
||||
var _ weightAllocator = &defaultWeightAllocator{}
|
||||
var _ weightAllocator = &fractionalWeightAllocator{}
|
||||
|
||||
type defaultWeightAllocator struct{}
|
||||
|
||||
func (d *defaultWeightAllocator) getWeight(host, path, serviceName string) int {
|
||||
return label.DefaultWeight
|
||||
}
|
||||
|
||||
type ingressService struct {
|
||||
host string
|
||||
path string
|
||||
service string
|
||||
}
|
||||
|
||||
type fractionalWeightAllocator map[ingressService]int
|
||||
|
||||
// String returns a string representation as service name / percentage tuples
|
||||
// sorted by service names.
|
||||
// Example: [foo-svc: 30.000% bar-svc: 70.000%]
|
||||
func (f *fractionalWeightAllocator) String() string {
|
||||
var sorted []ingressService
|
||||
for ingServ := range map[ingressService]int(*f) {
|
||||
sorted = append(sorted, ingServ)
|
||||
}
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
return sorted[i].service < sorted[j].service
|
||||
})
|
||||
|
||||
var res []string
|
||||
for _, ingServ := range sorted {
|
||||
res = append(res, fmt.Sprintf("%s: %s", ingServ.service, percentageValue(map[ingressService]int(*f)[ingServ])))
|
||||
}
|
||||
return fmt.Sprintf("[%s]", strings.Join(res, " "))
|
||||
}
|
||||
|
||||
func newFractionalWeightAllocator(ingress *extensionsv1beta1.Ingress, client Client) (*fractionalWeightAllocator, error) {
|
||||
servicePercentageWeights, err := getServicesPercentageWeights(ingress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serviceInstanceCounts, err := getServiceInstanceCounts(ingress, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serviceWeights := map[ingressService]int{}
|
||||
|
||||
for _, rule := range ingress.Spec.Rules {
|
||||
// key: rule path string
|
||||
// value: service names
|
||||
fractionalPathServices := map[string][]string{}
|
||||
|
||||
// key: rule path string
|
||||
// value: fractional percentage weight
|
||||
fractionalPathWeights := map[string]percentageValue{}
|
||||
|
||||
for _, pa := range rule.HTTP.Paths {
|
||||
if _, ok := fractionalPathWeights[pa.Path]; !ok {
|
||||
fractionalPathWeights[pa.Path] = newPercentageValueFromFloat64(1)
|
||||
}
|
||||
|
||||
if weight, ok := servicePercentageWeights[pa.Backend.ServiceName]; ok {
|
||||
ingSvc := ingressService{
|
||||
host: rule.Host,
|
||||
path: pa.Path,
|
||||
service: pa.Backend.ServiceName,
|
||||
}
|
||||
|
||||
serviceWeights[ingSvc] = weight.computeWeight(serviceInstanceCounts[ingSvc])
|
||||
|
||||
fractionalPathWeights[pa.Path] -= weight
|
||||
|
||||
if fractionalPathWeights[pa.Path].toFloat64() < 0 {
|
||||
assignedWeight := newPercentageValueFromFloat64(1) - fractionalPathWeights[pa.Path]
|
||||
return nil, fmt.Errorf("percentage value %s must not exceed 100%%", assignedWeight.String())
|
||||
}
|
||||
} else {
|
||||
fractionalPathServices[pa.Path] = append(fractionalPathServices[pa.Path], pa.Backend.ServiceName)
|
||||
}
|
||||
}
|
||||
|
||||
for pa, fractionalWeight := range fractionalPathWeights {
|
||||
fractionalServices := fractionalPathServices[pa]
|
||||
|
||||
if len(fractionalServices) == 0 {
|
||||
if fractionalWeight > 0 {
|
||||
assignedWeight := newPercentageValueFromFloat64(1) - fractionalWeight
|
||||
return nil, fmt.Errorf("the sum of weights(%s) in the path %s%s must be 100%% when no omitted fractional service left", assignedWeight.String(), rule.Host, pa)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
totalFractionalInstanceCount := 0
|
||||
for _, svc := range fractionalServices {
|
||||
totalFractionalInstanceCount += serviceInstanceCounts[ingressService{
|
||||
host: rule.Host,
|
||||
path: pa,
|
||||
service: svc,
|
||||
}]
|
||||
}
|
||||
|
||||
for _, svc := range fractionalServices {
|
||||
ingSvc := ingressService{
|
||||
host: rule.Host,
|
||||
path: pa,
|
||||
service: svc,
|
||||
}
|
||||
serviceWeights[ingSvc] = fractionalWeight.computeWeight(totalFractionalInstanceCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allocator := fractionalWeightAllocator(serviceWeights)
|
||||
return &allocator, nil
|
||||
}
|
||||
|
||||
func (f *fractionalWeightAllocator) getWeight(host, path, serviceName string) int {
|
||||
return map[ingressService]int(*f)[ingressService{
|
||||
host: host,
|
||||
path: path,
|
||||
service: serviceName,
|
||||
}]
|
||||
}
|
||||
|
||||
func getServicesPercentageWeights(ingress *extensionsv1beta1.Ingress) (map[string]percentageValue, error) {
|
||||
percentageWeight := make(map[string]string)
|
||||
|
||||
annotationPercentageWeights := getAnnotationName(ingress.Annotations, annotationKubernetesServiceWeights)
|
||||
if err := yaml.Unmarshal([]byte(ingress.Annotations[annotationPercentageWeights]), percentageWeight); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
servicesPercentageWeights := make(map[string]percentageValue)
|
||||
for serviceName, percentageStr := range percentageWeight {
|
||||
percentageValue, err := newPercentageValueFromString(percentageStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid percentage value %q", percentageStr)
|
||||
}
|
||||
|
||||
servicesPercentageWeights[serviceName] = percentageValue
|
||||
}
|
||||
return servicesPercentageWeights, nil
|
||||
}
|
||||
|
||||
func getServiceInstanceCounts(ingress *extensionsv1beta1.Ingress, client Client) (map[ingressService]int, error) {
|
||||
serviceInstanceCounts := map[ingressService]int{}
|
||||
|
||||
for _, rule := range ingress.Spec.Rules {
|
||||
for _, pa := range rule.HTTP.Paths {
|
||||
svc, exists, err := client.GetService(ingress.Namespace, pa.Backend.ServiceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get service %s/%s: %v", ingress.Namespace, pa.Backend.ServiceName, err)
|
||||
}
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("service not found for %s/%s", ingress.Namespace, pa.Backend.ServiceName)
|
||||
}
|
||||
if svc.Spec.Type == corev1.ServiceTypeExternalName {
|
||||
// external-name service has only one instance b/c it will actually be interpreted as a DNS record
|
||||
// instead of real server.
|
||||
serviceInstanceCounts[ingressService{
|
||||
host: rule.Host,
|
||||
path: pa.Path,
|
||||
service: pa.Backend.ServiceName,
|
||||
}] = 1
|
||||
continue
|
||||
}
|
||||
count := 0
|
||||
endpoints, exists, err := client.GetEndpoints(ingress.Namespace, pa.Backend.ServiceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get endpoints %s/%s: %v", ingress.Namespace, pa.Backend.ServiceName, err)
|
||||
}
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("endpoints not found for %s/%s", ingress.Namespace, pa.Backend.ServiceName)
|
||||
}
|
||||
|
||||
for _, subset := range endpoints.Subsets {
|
||||
count += len(subset.Addresses)
|
||||
}
|
||||
|
||||
serviceInstanceCounts[ingressService{
|
||||
host: rule.Host,
|
||||
path: pa.Path,
|
||||
service: pa.Backend.ServiceName,
|
||||
}] += count
|
||||
}
|
||||
}
|
||||
|
||||
return serviceInstanceCounts, nil
|
||||
}
|
|
@ -1,477 +0,0 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
pv1 := newPercentageValueFromFloat64(0.5)
|
||||
pv2 := newPercentageValueFromFloat64(0.2)
|
||||
pv3 := newPercentageValueFromFloat64(0.3)
|
||||
f := fractionalWeightAllocator(
|
||||
map[ingressService]int{
|
||||
{
|
||||
host: "host2",
|
||||
path: "path2",
|
||||
service: "service2",
|
||||
}: int(pv2),
|
||||
{
|
||||
host: "host3",
|
||||
path: "path3",
|
||||
service: "service3",
|
||||
}: int(pv3),
|
||||
{
|
||||
host: "host1",
|
||||
path: "path1",
|
||||
service: "service1",
|
||||
}: int(pv1),
|
||||
},
|
||||
)
|
||||
|
||||
expected := fmt.Sprintf("[service1: %s service2: %s service3: %s]", pv1, pv2, pv3)
|
||||
actual := f.String()
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestGetServicesPercentageWeights(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
annotationValue string
|
||||
expectError bool
|
||||
expectedWeights map[string]percentageValue
|
||||
}{
|
||||
{
|
||||
desc: "empty annotation",
|
||||
annotationValue: ``,
|
||||
expectedWeights: map[string]percentageValue{},
|
||||
},
|
||||
{
|
||||
desc: "50% fraction",
|
||||
annotationValue: `
|
||||
service1: 10%
|
||||
service2: 20%
|
||||
service3: 20%
|
||||
`,
|
||||
expectedWeights: map[string]percentageValue{
|
||||
"service1": newPercentageValueFromFloat64(0.1),
|
||||
"service2": newPercentageValueFromFloat64(0.2),
|
||||
"service3": newPercentageValueFromFloat64(0.2),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "50% fraction with empty fraction",
|
||||
annotationValue: `
|
||||
service1: 10%
|
||||
service2: 20%
|
||||
service3: 20%
|
||||
service4:
|
||||
`,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
desc: "50% fraction float form",
|
||||
annotationValue: `
|
||||
service1: 0.1
|
||||
service2: 0.2
|
||||
service3: 0.2
|
||||
`,
|
||||
expectedWeights: map[string]percentageValue{
|
||||
"service1": newPercentageValueFromFloat64(0.001),
|
||||
"service2": newPercentageValueFromFloat64(0.002),
|
||||
"service3": newPercentageValueFromFloat64(0.002),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no fraction",
|
||||
annotationValue: `
|
||||
service1: 10%
|
||||
service2: 90%
|
||||
`,
|
||||
expectedWeights: map[string]percentageValue{
|
||||
"service1": newPercentageValueFromFloat64(0.1),
|
||||
"service2": newPercentageValueFromFloat64(0.9),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "extra weight specification",
|
||||
annotationValue: `
|
||||
service1: 90%
|
||||
service5: 90%
|
||||
`,
|
||||
expectedWeights: map[string]percentageValue{
|
||||
"service1": newPercentageValueFromFloat64(0.9),
|
||||
"service5": newPercentageValueFromFloat64(0.9),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "malformed annotation",
|
||||
annotationValue: `
|
||||
service1- 90%
|
||||
service5- 90%
|
||||
`,
|
||||
expectError: true,
|
||||
expectedWeights: nil,
|
||||
},
|
||||
{
|
||||
desc: "more than one hundred percentaged service",
|
||||
annotationValue: `
|
||||
service1: 100%
|
||||
service2: 1%
|
||||
`,
|
||||
expectedWeights: map[string]percentageValue{
|
||||
"service1": newPercentageValueFromFloat64(1),
|
||||
"service2": newPercentageValueFromFloat64(0.01),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "incorrect percentage value",
|
||||
annotationValue: `
|
||||
service1: 1000%
|
||||
`,
|
||||
expectedWeights: map[string]percentageValue{
|
||||
"service1": newPercentageValueFromFloat64(10),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ingress := &extensionsv1beta1.Ingress{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
annotationKubernetesServiceWeights: test.annotationValue,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
weights, err := getServicesPercentageWeights(ingress)
|
||||
|
||||
if test.expectError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedWeights, weights)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeServiceWeights(t *testing.T) {
|
||||
client := clientMock{
|
||||
services: []*corev1.Service{
|
||||
buildService(
|
||||
sName("service1"),
|
||||
sNamespace("testing"),
|
||||
),
|
||||
buildService(
|
||||
sName("service2"),
|
||||
sNamespace("testing"),
|
||||
),
|
||||
buildService(
|
||||
sName("service3"),
|
||||
sNamespace("testing"),
|
||||
),
|
||||
buildService(
|
||||
sName("service4"),
|
||||
sNamespace("testing"),
|
||||
),
|
||||
},
|
||||
endpoints: []*corev1.Endpoints{
|
||||
buildEndpoint(
|
||||
eNamespace("testing"),
|
||||
eName("service1"),
|
||||
eUID("1"),
|
||||
subset(
|
||||
eAddresses(eAddress("10.10.0.1")),
|
||||
ePorts(ePort(8080, ""))),
|
||||
subset(
|
||||
eAddresses(eAddress("10.21.0.2")),
|
||||
ePorts(ePort(8080, ""))),
|
||||
),
|
||||
buildEndpoint(
|
||||
eNamespace("testing"),
|
||||
eName("service2"),
|
||||
eUID("2"),
|
||||
subset(
|
||||
eAddresses(eAddress("10.10.0.3")),
|
||||
ePorts(ePort(8080, ""))),
|
||||
),
|
||||
buildEndpoint(
|
||||
eNamespace("testing"),
|
||||
eName("service3"),
|
||||
eUID("3"),
|
||||
subset(
|
||||
eAddresses(eAddress("10.10.0.4")),
|
||||
ePorts(ePort(8080, ""))),
|
||||
subset(
|
||||
eAddresses(eAddress("10.21.0.5")),
|
||||
ePorts(ePort(8080, ""))),
|
||||
subset(
|
||||
eAddresses(eAddress("10.21.0.6")),
|
||||
ePorts(ePort(8080, ""))),
|
||||
subset(
|
||||
eAddresses(eAddress("10.21.0.7")),
|
||||
ePorts(ePort(8080, ""))),
|
||||
),
|
||||
buildEndpoint(
|
||||
eNamespace("testing"),
|
||||
eName("service4"),
|
||||
eUID("4"),
|
||||
subset(
|
||||
eAddresses(eAddress("10.10.0.7")),
|
||||
ePorts(ePort(8080, ""))),
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
ingress *extensionsv1beta1.Ingress
|
||||
expectError bool
|
||||
expectedWeights map[ingressService]percentageValue
|
||||
}{
|
||||
{
|
||||
desc: "1 path 2 service",
|
||||
ingress: buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesServiceWeights, `
|
||||
service1: 10%
|
||||
`),
|
||||
iRules(
|
||||
iRule(iHost("foo.test"), iPaths(
|
||||
onePath(iPath("/foo"), iBackend("service1", intstr.FromInt(8080))),
|
||||
onePath(iPath("/foo"), iBackend("service2", intstr.FromInt(8080))),
|
||||
)),
|
||||
),
|
||||
),
|
||||
expectError: false,
|
||||
expectedWeights: map[ingressService]percentageValue{
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/foo",
|
||||
service: "service1",
|
||||
}: newPercentageValueFromFloat64(0.05),
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/foo",
|
||||
service: "service2",
|
||||
}: newPercentageValueFromFloat64(0.90),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 path 2 service",
|
||||
ingress: buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesServiceWeights, `
|
||||
service1: 60%
|
||||
`),
|
||||
iRules(
|
||||
iRule(iHost("foo.test"), iPaths(
|
||||
onePath(iPath("/foo"), iBackend("service1", intstr.FromInt(8080))),
|
||||
onePath(iPath("/foo"), iBackend("service2", intstr.FromInt(8080))),
|
||||
onePath(iPath("/bar"), iBackend("service1", intstr.FromInt(8080))),
|
||||
onePath(iPath("/bar"), iBackend("service3", intstr.FromInt(8080))),
|
||||
)),
|
||||
),
|
||||
),
|
||||
expectError: false,
|
||||
expectedWeights: map[ingressService]percentageValue{
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/foo",
|
||||
service: "service1",
|
||||
}: newPercentageValueFromFloat64(0.30),
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/foo",
|
||||
service: "service2",
|
||||
}: newPercentageValueFromFloat64(0.40),
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/bar",
|
||||
service: "service1",
|
||||
}: newPercentageValueFromFloat64(0.30),
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/bar",
|
||||
service: "service3",
|
||||
}: newPercentageValueFromFloat64(0.10),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 path 3 service",
|
||||
ingress: buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesServiceWeights, `
|
||||
service1: 20%
|
||||
service3: 20%
|
||||
`),
|
||||
iRules(
|
||||
iRule(iHost("foo.test"), iPaths(
|
||||
onePath(iPath("/foo"), iBackend("service1", intstr.FromInt(8080))),
|
||||
onePath(iPath("/foo"), iBackend("service2", intstr.FromInt(8080))),
|
||||
onePath(iPath("/bar"), iBackend("service2", intstr.FromInt(8080))),
|
||||
onePath(iPath("/bar"), iBackend("service3", intstr.FromInt(8080))),
|
||||
)),
|
||||
),
|
||||
),
|
||||
expectError: false,
|
||||
expectedWeights: map[ingressService]percentageValue{
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/foo",
|
||||
service: "service1",
|
||||
}: newPercentageValueFromFloat64(0.10),
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/foo",
|
||||
service: "service2",
|
||||
}: newPercentageValueFromFloat64(0.80),
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/bar",
|
||||
service: "service3",
|
||||
}: newPercentageValueFromFloat64(0.05),
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/bar",
|
||||
service: "service2",
|
||||
}: newPercentageValueFromFloat64(0.80),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "1 path 4 service",
|
||||
ingress: buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesServiceWeights, `
|
||||
service1: 20%
|
||||
service2: 40%
|
||||
service3: 40%
|
||||
`),
|
||||
iRules(
|
||||
iRule(iHost("foo.test"), iPaths(
|
||||
onePath(iPath("/foo"), iBackend("service1", intstr.FromInt(8080))),
|
||||
onePath(iPath("/foo"), iBackend("service2", intstr.FromInt(8080))),
|
||||
onePath(iPath("/foo"), iBackend("service3", intstr.FromInt(8080))),
|
||||
onePath(iPath("/foo"), iBackend("service4", intstr.FromInt(8080))),
|
||||
)),
|
||||
),
|
||||
),
|
||||
expectError: false,
|
||||
expectedWeights: map[ingressService]percentageValue{
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/foo",
|
||||
service: "service1",
|
||||
}: newPercentageValueFromFloat64(0.10),
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/foo",
|
||||
service: "service2",
|
||||
}: newPercentageValueFromFloat64(0.40),
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/foo",
|
||||
service: "service3",
|
||||
}: newPercentageValueFromFloat64(0.10),
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/foo",
|
||||
service: "service4",
|
||||
}: newPercentageValueFromFloat64(0.00),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 path no service",
|
||||
ingress: buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesServiceWeights, `
|
||||
service1: 20%
|
||||
service2: 40%
|
||||
service3: 40%
|
||||
`),
|
||||
iRules(
|
||||
iRule(iHost("foo.test"), iPaths(
|
||||
onePath(iPath("/foo"), iBackend("noservice", intstr.FromInt(8080))),
|
||||
onePath(iPath("/bar"), iBackend("noservice", intstr.FromInt(8080))),
|
||||
)),
|
||||
),
|
||||
),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
desc: "2 path without weight",
|
||||
ingress: buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesServiceWeights, ``),
|
||||
iRules(
|
||||
iRule(iHost("foo.test"), iPaths(
|
||||
onePath(iPath("/foo"), iBackend("service1", intstr.FromInt(8080))),
|
||||
onePath(iPath("/bar"), iBackend("service2", intstr.FromInt(8080))),
|
||||
)),
|
||||
),
|
||||
),
|
||||
expectError: false,
|
||||
expectedWeights: map[ingressService]percentageValue{
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/foo",
|
||||
service: "service1",
|
||||
}: newPercentageValueFromFloat64(0.50),
|
||||
{
|
||||
host: "foo.test",
|
||||
path: "/bar",
|
||||
service: "service2",
|
||||
}: newPercentageValueFromFloat64(1.00),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "2 path overflow",
|
||||
ingress: buildIngress(
|
||||
iNamespace("testing"),
|
||||
iAnnotation(annotationKubernetesServiceWeights, `
|
||||
service1: 70%
|
||||
service2: 80%
|
||||
`),
|
||||
iRules(
|
||||
iRule(iHost("foo.test"), iPaths(
|
||||
onePath(iPath("/foo"), iBackend("service1", intstr.FromInt(8080))),
|
||||
onePath(iPath("/foo"), iBackend("service2", intstr.FromInt(8080))),
|
||||
)),
|
||||
),
|
||||
),
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
weightAllocator, err := newFractionalWeightAllocator(test.ingress, client)
|
||||
if test.expectError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("%v failed: %v", test.desc, err)
|
||||
} else {
|
||||
for ingSvc, percentage := range test.expectedWeights {
|
||||
assert.Equal(t, int(percentage), weightAllocator.getWeight(ingSvc.host, ingSvc.path, ingSvc.service))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue