1
0
Fork 0

Custom resource definition

Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
This commit is contained in:
Ludovic Fernandez 2019-03-14 15:56:06 +01:00 committed by Traefiker Bot
parent cfaf47c8a2
commit 4c060a78cc
1348 changed files with 92364 additions and 55766 deletions

View file

@ -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)
}

View file

@ -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)
})
}
}

View file

@ -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"),
},
},
},
}
}

View file

@ -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,
},
},
},
},
}
}

View file

@ -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",
},
},
},
}
}

View file

@ -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,
},
},
},
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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)
}

View file

@ -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))
}

View file

@ -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)
})
}
}

View file

@ -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
}

View file

@ -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))
}
}
}
})
}
}