UDP support

Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
This commit is contained in:
mpl 2020-02-11 01:26:04 +01:00 committed by GitHub
parent 8988c8f9af
commit 115d42e0f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
72 changed files with 4730 additions and 321 deletions

View file

@ -75,5 +75,5 @@ func Do(staticConfiguration static.Configuration) (*http.Response, error) {
path := "/"
return client.Head(protocol + "://" + pingEntryPoint.Address + path + "ping")
return client.Head(protocol + "://" + pingEntryPoint.GetAddress() + path + "ping")
}

View file

@ -177,6 +177,11 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
return nil, err
}
serverEntryPointsUDP, err := server.NewUDPEntryPoints(staticConfiguration.EntryPoints)
if err != nil {
return nil, err
}
ctx := context.Background()
routinesPool := safe.NewPool(ctx)
@ -184,7 +189,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
accessLog := setupAccessLog(staticConfiguration.AccessLog)
chainBuilder := middleware.NewChainBuilder(*staticConfiguration, metricsRegistry, accessLog)
managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry)
tcpRouterFactory := server.NewTCPRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder)
routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder)
watcher := server.NewConfigurationWatcher(routinesPool, providerAggregator, time.Duration(staticConfiguration.Providers.ProvidersThrottleDuration))
@ -198,7 +203,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
metricsRegistry.LastConfigReloadSuccessGauge().Set(float64(time.Now().Unix()))
})
watcher.AddListener(switchRouter(tcpRouterFactory, acmeProviders, serverEntryPointsTCP))
watcher.AddListener(switchRouter(routerFactory, acmeProviders, serverEntryPointsTCP, serverEntryPointsUDP))
watcher.AddListener(func(conf dynamic.Configuration) {
if metricsRegistry.IsEpEnabled() || metricsRegistry.IsSvcEnabled() {
@ -229,12 +234,12 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
}
})
return server.NewServer(routinesPool, serverEntryPointsTCP, watcher, chainBuilder, accessLog), nil
return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, chainBuilder, accessLog), nil
}
func switchRouter(tcpRouterFactory *server.TCPRouterFactory, acmeProviders []*acme.Provider, serverEntryPointsTCP server.TCPEntryPoints) func(conf dynamic.Configuration) {
func switchRouter(routerFactory *server.RouterFactory, acmeProviders []*acme.Provider, serverEntryPointsTCP server.TCPEntryPoints, serverEntryPointsUDP server.UDPEntryPoints) func(conf dynamic.Configuration) {
return func(conf dynamic.Configuration) {
routers := tcpRouterFactory.CreateTCPRouters(conf)
routers, udpRouters := routerFactory.CreateRouters(conf)
for entryPointName, rt := range routers {
for _, p := range acmeProviders {
if p != nil && p.HTTPChallenge != nil && p.HTTPChallenge.EntryPoint == entryPointName {
@ -244,6 +249,7 @@ func switchRouter(tcpRouterFactory *server.TCPRouterFactory, acmeProviders []*ac
}
}
serverEntryPointsTCP.Switch(routers)
serverEntryPointsUDP.Switch(udpRouters)
}
}

View file

@ -81,6 +81,7 @@
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.notafter=true"
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.notbefore=true"
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.sans=true"
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.serialnumber=true"
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.commonname=true"
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.country=true"
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.domaincomponent=true"
@ -90,8 +91,8 @@
- "traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.serialnumber=true"
- "traefik.http.middlewares.middleware13.passtlsclientcert.pem=true"
- "traefik.http.middlewares.middleware14.ratelimit.average=42"
- "traefik.http.middlewares.middleware14.ratelimit.period=42"
- "traefik.http.middlewares.middleware14.ratelimit.burst=42"
- "traefik.http.middlewares.middleware14.ratelimit.period=42"
- "traefik.http.middlewares.middleware14.ratelimit.sourcecriterion.ipstrategy.depth=42"
- "traefik.http.middlewares.middleware14.ratelimit.sourcecriterion.ipstrategy.excludedips=foobar, foobar"
- "traefik.http.middlewares.middleware14.ratelimit.sourcecriterion.requestheadername=foobar"
@ -173,3 +174,8 @@
- "traefik.tcp.routers.tcprouter1.tls.passthrough=true"
- "traefik.tcp.services.tcpservice01.loadbalancer.terminationdelay=42"
- "traefik.tcp.services.tcpservice01.loadbalancer.server.port=foobar"
- "traefik.udp.routers.udprouter0.entrypoints=foobar, foobar"
- "traefik.udp.routers.udprouter0.service=foobar"
- "traefik.udp.routers.udprouter1.entrypoints=foobar, foobar"
- "traefik.udp.routers.udprouter1.service=foobar"
- "traefik.udp.services.udpservice01.loadbalancer.server.port=foobar"

View file

@ -311,6 +311,34 @@
name = "foobar"
weight = 42
[udp]
[udp.routers]
[udp.routers.UDPRouter0]
entryPoints = ["foobar", "foobar"]
service = "foobar"
[udp.routers.UDPRouter1]
entryPoints = ["foobar", "foobar"]
service = "foobar"
[udp.services]
[udp.services.UDPService01]
[udp.services.UDPService01.loadBalancer]
[[udp.services.UDPService01.loadBalancer.servers]]
address = "foobar"
[[udp.services.UDPService01.loadBalancer.servers]]
address = "foobar"
[udp.services.UDPService02]
[udp.services.UDPService02.weighted]
[[udp.services.UDPService02.weighted.services]]
name = "foobar"
weight = 42
[[udp.services.UDPService02.weighted.services]]
name = "foobar"
weight = 42
[tls]
[[tls.certificates]]

View file

@ -344,6 +344,31 @@ tcp:
weight: 42
- name: foobar
weight: 42
udp:
routers:
UDPRouter0:
entryPoints:
- foobar
- foobar
service: foobar
UDPRouter1:
entryPoints:
- foobar
- foobar
service: foobar
services:
UDPService01:
loadBalancer:
servers:
- address: foobar
- address: foobar
UDPService02:
weighted:
services:
- name: foobar
weight: 42
- name: foobar
weight: 42
tls:
certificates:
- certFile: foobar

View file

@ -13,117 +13,119 @@
| `traefik/http/middlewares/Middleware03/chain/middlewares/0` | `foobar` |
| `traefik/http/middlewares/Middleware03/chain/middlewares/1` | `foobar` |
| `traefik/http/middlewares/Middleware04/circuitBreaker/expression` | `foobar` |
| `traefik/http/middlewares/Middleware05/compress` | `` |
| `traefik/http/middlewares/Middleware06/digestAuth/headerField` | `foobar` |
| `traefik/http/middlewares/Middleware06/digestAuth/realm` | `foobar` |
| `traefik/http/middlewares/Middleware06/digestAuth/removeHeader` | `true` |
| `traefik/http/middlewares/Middleware06/digestAuth/users/0` | `foobar` |
| `traefik/http/middlewares/Middleware06/digestAuth/users/1` | `foobar` |
| `traefik/http/middlewares/Middleware06/digestAuth/usersFile` | `foobar` |
| `traefik/http/middlewares/Middleware07/errors/query` | `foobar` |
| `traefik/http/middlewares/Middleware07/errors/service` | `foobar` |
| `traefik/http/middlewares/Middleware07/errors/status/0` | `foobar` |
| `traefik/http/middlewares/Middleware07/errors/status/1` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/address` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/authResponseHeaders/0` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/authResponseHeaders/1` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/tls/ca` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/tls/caOptional` | `true` |
| `traefik/http/middlewares/Middleware08/forwardAuth/tls/cert` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/tls/insecureSkipVerify` | `true` |
| `traefik/http/middlewares/Middleware08/forwardAuth/tls/key` | `foobar` |
| `traefik/http/middlewares/Middleware08/forwardAuth/trustForwardHeader` | `true` |
| `traefik/http/middlewares/Middleware09/headers/accessControlAllowCredentials` | `true` |
| `traefik/http/middlewares/Middleware09/headers/accessControlAllowHeaders/0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlAllowHeaders/1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlAllowMethods/0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlAllowMethods/1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlAllowOrigin` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlExposeHeaders/1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/accessControlMaxAge` | `42` |
| `traefik/http/middlewares/Middleware09/headers/addVaryHeader` | `true` |
| `traefik/http/middlewares/Middleware09/headers/allowedHosts/0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/allowedHosts/1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/browserXssFilter` | `true` |
| `traefik/http/middlewares/Middleware09/headers/contentSecurityPolicy` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/contentTypeNosniff` | `true` |
| `traefik/http/middlewares/Middleware09/headers/customBrowserXSSValue` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/customFrameOptionsValue` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/customRequestHeaders/name1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/customResponseHeaders/name0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/customResponseHeaders/name1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/featurePolicy` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/forceSTSHeader` | `true` |
| `traefik/http/middlewares/Middleware09/headers/frameDeny` | `true` |
| `traefik/http/middlewares/Middleware09/headers/hostsProxyHeaders/0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/hostsProxyHeaders/1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/isDevelopment` | `true` |
| `traefik/http/middlewares/Middleware09/headers/publicKey` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/referrerPolicy` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/sslForceHost` | `true` |
| `traefik/http/middlewares/Middleware09/headers/sslHost` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/sslProxyHeaders/name0` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/sslProxyHeaders/name1` | `foobar` |
| `traefik/http/middlewares/Middleware09/headers/sslRedirect` | `true` |
| `traefik/http/middlewares/Middleware09/headers/sslTemporaryRedirect` | `true` |
| `traefik/http/middlewares/Middleware09/headers/stsIncludeSubdomains` | `true` |
| `traefik/http/middlewares/Middleware09/headers/stsPreload` | `true` |
| `traefik/http/middlewares/Middleware09/headers/stsSeconds` | `42` |
| `traefik/http/middlewares/Middleware10/ipWhiteList/ipStrategy/depth` | `42` |
| `traefik/http/middlewares/Middleware10/ipWhiteList/ipStrategy/excludedIPs/0` | `foobar` |
| `traefik/http/middlewares/Middleware10/ipWhiteList/ipStrategy/excludedIPs/1` | `foobar` |
| `traefik/http/middlewares/Middleware10/ipWhiteList/sourceRange/0` | `foobar` |
| `traefik/http/middlewares/Middleware10/ipWhiteList/sourceRange/1` | `foobar` |
| `traefik/http/middlewares/Middleware11/inFlightReq/amount` | `42` |
| `traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/ipStrategy/depth` | `42` |
| `traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` |
| `traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` |
| `traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/requestHeaderName` | `foobar` |
| `traefik/http/middlewares/Middleware11/inFlightReq/sourceCriterion/requestHost` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/commonName` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/country` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/domainComponent` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/locality` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/organization` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/province` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/issuer/serialNumber` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/notAfter` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/notBefore` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/sans` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/serialNumber` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/commonName` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/country` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/domainComponent` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/locality` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/organization` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/province` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/info/subject/serialNumber` | `true` |
| `traefik/http/middlewares/Middleware12/passTLSClientCert/pem` | `true` |
| `traefik/http/middlewares/Middleware13/rateLimit/average` | `42` |
| `traefik/http/middlewares/Middleware13/rateLimit/period` | `42` |
| `traefik/http/middlewares/Middleware13/rateLimit/burst` | `42` |
| `traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/depth` | `42` |
| `traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` |
| `traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` |
| `traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/requestHeaderName` | `foobar` |
| `traefik/http/middlewares/Middleware13/rateLimit/sourceCriterion/requestHost` | `true` |
| `traefik/http/middlewares/Middleware14/redirectRegex/permanent` | `true` |
| `traefik/http/middlewares/Middleware14/redirectRegex/regex` | `foobar` |
| `traefik/http/middlewares/Middleware14/redirectRegex/replacement` | `foobar` |
| `traefik/http/middlewares/Middleware15/redirectScheme/permanent` | `true` |
| `traefik/http/middlewares/Middleware15/redirectScheme/port` | `foobar` |
| `traefik/http/middlewares/Middleware15/redirectScheme/scheme` | `foobar` |
| `traefik/http/middlewares/Middleware16/replacePath/path` | `foobar` |
| `traefik/http/middlewares/Middleware17/replacePathRegex/regex` | `foobar` |
| `traefik/http/middlewares/Middleware17/replacePathRegex/replacement` | `foobar` |
| `traefik/http/middlewares/Middleware18/retry/attempts` | `42` |
| `traefik/http/middlewares/Middleware19/stripPrefix/forceSlash` | `true` |
| `traefik/http/middlewares/Middleware19/stripPrefix/prefixes/0` | `foobar` |
| `traefik/http/middlewares/Middleware19/stripPrefix/prefixes/1` | `foobar` |
| `traefik/http/middlewares/Middleware20/stripPrefixRegex/regex/0` | `foobar` |
| `traefik/http/middlewares/Middleware20/stripPrefixRegex/regex/1` | `foobar` |
| `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/0` | `foobar` |
| `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/1` | `foobar` |
| `traefik/http/middlewares/Middleware06/contentType/autoDetect` | `true` |
| `traefik/http/middlewares/Middleware07/digestAuth/headerField` | `foobar` |
| `traefik/http/middlewares/Middleware07/digestAuth/realm` | `foobar` |
| `traefik/http/middlewares/Middleware07/digestAuth/removeHeader` | `true` |
| `traefik/http/middlewares/Middleware07/digestAuth/users/0` | `foobar` |
| `traefik/http/middlewares/Middleware07/digestAuth/users/1` | `foobar` |
| `traefik/http/middlewares/Middleware07/digestAuth/usersFile` | `foobar` |
| `traefik/http/middlewares/Middleware08/errors/query` | `foobar` |
| `traefik/http/middlewares/Middleware08/errors/service` | `foobar` |
| `traefik/http/middlewares/Middleware08/errors/status/0` | `foobar` |
| `traefik/http/middlewares/Middleware08/errors/status/1` | `foobar` |
| `traefik/http/middlewares/Middleware09/forwardAuth/address` | `foobar` |
| `traefik/http/middlewares/Middleware09/forwardAuth/authResponseHeaders/0` | `foobar` |
| `traefik/http/middlewares/Middleware09/forwardAuth/authResponseHeaders/1` | `foobar` |
| `traefik/http/middlewares/Middleware09/forwardAuth/tls/ca` | `foobar` |
| `traefik/http/middlewares/Middleware09/forwardAuth/tls/caOptional` | `true` |
| `traefik/http/middlewares/Middleware09/forwardAuth/tls/cert` | `foobar` |
| `traefik/http/middlewares/Middleware09/forwardAuth/tls/insecureSkipVerify` | `true` |
| `traefik/http/middlewares/Middleware09/forwardAuth/tls/key` | `foobar` |
| `traefik/http/middlewares/Middleware09/forwardAuth/trustForwardHeader` | `true` |
| `traefik/http/middlewares/Middleware10/headers/accessControlAllowCredentials` | `true` |
| `traefik/http/middlewares/Middleware10/headers/accessControlAllowHeaders/0` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/accessControlAllowHeaders/1` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/accessControlAllowMethods/0` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/accessControlAllowMethods/1` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/accessControlAllowOrigin` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/accessControlExposeHeaders/0` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/accessControlExposeHeaders/1` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/accessControlMaxAge` | `42` |
| `traefik/http/middlewares/Middleware10/headers/addVaryHeader` | `true` |
| `traefik/http/middlewares/Middleware10/headers/allowedHosts/0` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/allowedHosts/1` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/browserXssFilter` | `true` |
| `traefik/http/middlewares/Middleware10/headers/contentSecurityPolicy` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/contentTypeNosniff` | `true` |
| `traefik/http/middlewares/Middleware10/headers/customBrowserXSSValue` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/customFrameOptionsValue` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/customRequestHeaders/name0` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/customRequestHeaders/name1` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/customResponseHeaders/name0` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/customResponseHeaders/name1` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/featurePolicy` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/forceSTSHeader` | `true` |
| `traefik/http/middlewares/Middleware10/headers/frameDeny` | `true` |
| `traefik/http/middlewares/Middleware10/headers/hostsProxyHeaders/0` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/hostsProxyHeaders/1` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/isDevelopment` | `true` |
| `traefik/http/middlewares/Middleware10/headers/publicKey` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/referrerPolicy` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/sslForceHost` | `true` |
| `traefik/http/middlewares/Middleware10/headers/sslHost` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/sslProxyHeaders/name0` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/sslProxyHeaders/name1` | `foobar` |
| `traefik/http/middlewares/Middleware10/headers/sslRedirect` | `true` |
| `traefik/http/middlewares/Middleware10/headers/sslTemporaryRedirect` | `true` |
| `traefik/http/middlewares/Middleware10/headers/stsIncludeSubdomains` | `true` |
| `traefik/http/middlewares/Middleware10/headers/stsPreload` | `true` |
| `traefik/http/middlewares/Middleware10/headers/stsSeconds` | `42` |
| `traefik/http/middlewares/Middleware11/ipWhiteList/ipStrategy/depth` | `42` |
| `traefik/http/middlewares/Middleware11/ipWhiteList/ipStrategy/excludedIPs/0` | `foobar` |
| `traefik/http/middlewares/Middleware11/ipWhiteList/ipStrategy/excludedIPs/1` | `foobar` |
| `traefik/http/middlewares/Middleware11/ipWhiteList/sourceRange/0` | `foobar` |
| `traefik/http/middlewares/Middleware11/ipWhiteList/sourceRange/1` | `foobar` |
| `traefik/http/middlewares/Middleware12/inFlightReq/amount` | `42` |
| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/ipStrategy/depth` | `42` |
| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` |
| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` |
| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/requestHeaderName` | `foobar` |
| `traefik/http/middlewares/Middleware12/inFlightReq/sourceCriterion/requestHost` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/commonName` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/country` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/domainComponent` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/locality` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/organization` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/province` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/issuer/serialNumber` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/notAfter` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/notBefore` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/sans` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/serialNumber` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/commonName` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/country` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/domainComponent` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/locality` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/organization` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/province` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/info/subject/serialNumber` | `true` |
| `traefik/http/middlewares/Middleware13/passTLSClientCert/pem` | `true` |
| `traefik/http/middlewares/Middleware14/rateLimit/average` | `42` |
| `traefik/http/middlewares/Middleware14/rateLimit/burst` | `42` |
| `traefik/http/middlewares/Middleware14/rateLimit/period` | `42` |
| `traefik/http/middlewares/Middleware14/rateLimit/sourceCriterion/ipStrategy/depth` | `42` |
| `traefik/http/middlewares/Middleware14/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` |
| `traefik/http/middlewares/Middleware14/rateLimit/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` |
| `traefik/http/middlewares/Middleware14/rateLimit/sourceCriterion/requestHeaderName` | `foobar` |
| `traefik/http/middlewares/Middleware14/rateLimit/sourceCriterion/requestHost` | `true` |
| `traefik/http/middlewares/Middleware15/redirectRegex/permanent` | `true` |
| `traefik/http/middlewares/Middleware15/redirectRegex/regex` | `foobar` |
| `traefik/http/middlewares/Middleware15/redirectRegex/replacement` | `foobar` |
| `traefik/http/middlewares/Middleware16/redirectScheme/permanent` | `true` |
| `traefik/http/middlewares/Middleware16/redirectScheme/port` | `foobar` |
| `traefik/http/middlewares/Middleware16/redirectScheme/scheme` | `foobar` |
| `traefik/http/middlewares/Middleware17/replacePath/path` | `foobar` |
| `traefik/http/middlewares/Middleware18/replacePathRegex/regex` | `foobar` |
| `traefik/http/middlewares/Middleware18/replacePathRegex/replacement` | `foobar` |
| `traefik/http/middlewares/Middleware19/retry/attempts` | `42` |
| `traefik/http/middlewares/Middleware20/stripPrefix/forceSlash` | `true` |
| `traefik/http/middlewares/Middleware20/stripPrefix/prefixes/0` | `foobar` |
| `traefik/http/middlewares/Middleware20/stripPrefix/prefixes/1` | `foobar` |
| `traefik/http/middlewares/Middleware21/stripPrefixRegex/regex/0` | `foobar` |
| `traefik/http/middlewares/Middleware21/stripPrefixRegex/regex/1` | `foobar` |
| `traefik/http/routers/Router0/entryPoints/0` | `foobar` |
| `traefik/http/routers/Router0/entryPoints/1` | `foobar` |
| `traefik/http/routers/Router0/middlewares/0` | `foobar` |
@ -246,3 +248,15 @@
| `traefik/tls/stores/Store0/defaultCertificate/keyFile` | `foobar` |
| `traefik/tls/stores/Store1/defaultCertificate/certFile` | `foobar` |
| `traefik/tls/stores/Store1/defaultCertificate/keyFile` | `foobar` |
| `traefik/udp/routers/UDPRouter0/entryPoints/0` | `foobar` |
| `traefik/udp/routers/UDPRouter0/entryPoints/1` | `foobar` |
| `traefik/udp/routers/UDPRouter0/service` | `foobar` |
| `traefik/udp/routers/UDPRouter1/entryPoints/0` | `foobar` |
| `traefik/udp/routers/UDPRouter1/entryPoints/1` | `foobar` |
| `traefik/udp/routers/UDPRouter1/service` | `foobar` |
| `traefik/udp/services/UDPService01/loadBalancer/servers/0/address` | `foobar` |
| `traefik/udp/services/UDPService01/loadBalancer/servers/1/address` | `foobar` |
| `traefik/udp/services/UDPService02/weighted/services/0/name` | `foobar` |
| `traefik/udp/services/UDPService02/weighted/services/0/weight` | `42` |
| `traefik/udp/services/UDPService02/weighted/services/1/name` | `foobar` |
| `traefik/udp/services/UDPService02/weighted/services/1/weight` | `42` |

View file

@ -81,6 +81,7 @@
"traefik.http.middlewares.middleware13.passtlsclientcert.info.notafter": "true",
"traefik.http.middlewares.middleware13.passtlsclientcert.info.notbefore": "true",
"traefik.http.middlewares.middleware13.passtlsclientcert.info.sans": "true",
"traefik.http.middlewares.middleware13.passtlsclientcert.info.serialnumber": "true",
"traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.commonname": "true",
"traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.country": "true",
"traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.domaincomponent": "true",
@ -90,8 +91,8 @@
"traefik.http.middlewares.middleware13.passtlsclientcert.info.subject.serialnumber": "true",
"traefik.http.middlewares.middleware13.passtlsclientcert.pem": "true",
"traefik.http.middlewares.middleware14.ratelimit.average": "42",
"traefik.http.middlewares.middleware13.ratelimit.period": "42",
"traefik.http.middlewares.middleware14.ratelimit.burst": "42",
"traefik.http.middlewares.middleware14.ratelimit.period": "42",
"traefik.http.middlewares.middleware14.ratelimit.sourcecriterion.ipstrategy.depth": "42",
"traefik.http.middlewares.middleware14.ratelimit.sourcecriterion.ipstrategy.excludedips": "foobar, foobar",
"traefik.http.middlewares.middleware14.ratelimit.sourcecriterion.requestheadername": "foobar",
@ -168,3 +169,8 @@
"traefik.tcp.routers.tcprouter1.tls.passthrough": "true",
"traefik.tcp.services.tcpservice01.loadbalancer.terminationdelay": "42",
"traefik.tcp.services.tcpservice01.loadbalancer.server.port": "foobar",
"traefik.udp.routers.udprouter0.entrypoints": "foobar, foobar",
"traefik.udp.routers.udprouter0.service": "foobar",
"traefik.udp.routers.udprouter1.entrypoints": "foobar, foobar",
"traefik.udp.routers.udprouter1.service": "foobar",
"traefik.udp.services.udpservice01.loadbalancer.server.port": "foobar",

View file

@ -6,7 +6,8 @@ Opening Connections for Incoming Requests
![entryPoints](../assets/img/entrypoints.png)
EntryPoints are the network entry points into Traefik.
They define the port which will receive the requests (whether HTTP or TCP).
They define the port which will receive the packets,
and whether to listen for TCP or UDP.
## Configuration Examples
@ -64,6 +65,27 @@ They define the port which will receive the requests (whether HTTP or TCP).
- Two entrypoints are defined: one called `web`, and the other called `websecure`.
- `web` listens on port `80`, and `websecure` on port `443`.
??? example "UDP on port 1704"
```toml tab="File (TOML)"
## Static configuration
[entryPoints]
[entryPoints.streaming]
address = ":1704/udp"
```
```yaml tab="File (YAML)"
## Static configuration
entryPoints:
streaming:
address: ":1704/udp"
```
```bash tab="CLI"
## Static configuration
--entryPoints.streaming.address=:1704/udp
```
## Configuration
### General
@ -77,7 +99,7 @@ You can define them using a toml file, CLI arguments, or a key-value store.
## Static configuration
[entryPoints]
[entryPoints.name]
address = ":8888"
address = ":8888" # same as ":8888/tcp"
[entryPoints.name.transport]
[entryPoints.name.transport.lifeCycle]
requestAcceptGraceTimeout = 42
@ -98,7 +120,7 @@ You can define them using a toml file, CLI arguments, or a key-value store.
## Static configuration
entryPoints:
name:
address: ":8888"
address: ":8888" # same as ":8888/tcp"
transport:
lifeCycle:
requestAcceptGraceTimeout: 42
@ -121,7 +143,7 @@ You can define them using a toml file, CLI arguments, or a key-value store.
```bash tab="CLI"
## Static configuration
--entryPoints.name.address=:8888
--entryPoints.name.address=:8888 # same as :8888/tcp
--entryPoints.name.transport.lifeCycle.requestAcceptGraceTimeout=42
--entryPoints.name.transport.lifeCycle.graceTimeOut=42
--entryPoints.name.transport.respondingTimeouts.readTimeout=42
@ -133,6 +155,45 @@ You can define them using a toml file, CLI arguments, or a key-value store.
--entryPoints.name.forwardedHeaders.trustedIPs=127.0.0.1,192.168.0.1
```
### Address
The address defines the port, and optionally the hostname, on which to listen for incoming connections and packets.
It also defines the protocol to use (TCP or UDP).
If no protocol is specified, the default is TCP.
The format is:
```bash
[host]:port[/tcp|/udp]
```
If both TCP and UDP are wanted for the same port, two entryPoints definitions are needed, such as in the example below.
??? example "Both TCP and UDP on port 3179"
```toml tab="File (TOML)"
## Static configuration
[entryPoints]
[entryPoints.tcpep]
address = ":3179"
[entryPoints.udpep]
address = ":3179/udp"
```
```yaml tab="File (YAML)"
## Static configuration
entryPoints:
tcpep:
address: ":3179"
udpep:
address: ":3179/udp"
```
```bash tab="CLI"
## Static configuration
--entryPoints.tcpep.address=:3179
--entryPoints.udpep.address=:3179/udp
```
### Forwarded Headers
You can configure Traefik to trust the forwarded headers information (`X-Forwarded-*`).
@ -202,6 +263,7 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
#### `respondingTimeouts`
`respondingTimeouts` are timeouts for incoming requests to the Traefik instance.
Setting them has no effect for UDP entryPoints.
??? info "`transport.respondingTimeouts.readTimeout`"

View file

@ -6,7 +6,8 @@ Connecting Requests to Services
![routers](../../assets/img/routers.png)
A router is in charge of connecting incoming requests to the services that can handle them.
In the process, routers may use pieces of [middleware](../../middlewares/overview.md) to update the request, or act before forwarding the request to the service.
In the process, routers may use pieces of [middleware](../../middlewares/overview.md) to update the request,
or act before forwarding the request to the service.
## Configuration Example
@ -792,9 +793,11 @@ Services are the target for the router.
#### General
When a TLS section is specified, it instructs Traefik that the current router is dedicated to TLS requests only (and that the router should ignore non-TLS requests).
When a TLS section is specified,
it instructs Traefik that the current router is dedicated to TLS requests only (and that the router should ignore non-TLS requests).
By default, Traefik will terminate the SSL connections (meaning that it will send decrypted data to the services), but Traefik can be configured in order to let the requests pass through (keeping the data encrypted), and be forwarded to the service "as is".
By default, Traefik will terminate the SSL connections (meaning that it will send decrypted data to the services),
but Traefik can be configured in order to let the requests pass through (keeping the data encrypted), and be forwarded to the service "as is".
??? example "Configuring TLS Termination"
@ -946,3 +949,157 @@ tcp:
sans:
- "*.snitest.com"
```
## Configuring UDP Routers
!!! warning "The character `@` is not allowed in the router name"
### General
Similarly to TCP, as UDP is the transport layer, there is no concept of a request,
so there is no notion of an URL path prefix to match an incoming UDP packet with.
Furthermore, as there is no good TLS support at the moment for multiple hosts,
there is no Host SNI notion to match against either.
Therefore, there is no criterion that could be used as a rule to match incoming packets in order to route them.
So UDP "routers" at this time are pretty much only load-balancers in one form or another.
!!! important "Sessions and timeout"
Even though UDP is connectionless (and because of that),
the implementation of an UDP router in Traefik relies on what we (and a couple of other implementations) call a `session`.
It basically means that some state is kept about an ongoing communication between a client and a backend,
notably so that the proxy knows where to forward a response packet from a backend.
As expected, a `timeout` is associated to each of these sessions,
so that they get cleaned out if they go through a period of inactivity longer than a given duration (that is hardcoded to 3 seconds for now).
Making this timeout configurable will be considered later if we get more usage feedback on this matter.
### EntryPoints
If not specified, UDP routers will accept packets from all defined (UDP) entry points.
If one wants to limit the router scope to a set of entry points, one should set the entry points option.
??? example "Listens to Every Entry Point"
**Dynamic Configuration**
```toml tab="File (TOML)"
## Dynamic configuration
[udp.routers]
[udp.routers.Router-1]
# By default, routers listen to all UDP entrypoints,
# i.e. "other", and "streaming".
service = "service-1"
```
```yaml tab="File (YAML)"
## Dynamic configuration
udp:
routers:
Router-1:
# By default, routers listen to all UDP entrypoints
# i.e. "other", and "streaming".
service: "service-1"
```
**Static Configuration**
```toml tab="File (TOML)"
## Static configuration
[entryPoints]
# not used by UDP routers
[entryPoints.web]
address = ":80"
# used by UDP routers
[entryPoints.other]
address = ":9090/udp"
[entryPoints.streaming]
address = ":9191/udp"
```
```yaml tab="File (YAML)"
## Static configuration
entryPoints:
# not used by UDP routers
web:
address: ":80"
# used by UDP routers
other:
address: ":9090/udp"
streaming:
address: ":9191/udp"
```
```bash tab="CLI"
## Static configuration
--entrypoints.web.address=":80"
--entrypoints.other.address=":9090/udp"
--entrypoints.streaming.address=":9191/udp"
```
??? example "Listens to Specific Entry Points"
**Dynamic Configuration**
```toml tab="File (TOML)"
## Dynamic configuration
[udp.routers]
[udp.routers.Router-1]
# does not listen on "other" entry point
entryPoints = ["streaming"]
service = "service-1"
```
```yaml tab="File (YAML)"
## Dynamic configuration
udp:
routers:
Router-1:
# does not listen on "other" entry point
entryPoints:
- "streaming"
service: "service-1"
```
**Static Configuration**
```toml tab="File (TOML)"
## Static configuration
[entryPoints]
[entryPoints.web]
address = ":80"
[entryPoints.other]
address = ":9090/udp"
[entryPoints.streaming]
address = ":9191/udp"
```
```yaml tab="File (YAML)"
## Static configuration
entryPoints:
web:
address: ":80"
other:
address: ":9090/udp"
streaming:
address: ":9191/udp"
```
```bash tab="CLI"
## Static configuration
--entrypoints.web.address=":80"
--entrypoints.other.address=":9090/udp"
--entrypoints.streaming.address=":9191/udp"
```
### Services
There must be one (and only one) UDP [service](../services/index.md) referenced per UDP router.
Services are the target for the router.
!!! important "UDP routers can only target UDP services (and not HTTP or TCP services)."

View file

@ -55,6 +55,28 @@ The `Services` are responsible for configuring how to reach the actual services
- address: "<private-ip-server-2>:<private-port-server-2>"
```
??? example "Declaring a UDP Service with Two Servers -- Using the [File Provider](../../providers/file.md)"
```toml tab="TOML"
## Dynamic configuration
[udp.services]
[udp.services.my-service.loadBalancer]
[[udp.services.my-service.loadBalancer.servers]]
address = "<private-ip-server-1>:<private-port-server-1>"
[[udp.services.my-service.loadBalancer.servers]]
address = "<private-ip-server-2>:<private-port-server-2>"
```
```yaml tab="YAML"
udp:
services:
my-service:
loadBalancer:
servers:
- address: "<private-ip-server-1>:<private-port-server-1>"
- address: "<private-ip-server-2>:<private-port-server-2>"
```
## Configuring HTTP Services
### Servers Load Balancer
@ -635,3 +657,117 @@ tcp:
servers:
- address: "xxx.xxx.xxx.xxx:8080"
```
## Configuring UDP Services
### General
Each of the fields of the service section represents a kind of service.
Which means, that for each specified service, one of the fields, and only one,
has to be enabled to define what kind of service is created.
Currently, the two available kinds are `LoadBalancer`, and `Weighted`.
### Servers Load Balancer
The servers load balancer is in charge of balancing the requests between the servers of the same service.
??? example "Declaring a Service with Two Servers -- Using the [File Provider](../../providers/file.md)"
```toml tab="TOML"
## Dynamic configuration
[udp.services]
[udp.services.my-service.loadBalancer]
[[udp.services.my-service.loadBalancer.servers]]
address = "xx.xx.xx.xx:xx"
[[udp.services.my-service.loadBalancer.servers]]
address = "xx.xx.xx.xx:xx"
```
```yaml tab="YAML"
## Dynamic configuration
udp:
services:
my-service:
loadBalancer:
servers:
- address: "xx.xx.xx.xx:xx"
- address: "xx.xx.xx.xx:xx"
```
#### Servers
The Servers field defines all the servers that are part of this load-balancing group,
i.e. each address (IP:Port) on which an instance of the service's program is deployed.
??? example "A Service with One Server -- Using the [File Provider](../../providers/file.md)"
```toml tab="TOML"
## Dynamic configuration
[udp.services]
[udp.services.my-service.loadBalancer]
[[udp.services.my-service.loadBalancer.servers]]
address = "xx.xx.xx.xx:xx"
```
```yaml tab="YAML"
## Dynamic configuration
udp:
services:
my-service:
loadBalancer:
servers:
- address: "xx.xx.xx.xx:xx"
```
### Weighted Round Robin
The Weighted Round Robin (alias `WRR`) load-balancer of services is in charge of balancing the requests between multiple services based on provided weights.
This strategy is only available to load balance between [services](./index.md) and not between [servers](./index.md#servers).
This strategy can only be defined with [File](../../providers/file.md).
```toml tab="TOML"
## Dynamic configuration
[udp.services]
[udp.services.app]
[[udp.services.app.weighted.services]]
name = "appv1"
weight = 3
[[udp.services.app.weighted.services]]
name = "appv2"
weight = 1
[udp.services.appv1]
[udp.services.appv1.loadBalancer]
[[udp.services.appv1.loadBalancer.servers]]
address = "private-ip-server-1:8080/"
[udp.services.appv2]
[udp.services.appv2.loadBalancer]
[[udp.services.appv2.loadBalancer.servers]]
address = "private-ip-server-2:8080/"
```
```yaml tab="YAML"
## Dynamic configuration
udp:
services:
app:
weighted:
services:
- name: appv1
weight: 3
- name: appv2
weight: 1
appv1:
loadBalancer:
servers:
- address: "xxx.xxx.xxx.xxx:8080"
appv2:
loadBalancer:
servers:
- address: "xxx.xxx.xxx.xxx:8080"
```

View file

@ -49,3 +49,30 @@
[tls.options.baz]
minversion = "VersionTLS11"
[tcp.routers]
[tcp.routers.router3]
entrypoints=["unknown-entrypoint"]
service = "service1"
rule = "HostSNI(`mydomain.com`)"
[tcp.routers.router4]
entrypoints=["websecure"]
service = "service1"
rule = "Host(`mydomain.com`)"
[tcp.services]
[tcp.services.service1]
[tcp.services.service1.loadBalancer]
[[tcp.services.service1.loadBalancer.servers]]
address = "127.0.0.1:9010"
[udp.routers]
[udp.routers.router3]
entrypoints=["unknown-entrypoint"]
service = "service1"
[udp.services]
[udp.services.service1]
[udp.services.service1.loadBalancer]
[[udp.services.service1.loadBalancer.servers]]
address = "127.0.0.1:9010"

View file

@ -8,6 +8,8 @@
[entryPoints]
[entryPoints.websecure]
address = ":4443"
[entryPoints.udp]
address = ":4443/udp"
[api]
insecure = true
@ -33,3 +35,35 @@
[http.services.service2.loadBalancer]
[[http.services.service2.loadBalancer.servers]]
url = "http://127.0.0.1:9010"
[tcp.routers]
[tcp.routers.router4]
service = "service1"
rule = "HostSNI(`snitest.net`)"
[tcp.routers.router5]
service = "service2"
rule = "HostSNI(`snitest.com`)"
[tcp.services]
[tcp.services.service1]
[tcp.services.service2]
[tcp.services.service2.loadBalancer]
[[tcp.services.service2.loadBalancer.servers]]
address = "127.0.0.1:9010"
[udp.routers]
[udp.routers.router4]
service = "service1"
[udp.routers.router5]
service = "service2"
[udp.services]
[udp.services.service1]
[udp.services.service2]
[udp.services.service2.loadBalancer]
[[udp.services.service2.loadBalancer.servers]]
address = "127.0.0.1:9010"

View file

@ -0,0 +1,53 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.udp]
address = ":8093/udp"
[entryPoints.web]
address = ":8093"
[api]
insecure = true
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[udp]
[udp.routers]
[udp.routers.to-whoami-a]
service = "whoami"
entryPoints = [ "udp" ]
[[udp.services.whoami.weighted.services]]
name="whoami-a"
weight=3
[[udp.services.whoami.weighted.services]]
name="whoami-b"
weight=1
[udp.services.whoami-a.loadBalancer]
[[udp.services.whoami-a.loadBalancer.servers]]
address = "{{ .WhoamiAIP}}:8080"
[[udp.services.whoami-a.loadBalancer.servers]]
address = "{{ .WhoamiCIP}}:8080"
[udp.services.whoami-b.loadBalancer]
[[udp.services.whoami-b.loadBalancer.servers]]
address = "{{ .WhoamiBIP}}:8080"
[http]
[http.routers]
[http.routers.to-whoami-d]
service = "whoami"
entryPoints = [ "web" ]
rule = "PathPrefix(`/who`)"
[http.services.whoami.loadBalancer]
[[http.services.whoami.loadBalancer.servers]]
url = "http://{{ .WhoamiDIP}}"

View file

@ -60,6 +60,7 @@ func Test(t *testing.T) {
check.Suite(&TimeoutSuite{})
check.Suite(&TLSClientHeadersSuite{})
check.Suite(&TracingSuite{})
check.Suite(&UDPSuite{})
check.Suite(&WebsocketSuite{})
check.Suite(&ZookeeperSuite{})
}

View file

@ -0,0 +1,14 @@
whoami-a:
image: containous/whoamiudp:dev
command: -name whoami-a
whoami-b:
image: containous/whoamiudp:dev
command: -name whoami-b
whoami-c:
image: containous/whoamiudp:dev
command: -name whoami-c
whoami-d:
image: containous/whoami

View file

@ -549,6 +549,83 @@ func (s *SimpleSuite) TestServiceConfigErrors(c *check.C) {
c.Assert(err, checker.IsNil)
}
func (s *SimpleSuite) TestTCPRouterConfigErrors(c *check.C) {
file := s.adaptFile(c, "fixtures/router_errors.toml", struct{}{})
defer os.Remove(file)
cmd, output := s.traefikCmd(withConfigFile(file))
defer output(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// router3 has an error because it uses an unknown entrypoint
err = try.GetRequest("http://127.0.0.1:8080/api/tcp/routers/router3@file", 1000*time.Millisecond, try.BodyContains(`entryPoint \"unknown-entrypoint\" doesn't exist`, "no valid entryPoint for this router"))
c.Assert(err, checker.IsNil)
// router4 has an unsupported Rule
err = try.GetRequest("http://127.0.0.1:8080/api/tcp/routers/router4@file", 1000*time.Millisecond, try.BodyContains("unknown rule Host(`mydomain.com`)"))
c.Assert(err, checker.IsNil)
}
func (s *SimpleSuite) TestTCPServiceConfigErrors(c *check.C) {
file := s.adaptFile(c, "fixtures/service_errors.toml", struct{}{})
defer os.Remove(file)
cmd, output := s.traefikCmd(withConfigFile(file))
defer output(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/tcp/services", 1000*time.Millisecond, try.BodyContains(`["the service \"service1@file\" does not have any type defined"]`))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8080/api/tcp/services/service1@file", 1000*time.Millisecond, try.BodyContains(`"status":"disabled"`))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8080/api/tcp/services/service2@file", 1000*time.Millisecond, try.BodyContains(`"status":"enabled"`))
c.Assert(err, checker.IsNil)
}
func (s *SimpleSuite) TestUDPRouterConfigErrors(c *check.C) {
file := s.adaptFile(c, "fixtures/router_errors.toml", struct{}{})
defer os.Remove(file)
cmd, output := s.traefikCmd(withConfigFile(file))
defer output(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/udp/routers/router3@file", 1000*time.Millisecond, try.BodyContains(`entryPoint \"unknown-entrypoint\" doesn't exist`, "no valid entryPoint for this router"))
c.Assert(err, checker.IsNil)
}
func (s *SimpleSuite) TestUDPServiceConfigErrors(c *check.C) {
file := s.adaptFile(c, "fixtures/service_errors.toml", struct{}{})
defer os.Remove(file)
cmd, output := s.traefikCmd(withConfigFile(file))
defer output(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/udp/services", 1000*time.Millisecond, try.BodyContains(`["the udp service \"service1@file\" does not have any type defined"]`))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8080/api/udp/services/service1@file", 1000*time.Millisecond, try.BodyContains(`"status":"disabled"`))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8080/api/udp/services/service2@file", 1000*time.Millisecond, try.BodyContains(`"status":"enabled"`))
c.Assert(err, checker.IsNil)
}
func (s *SimpleSuite) TestWRR(c *check.C) {
s.createComposeProject(c, "base")
s.composeProject.Start(c)

107
integration/udp_test.go Normal file
View file

@ -0,0 +1,107 @@
package integration
import (
"net"
"net/http"
"os"
"strings"
"time"
"github.com/containous/traefik/v2/integration/try"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
)
type UDPSuite struct{ BaseSuite }
func (s *UDPSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "udp")
s.composeProject.Start(c)
}
func guessWhoUDP(addr string) (string, error) {
var conn net.Conn
var err error
udpAddr, err2 := net.ResolveUDPAddr("udp", addr)
if err2 != nil {
return "", err2
}
conn, err = net.DialUDP("udp", nil, udpAddr)
if err != nil {
return "", err
}
_, err = conn.Write([]byte("WHO"))
if err != nil {
return "", err
}
out := make([]byte, 2048)
n, err := conn.Read(out)
if err != nil {
return "", err
}
return string(out[:n]), nil
}
func (s *UDPSuite) TestWRR(c *check.C) {
whoamiAIP := s.composeProject.Container(c, "whoami-a").NetworkSettings.IPAddress
whoamiBIP := s.composeProject.Container(c, "whoami-b").NetworkSettings.IPAddress
whoamiCIP := s.composeProject.Container(c, "whoami-c").NetworkSettings.IPAddress
whoamiDIP := s.composeProject.Container(c, "whoami-d").NetworkSettings.IPAddress
file := s.adaptFile(c, "fixtures/udp/wrr.toml", struct {
WhoamiAIP string
WhoamiBIP string
WhoamiCIP string
WhoamiDIP string
}{
WhoamiAIP: whoamiAIP,
WhoamiBIP: whoamiBIP,
WhoamiCIP: whoamiCIP,
WhoamiDIP: whoamiDIP,
})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.StatusCodeIs(http.StatusOK), try.BodyContains("whoami-a"))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8093/who", 5*time.Second, try.StatusCodeIs(http.StatusOK))
c.Assert(err, checker.IsNil)
stop := make(chan struct{})
go func() {
call := map[string]int{}
for i := 0; i < 4; i++ {
out, err := guessWhoUDP("127.0.0.1:8093")
c.Assert(err, checker.IsNil)
switch {
case strings.Contains(out, "whoami-a"):
call["whoami-a"]++
case strings.Contains(out, "whoami-b"):
call["whoami-b"]++
case strings.Contains(out, "whoami-c"):
call["whoami-c"]++
default:
call["unknown"]++
}
}
c.Assert(call, checker.DeepEquals, map[string]int{"whoami-a": 2, "whoami-b": 1, "whoami-c": 1})
close(stop)
}()
select {
case <-stop:
case <-time.Tick(time.Second * 5):
c.Error("Timeout")
}
}

View file

@ -40,6 +40,8 @@ type RunTimeRepresentation struct {
Services map[string]*serviceInfoRepresentation `json:"services,omitempty"`
TCPRouters map[string]*runtime.TCPRouterInfo `json:"tcpRouters,omitempty"`
TCPServices map[string]*runtime.TCPServiceInfo `json:"tcpServices,omitempty"`
UDPRouters map[string]*runtime.UDPRouterInfo `json:"udpRouters,omitempty"`
UDPServices map[string]*runtime.UDPServiceInfo `json:"udpServices,omitempty"`
}
// Handler serves the configuration and status of Traefik on API endpoints.
@ -105,6 +107,11 @@ func (h Handler) createRouter() *mux.Router {
router.Methods(http.MethodGet).Path("/api/tcp/services").HandlerFunc(h.getTCPServices)
router.Methods(http.MethodGet).Path("/api/tcp/services/{serviceID}").HandlerFunc(h.getTCPService)
router.Methods(http.MethodGet).Path("/api/udp/routers").HandlerFunc(h.getUDPRouters)
router.Methods(http.MethodGet).Path("/api/udp/routers/{routerID}").HandlerFunc(h.getUDPRouter)
router.Methods(http.MethodGet).Path("/api/udp/services").HandlerFunc(h.getUDPServices)
router.Methods(http.MethodGet).Path("/api/udp/services/{serviceID}").HandlerFunc(h.getUDPService)
version.Handler{}.Append(router)
if h.dashboard {
@ -129,6 +136,8 @@ func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.R
Services: siRepr,
TCPRouters: h.runtimeConfiguration.TCPRouters,
TCPServices: h.runtimeConfiguration.TCPServices,
UDPRouters: h.runtimeConfiguration.UDPRouters,
UDPServices: h.runtimeConfiguration.UDPServices,
}
rw.Header().Set("Content-Type", "application/json")

164
pkg/api/handler_udp.go Normal file
View file

@ -0,0 +1,164 @@
package api
import (
"encoding/json"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/log"
"github.com/gorilla/mux"
)
type udpRouterRepresentation struct {
*runtime.UDPRouterInfo
Name string `json:"name,omitempty"`
Provider string `json:"provider,omitempty"`
}
func newUDPRouterRepresentation(name string, rt *runtime.UDPRouterInfo) udpRouterRepresentation {
return udpRouterRepresentation{
UDPRouterInfo: rt,
Name: name,
Provider: getProviderName(name),
}
}
type udpServiceRepresentation struct {
*runtime.UDPServiceInfo
Name string `json:"name,omitempty"`
Provider string `json:"provider,omitempty"`
Type string `json:"type,omitempty"`
}
func newUDPServiceRepresentation(name string, si *runtime.UDPServiceInfo) udpServiceRepresentation {
return udpServiceRepresentation{
UDPServiceInfo: si,
Name: name,
Provider: getProviderName(name),
Type: strings.ToLower(extractType(si.UDPService)),
}
}
func (h Handler) getUDPRouters(rw http.ResponseWriter, request *http.Request) {
results := make([]udpRouterRepresentation, 0, len(h.runtimeConfiguration.UDPRouters))
criterion := newSearchCriterion(request.URL.Query())
for name, rt := range h.runtimeConfiguration.UDPRouters {
if keepUDPRouter(name, rt, criterion) {
results = append(results, newUDPRouterRepresentation(name, rt))
}
}
sort.Slice(results, func(i, j int) bool {
return results[i].Name < results[j].Name
})
rw.Header().Set("Content-Type", "application/json")
pageInfo, err := pagination(request, len(results))
if err != nil {
writeError(rw, err.Error(), http.StatusBadRequest)
return
}
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
if err != nil {
log.FromContext(request.Context()).Error(err)
writeError(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getUDPRouter(rw http.ResponseWriter, request *http.Request) {
routerID := mux.Vars(request)["routerID"]
rw.Header().Set("Content-Type", "application/json")
router, ok := h.runtimeConfiguration.UDPRouters[routerID]
if !ok {
writeError(rw, fmt.Sprintf("router not found: %s", routerID), http.StatusNotFound)
return
}
result := newUDPRouterRepresentation(routerID, router)
err := json.NewEncoder(rw).Encode(result)
if err != nil {
log.FromContext(request.Context()).Error(err)
writeError(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getUDPServices(rw http.ResponseWriter, request *http.Request) {
results := make([]udpServiceRepresentation, 0, len(h.runtimeConfiguration.UDPServices))
criterion := newSearchCriterion(request.URL.Query())
for name, si := range h.runtimeConfiguration.UDPServices {
if keepUDPService(name, si, criterion) {
results = append(results, newUDPServiceRepresentation(name, si))
}
}
sort.Slice(results, func(i, j int) bool {
return results[i].Name < results[j].Name
})
rw.Header().Set("Content-Type", "application/json")
pageInfo, err := pagination(request, len(results))
if err != nil {
writeError(rw, err.Error(), http.StatusBadRequest)
return
}
rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
if err != nil {
log.FromContext(request.Context()).Error(err)
writeError(rw, err.Error(), http.StatusInternalServerError)
}
}
func (h Handler) getUDPService(rw http.ResponseWriter, request *http.Request) {
serviceID := mux.Vars(request)["serviceID"]
rw.Header().Set("Content-Type", "application/json")
service, ok := h.runtimeConfiguration.UDPServices[serviceID]
if !ok {
writeError(rw, fmt.Sprintf("service not found: %s", serviceID), http.StatusNotFound)
return
}
result := newUDPServiceRepresentation(serviceID, service)
err := json.NewEncoder(rw).Encode(result)
if err != nil {
log.FromContext(request.Context()).Error(err)
writeError(rw, err.Error(), http.StatusInternalServerError)
}
}
func keepUDPRouter(name string, item *runtime.UDPRouterInfo, criterion *searchCriterion) bool {
if criterion == nil {
return true
}
return criterion.withStatus(item.Status) && criterion.searchIn(name)
}
func keepUDPService(name string, item *runtime.UDPServiceInfo, criterion *searchCriterion) bool {
if criterion == nil {
return true
}
return criterion.withStatus(item.Status) && criterion.searchIn(name)
}

537
pkg/api/handler_udp_test.go Normal file
View file

@ -0,0 +1,537 @@
package api
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHandler_UDP(t *testing.T) {
type expected struct {
statusCode int
nextPage string
jsonFile string
}
testCases := []struct {
desc string
path string
conf runtime.Configuration
expected expected
}{
{
desc: "all UDP routers, but no config",
path: "/api/udp/routers",
conf: runtime.Configuration{},
expected: expected{
statusCode: http.StatusOK,
nextPage: "1",
jsonFile: "testdata/udprouters-empty.json",
},
},
{
desc: "all UDP routers",
path: "/api/udp/routers",
conf: runtime.Configuration{
UDPRouters: map[string]*runtime.UDPRouterInfo{
"test@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
Status: runtime.StatusEnabled,
},
"bar@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
Status: runtime.StatusWarning,
},
"foo@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
Status: runtime.StatusDisabled,
},
},
},
expected: expected{
statusCode: http.StatusOK,
nextPage: "1",
jsonFile: "testdata/udprouters.json",
},
},
{
desc: "all UDP routers, pagination, 1 res per page, want page 2",
path: "/api/udp/routers?page=2&per_page=1",
conf: runtime.Configuration{
UDPRouters: map[string]*runtime.UDPRouterInfo{
"bar@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
},
"baz@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
},
"test@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
},
},
},
expected: expected{
statusCode: http.StatusOK,
nextPage: "3",
jsonFile: "testdata/udprouters-page2.json",
},
},
{
desc: "UDP routers filtered by status",
path: "/api/udp/routers?status=enabled",
conf: runtime.Configuration{
UDPRouters: map[string]*runtime.UDPRouterInfo{
"test@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
Status: runtime.StatusEnabled,
},
"bar@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
Status: runtime.StatusWarning,
},
"foo@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
Status: runtime.StatusDisabled,
},
},
},
expected: expected{
statusCode: http.StatusOK,
nextPage: "1",
jsonFile: "testdata/udprouters-filtered-status.json",
},
},
{
desc: "UDP routers filtered by search",
path: "/api/udp/routers?search=bar@my",
conf: runtime.Configuration{
UDPRouters: map[string]*runtime.UDPRouterInfo{
"test@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
Status: runtime.StatusEnabled,
},
"bar@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
Status: runtime.StatusWarning,
},
"foo@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
Status: runtime.StatusDisabled,
},
},
},
expected: expected{
statusCode: http.StatusOK,
nextPage: "1",
jsonFile: "testdata/udprouters-filtered-search.json",
},
},
{
desc: "one UDP router by id",
path: "/api/udp/routers/bar@myprovider",
conf: runtime.Configuration{
UDPRouters: map[string]*runtime.UDPRouterInfo{
"bar@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
},
},
},
expected: expected{
statusCode: http.StatusOK,
jsonFile: "testdata/udprouter-bar.json",
},
},
{
desc: "one UDP router by id, that does not exist",
path: "/api/udp/routers/foo@myprovider",
conf: runtime.Configuration{
UDPRouters: map[string]*runtime.UDPRouterInfo{
"bar@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
},
},
},
expected: expected{
statusCode: http.StatusNotFound,
},
},
{
desc: "one UDP router by id, but no config",
path: "/api/udp/routers/bar@myprovider",
conf: runtime.Configuration{},
expected: expected{
statusCode: http.StatusNotFound,
},
},
{
desc: "all udp services, but no config",
path: "/api/udp/services",
conf: runtime.Configuration{},
expected: expected{
statusCode: http.StatusOK,
nextPage: "1",
jsonFile: "testdata/udpservices-empty.json",
},
},
{
desc: "all udp services",
path: "/api/udp/services",
conf: runtime.Configuration{
UDPServices: map[string]*runtime.UDPServiceInfo{
"bar@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider", "test@myprovider"},
Status: runtime.StatusEnabled,
},
"baz@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.2:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider"},
Status: runtime.StatusWarning,
},
"foz@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.2:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider"},
Status: runtime.StatusDisabled,
},
},
},
expected: expected{
statusCode: http.StatusOK,
nextPage: "1",
jsonFile: "testdata/udpservices.json",
},
},
{
desc: "udp services filtered by status",
path: "/api/udp/services?status=enabled",
conf: runtime.Configuration{
UDPServices: map[string]*runtime.UDPServiceInfo{
"bar@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider", "test@myprovider"},
Status: runtime.StatusEnabled,
},
"baz@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.2:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider"},
Status: runtime.StatusWarning,
},
"foz@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.2:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider"},
Status: runtime.StatusDisabled,
},
},
},
expected: expected{
statusCode: http.StatusOK,
nextPage: "1",
jsonFile: "testdata/udpservices-filtered-status.json",
},
},
{
desc: "udp services filtered by search",
path: "/api/udp/services?search=baz@my",
conf: runtime.Configuration{
UDPServices: map[string]*runtime.UDPServiceInfo{
"bar@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider", "test@myprovider"},
Status: runtime.StatusEnabled,
},
"baz@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.2:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider"},
Status: runtime.StatusWarning,
},
"foz@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.2:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider"},
Status: runtime.StatusDisabled,
},
},
},
expected: expected{
statusCode: http.StatusOK,
nextPage: "1",
jsonFile: "testdata/udpservices-filtered-search.json",
},
},
{
desc: "all udp services, 1 res per page, want page 2",
path: "/api/udp/services?page=2&per_page=1",
conf: runtime.Configuration{
UDPServices: map[string]*runtime.UDPServiceInfo{
"bar@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider", "test@myprovider"},
},
"baz@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.2:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider"},
},
"test@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.3:2345",
},
},
},
},
},
},
},
expected: expected{
statusCode: http.StatusOK,
nextPage: "3",
jsonFile: "testdata/udpservices-page2.json",
},
},
{
desc: "one udp service by id",
path: "/api/udp/services/bar@myprovider",
conf: runtime.Configuration{
UDPServices: map[string]*runtime.UDPServiceInfo{
"bar@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider", "test@myprovider"},
},
},
},
expected: expected{
statusCode: http.StatusOK,
jsonFile: "testdata/udpservice-bar.json",
},
},
{
desc: "one udp service by id, that does not exist",
path: "/api/udp/services/nono@myprovider",
conf: runtime.Configuration{
UDPServices: map[string]*runtime.UDPServiceInfo{
"bar@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider", "test@myprovider"},
},
},
},
expected: expected{
statusCode: http.StatusNotFound,
},
},
{
desc: "one udp service by id, but no config",
path: "/api/udp/services/foo@myprovider",
conf: runtime.Configuration{},
expected: expected{
statusCode: http.StatusNotFound,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rtConf := &test.conf
// To lazily initialize the Statuses.
rtConf.PopulateUsedBy()
rtConf.GetUDPRoutersByEntryPoints(context.Background(), []string{"web"})
handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
server := httptest.NewServer(handler.createRouter())
resp, err := http.DefaultClient.Get(server.URL + test.path)
require.NoError(t, err)
assert.Equal(t, test.expected.nextPage, resp.Header.Get(nextPageHeader))
require.Equal(t, test.expected.statusCode, resp.StatusCode)
if test.expected.jsonFile == "" {
return
}
assert.Equal(t, resp.Header.Get("Content-Type"), "application/json")
contents, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
err = resp.Body.Close()
require.NoError(t, err)
if *updateExpected {
var results interface{}
err := json.Unmarshal(contents, &results)
require.NoError(t, err)
newJSON, err := json.MarshalIndent(results, "", "\t")
require.NoError(t, err)
err = ioutil.WriteFile(test.expected.jsonFile, newJSON, 0644)
require.NoError(t, err)
}
data, err := ioutil.ReadFile(test.expected.jsonFile)
require.NoError(t, err)
assert.JSONEq(t, string(data), string(contents))
})
}
}

12
pkg/api/testdata/udprouter-bar.json vendored Normal file
View file

@ -0,0 +1,12 @@
{
"entryPoints": [
"web"
],
"name": "bar@myprovider",
"provider": "myprovider",
"service": "foo-service@myprovider",
"status": "enabled",
"using": [
"web"
]
}

View file

@ -0,0 +1 @@
[]

View file

@ -0,0 +1,14 @@
[
{
"entryPoints": [
"web"
],
"name": "bar@myprovider",
"provider": "myprovider",
"service": "foo-service@myprovider",
"status": "warning",
"using": [
"web"
]
}
]

View file

@ -0,0 +1,14 @@
[
{
"entryPoints": [
"web"
],
"name": "test@myprovider",
"provider": "myprovider",
"service": "foo-service@myprovider",
"status": "enabled",
"using": [
"web"
]
}
]

14
pkg/api/testdata/udprouters-page2.json vendored Normal file
View file

@ -0,0 +1,14 @@
[
{
"entryPoints": [
"web"
],
"name": "baz@myprovider",
"provider": "myprovider",
"service": "foo-service@myprovider",
"status": "enabled",
"using": [
"web"
]
}
]

38
pkg/api/testdata/udprouters.json vendored Normal file
View file

@ -0,0 +1,38 @@
[
{
"entryPoints": [
"web"
],
"name": "bar@myprovider",
"provider": "myprovider",
"service": "foo-service@myprovider",
"status": "warning",
"using": [
"web"
]
},
{
"entryPoints": [
"web"
],
"name": "foo@myprovider",
"provider": "myprovider",
"service": "foo-service@myprovider",
"status": "disabled",
"using": [
"web"
]
},
{
"entryPoints": [
"web"
],
"name": "test@myprovider",
"provider": "myprovider",
"service": "foo-service@myprovider",
"status": "enabled",
"using": [
"web"
]
}
]

17
pkg/api/testdata/udpservice-bar.json vendored Normal file
View file

@ -0,0 +1,17 @@
{
"loadBalancer": {
"servers": [
{
"address": "127.0.0.1:2345"
}
]
},
"name": "bar@myprovider",
"provider": "myprovider",
"status": "enabled",
"type": "loadbalancer",
"usedBy": [
"foo@myprovider",
"test@myprovider"
]
}

View file

@ -0,0 +1 @@
[]

View file

@ -0,0 +1,18 @@
[
{
"loadBalancer": {
"servers": [
{
"address": "127.0.0.2:2345"
}
]
},
"name": "baz@myprovider",
"provider": "myprovider",
"status": "warning",
"type": "loadbalancer",
"usedBy": [
"foo@myprovider"
]
}
]

View file

@ -0,0 +1,19 @@
[
{
"loadBalancer": {
"servers": [
{
"address": "127.0.0.1:2345"
}
]
},
"name": "bar@myprovider",
"provider": "myprovider",
"status": "enabled",
"type": "loadbalancer",
"usedBy": [
"foo@myprovider",
"test@myprovider"
]
}
]

18
pkg/api/testdata/udpservices-page2.json vendored Normal file
View file

@ -0,0 +1,18 @@
[
{
"loadBalancer": {
"servers": [
{
"address": "127.0.0.2:2345"
}
]
},
"name": "baz@myprovider",
"provider": "myprovider",
"status": "enabled",
"type": "loadbalancer",
"usedBy": [
"foo@myprovider"
]
}
]

51
pkg/api/testdata/udpservices.json vendored Normal file
View file

@ -0,0 +1,51 @@
[
{
"loadBalancer": {
"servers": [
{
"address": "127.0.0.1:2345"
}
]
},
"name": "bar@myprovider",
"provider": "myprovider",
"status": "enabled",
"type": "loadbalancer",
"usedBy": [
"foo@myprovider",
"test@myprovider"
]
},
{
"loadBalancer": {
"servers": [
{
"address": "127.0.0.2:2345"
}
]
},
"name": "baz@myprovider",
"provider": "myprovider",
"status": "warning",
"type": "loadbalancer",
"usedBy": [
"foo@myprovider"
]
},
{
"loadBalancer": {
"servers": [
{
"address": "127.0.0.2:2345"
}
]
},
"name": "foz@myprovider",
"provider": "myprovider",
"status": "disabled",
"type": "loadbalancer",
"usedBy": [
"foo@myprovider"
]
}
]

View file

@ -23,6 +23,7 @@ type Configurations map[string]*Configuration
type Configuration struct {
HTTP *HTTPConfiguration `json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty"`
TCP *TCPConfiguration `json:"tcp,omitempty" toml:"tcp,omitempty" yaml:"tcp,omitempty"`
UDP *UDPConfiguration `json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty"`
TLS *TLSConfiguration `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty"`
}

View file

@ -0,0 +1,82 @@
package dynamic
import (
"reflect"
)
// +k8s:deepcopy-gen=true
// UDPConfiguration contains all the UDP configuration parameters.
type UDPConfiguration struct {
Routers map[string]*UDPRouter `json:"routers,omitempty" toml:"routers,omitempty" yaml:"routers,omitempty"`
Services map[string]*UDPService `json:"services,omitempty" toml:"services,omitempty" yaml:"services,omitempty"`
}
// +k8s:deepcopy-gen=true
// UDPService defines the configuration for a UDP service. All fields are mutually exclusive.
type UDPService struct {
LoadBalancer *UDPServersLoadBalancer `json:"loadBalancer,omitempty" toml:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty"`
Weighted *UDPWeightedRoundRobin `json:"weighted,omitempty" toml:"weighted,omitempty" yaml:"weighted,omitempty" label:"-"`
}
// +k8s:deepcopy-gen=true
// UDPWeightedRoundRobin is a weighted round robin UDP load-balancer of services.
type UDPWeightedRoundRobin struct {
Services []UDPWRRService `json:"services,omitempty" toml:"services,omitempty" yaml:"services,omitempty"`
}
// +k8s:deepcopy-gen=true
// UDPWRRService is a reference to a UDP service load-balanced with weighted round robin.
type UDPWRRService struct {
Name string `json:"name,omitempty" toml:"name,omitempty" yaml:"name,omitempty"`
Weight *int `json:"weight,omitempty" toml:"weight,omitempty" yaml:"weight,omitempty"`
}
// SetDefaults sets the default values for a UDPWRRService.
func (w *UDPWRRService) SetDefaults() {
defaultWeight := 1
w.Weight = &defaultWeight
}
// +k8s:deepcopy-gen=true
// UDPRouter defines the configuration for an UDP router.
type UDPRouter struct {
EntryPoints []string `json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty"`
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty"`
}
// +k8s:deepcopy-gen=true
// UDPServersLoadBalancer defines the configuration for a load-balancer of UDP servers.
type UDPServersLoadBalancer struct {
Servers []UDPServer `json:"servers,omitempty" toml:"servers,omitempty" yaml:"servers,omitempty" label-slice-as-struct:"server"`
}
// Mergeable reports whether the given load-balancer can be merged with the receiver.
func (l *UDPServersLoadBalancer) Mergeable(loadBalancer *UDPServersLoadBalancer) bool {
savedServers := l.Servers
defer func() {
l.Servers = savedServers
}()
l.Servers = nil
savedServersLB := loadBalancer.Servers
defer func() {
loadBalancer.Servers = savedServersLB
}()
loadBalancer.Servers = nil
return reflect.DeepEqual(l, loadBalancer)
}
// +k8s:deepcopy-gen=true
// UDPServer defines a UDP server configuration.
type UDPServer struct {
Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty" label:"-"`
Port string `toml:"-" json:"-" yaml:"-"`
}

View file

@ -22,11 +22,13 @@ type Configuration struct {
Services map[string]*ServiceInfo `json:"services,omitempty"`
TCPRouters map[string]*TCPRouterInfo `json:"tcpRouters,omitempty"`
TCPServices map[string]*TCPServiceInfo `json:"tcpServices,omitempty"`
UDPRouters map[string]*UDPRouterInfo `json:"udpRouters,omitempty"`
UDPServices map[string]*UDPServiceInfo `json:"updServices,omitempty"`
}
// NewConfig returns a Configuration initialized with the given conf. It never returns nil.
func NewConfig(conf dynamic.Configuration) *Configuration {
if conf.HTTP == nil && conf.TCP == nil {
if conf.HTTP == nil && conf.TCP == nil && conf.UDP == nil {
return &Configuration{}
}
@ -74,6 +76,22 @@ func NewConfig(conf dynamic.Configuration) *Configuration {
}
}
if conf.UDP != nil {
if len(conf.UDP.Routers) > 0 {
runtimeConfig.UDPRouters = make(map[string]*UDPRouterInfo, len(conf.UDP.Routers))
for k, v := range conf.UDP.Routers {
runtimeConfig.UDPRouters[k] = &UDPRouterInfo{UDPRouter: v, Status: StatusEnabled}
}
}
if len(conf.UDP.Services) > 0 {
runtimeConfig.UDPServices = make(map[string]*UDPServiceInfo, len(conf.UDP.Services))
for k, v := range conf.UDP.Services {
runtimeConfig.UDPServices[k] = &UDPServiceInfo{UDPService: v, Status: StatusEnabled}
}
}
}
return runtimeConfig
}
@ -158,6 +176,34 @@ func (c *Configuration) PopulateUsedBy() {
sort.Strings(c.TCPServices[k].UsedBy)
}
for routerName, routerInfo := range c.UDPRouters {
// lazily initialize Status in case caller forgot to do it
if routerInfo.Status == "" {
routerInfo.Status = StatusEnabled
}
providerName := getProviderName(routerName)
if providerName == "" {
logger.WithField(log.RouterName, routerName).Error("udp router name is not fully qualified")
continue
}
serviceName := getQualifiedName(providerName, routerInfo.UDPRouter.Service)
if _, ok := c.UDPServices[serviceName]; !ok {
continue
}
c.UDPServices[serviceName].UsedBy = append(c.UDPServices[serviceName].UsedBy, routerName)
}
for k, serviceInfo := range c.UDPServices {
// lazily initialize Status in case caller forgot to do it
if serviceInfo.Status == "" {
serviceInfo.Status = StatusEnabled
}
sort.Strings(c.UDPServices[k].UsedBy)
}
}
func contains(entryPoints []string, entryPointName string) bool {

View file

@ -0,0 +1,114 @@
package runtime
import (
"context"
"fmt"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/log"
)
// GetUDPRoutersByEntryPoints returns all the UDP routers by entry points name and routers name.
func (c *Configuration) GetUDPRoutersByEntryPoints(ctx context.Context, entryPoints []string) map[string]map[string]*UDPRouterInfo {
entryPointsRouters := make(map[string]map[string]*UDPRouterInfo)
for rtName, rt := range c.UDPRouters {
logger := log.FromContext(log.With(ctx, log.Str(log.RouterName, rtName)))
eps := rt.EntryPoints
if len(eps) == 0 {
logger.Debugf("No entryPoint defined for this router, using the default one(s) instead: %+v", entryPoints)
eps = entryPoints
}
entryPointsCount := 0
for _, entryPointName := range eps {
if !contains(entryPoints, entryPointName) {
rt.AddError(fmt.Errorf("entryPoint %q doesn't exist", entryPointName), false)
logger.WithField(log.EntryPointName, entryPointName).
Errorf("entryPoint %q doesn't exist", entryPointName)
continue
}
if _, ok := entryPointsRouters[entryPointName]; !ok {
entryPointsRouters[entryPointName] = make(map[string]*UDPRouterInfo)
}
entryPointsCount++
rt.Using = append(rt.Using, entryPointName)
entryPointsRouters[entryPointName][rtName] = rt
}
if entryPointsCount == 0 {
rt.AddError(fmt.Errorf("no valid entryPoint for this router"), true)
logger.Error("no valid entryPoint for this router")
}
}
return entryPointsRouters
}
// UDPRouterInfo holds information about a currently running UDP router.
type UDPRouterInfo struct {
*dynamic.UDPRouter // dynamic configuration
Err []string `json:"error,omitempty"` // initialization error
// Status reports whether the router is disabled, in a warning state, or all good (enabled).
// If not in "enabled" state, the reason for it should be in the list of Err.
// It is the caller's responsibility to set the initial status.
Status string `json:"status,omitempty"`
Using []string `json:"using,omitempty"` // Effective entry points used by that router.
}
// AddError adds err to r.Err, if it does not already exist.
// If critical is set, r is marked as disabled.
func (r *UDPRouterInfo) AddError(err error, critical bool) {
for _, value := range r.Err {
if value == err.Error() {
return
}
}
r.Err = append(r.Err, err.Error())
if critical {
r.Status = StatusDisabled
return
}
// only set it to "warning" if not already in a worse state
if r.Status != StatusDisabled {
r.Status = StatusWarning
}
}
// UDPServiceInfo holds information about a currently running UDP service.
type UDPServiceInfo struct {
*dynamic.UDPService // dynamic configuration
Err []string `json:"error,omitempty"` // initialization error
// Status reports whether the service is disabled, in a warning state, or all good (enabled).
// If not in "enabled" state, the reason for it should be in the list of Err.
// It is the caller's responsibility to set the initial status.
Status string `json:"status,omitempty"`
UsedBy []string `json:"usedBy,omitempty"` // list of routers using that service
}
// AddError adds err to s.Err, if it does not already exist.
// If critical is set, s is marked as disabled.
func (s *UDPServiceInfo) AddError(err error, critical bool) {
for _, value := range s.Err {
if value == err.Error() {
return
}
}
s.Err = append(s.Err, err.Error())
if critical {
s.Status = StatusDisabled
return
}
// only set it to "warning" if not already in a worse state
if s.Status != StatusDisabled {
s.Status = StatusWarning
}
}

View file

@ -0,0 +1,201 @@
package runtime
import (
"context"
"testing"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/stretchr/testify/assert"
)
func TestGetUDPRoutersByEntryPoints(t *testing.T) {
testCases := []struct {
desc string
conf dynamic.Configuration
entryPoints []string
expected map[string]map[string]*UDPRouterInfo
}{
{
desc: "Empty Configuration without entrypoint",
conf: dynamic.Configuration{},
entryPoints: []string{""},
expected: map[string]map[string]*UDPRouterInfo{},
},
{
desc: "Empty Configuration with unknown entrypoints",
conf: dynamic.Configuration{},
entryPoints: []string{"foo"},
expected: map[string]map[string]*UDPRouterInfo{},
},
{
desc: "Valid configuration with an unknown entrypoint",
conf: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
Rule: "Host(`bar.foo`)",
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
},
},
},
entryPoints: []string{"foo"},
expected: map[string]map[string]*UDPRouterInfo{},
},
{
desc: "Valid configuration with a known entrypoint",
conf: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
"bar": {
EntryPoints: []string{"webs"},
Service: "bar-service@myprovider",
},
"foobar": {
EntryPoints: []string{"web", "webs"},
Service: "foobar-service@myprovider",
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
"bar": {
EntryPoints: []string{"webs"},
Service: "bar-service@myprovider",
},
"foobar": {
EntryPoints: []string{"web", "webs"},
Service: "foobar-service@myprovider",
},
},
},
},
entryPoints: []string{"web"},
expected: map[string]map[string]*UDPRouterInfo{
"web": {
"foo": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
Status: "enabled",
Using: []string{"web"},
},
"foobar": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web", "webs"},
Service: "foobar-service@myprovider",
},
Status: "warning",
Err: []string{`entryPoint "webs" doesn't exist`},
Using: []string{"web"},
},
},
},
},
{
desc: "Valid configuration with multiple known entrypoints",
conf: dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"foo": {
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
"bar": {
EntryPoints: []string{"webs"},
Service: "bar-service@myprovider",
},
"foobar": {
EntryPoints: []string{"web", "webs"},
Service: "foobar-service@myprovider",
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
"bar": {
EntryPoints: []string{"webs"},
Service: "bar-service@myprovider",
},
"foobar": {
EntryPoints: []string{"web", "webs"},
Service: "foobar-service@myprovider",
},
},
},
},
entryPoints: []string{"web", "webs"},
expected: map[string]map[string]*UDPRouterInfo{
"web": {
"foo": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
Status: "enabled",
Using: []string{"web"},
},
"foobar": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web", "webs"},
Service: "foobar-service@myprovider",
},
Status: "enabled",
Using: []string{"web", "webs"},
},
},
"webs": {
"bar": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"webs"},
Service: "bar-service@myprovider",
},
Status: "enabled",
Using: []string{"webs"},
},
"foobar": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web", "webs"},
Service: "foobar-service@myprovider",
},
Status: "enabled",
Using: []string{"web", "webs"},
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
runtimeConfig := NewConfig(test.conf)
actual := runtimeConfig.GetUDPRoutersByEntryPoints(context.Background(), test.entryPoints)
assert.Equal(t, test.expected, actual)
})
}
}

View file

@ -1,5 +1,10 @@
package static
import (
"fmt"
"strings"
)
// EntryPoint holds the entry point configuration.
type EntryPoint struct {
Address string `description:"Entry point address." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
@ -8,11 +13,34 @@ type EntryPoint struct {
ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty"`
}
// GetAddress strips any potential protocol part of the address field of the
// entry point, in order to return the actual address.
func (ep EntryPoint) GetAddress() string {
splitN := strings.SplitN(ep.Address, "/", 2)
return splitN[0]
}
// GetProtocol returns the protocol part of the address field of the entry point.
// If none is specified, it defaults to "tcp".
func (ep EntryPoint) GetProtocol() (string, error) {
splitN := strings.SplitN(ep.Address, "/", 2)
if len(splitN) < 2 {
return "tcp", nil
}
protocol := strings.ToLower(splitN[1])
if protocol == "tcp" || protocol == "udp" {
return protocol, nil
}
return "", fmt.Errorf("invalid protocol: %s", splitN[1])
}
// SetDefaults sets the default values.
func (e *EntryPoint) SetDefaults() {
e.Transport = &EntryPointsTransport{}
e.Transport.SetDefaults()
e.ForwardedHeaders = &ForwardedHeaders{}
func (ep *EntryPoint) SetDefaults() {
ep.Transport = &EntryPointsTransport{}
ep.Transport.SetDefaults()
ep.ForwardedHeaders = &ForwardedHeaders{}
}
// ForwardedHeaders Trust client forwarding headers.

View file

@ -0,0 +1,67 @@
package static
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestEntryPointProtocol(t *testing.T) {
tests := []struct {
name string
address string
expectedAddress string
expectedProtocol string
expectedError bool
}{
{
name: "Without protocol",
address: "127.0.0.1:8080",
expectedAddress: "127.0.0.1:8080",
expectedProtocol: "tcp",
expectedError: false,
},
{
name: "With TCP protocol in upper case",
address: "127.0.0.1:8080/TCP",
expectedAddress: "127.0.0.1:8080",
expectedProtocol: "tcp",
expectedError: false,
},
{
name: "With UDP protocol in upper case",
address: "127.0.0.1:8080/UDP",
expectedAddress: "127.0.0.1:8080",
expectedProtocol: "udp",
expectedError: false,
},
{
name: "With UDP protocol in weird case",
address: "127.0.0.1:8080/uDp",
expectedAddress: "127.0.0.1:8080",
expectedProtocol: "udp",
expectedError: false,
},
{
name: "With invalid protocol",
address: "127.0.0.1:8080/toto/tata",
expectedError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ep := EntryPoint{
Address: tt.address,
}
protocol, err := ep.GetProtocol()
if tt.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tt.expectedProtocol, protocol)
require.Equal(t, tt.expectedAddress, ep.GetAddress())
})
}
}

View file

@ -28,6 +28,10 @@ func Merge(ctx context.Context, configurations map[string]*dynamic.Configuration
Routers: make(map[string]*dynamic.TCPRouter),
Services: make(map[string]*dynamic.TCPService),
},
UDP: &dynamic.UDPConfiguration{
Routers: make(map[string]*dynamic.UDPRouter),
Services: make(map[string]*dynamic.UDPService),
},
}
servicesToDelete := map[string]struct{}{}

View file

@ -40,6 +40,10 @@ func TestDefaultRule(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -84,6 +88,10 @@ func TestDefaultRule(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -126,6 +134,10 @@ func TestDefaultRule(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -163,6 +175,10 @@ func TestDefaultRule(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -200,6 +216,10 @@ func TestDefaultRule(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -276,6 +296,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -326,6 +350,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -390,6 +418,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -443,6 +475,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -493,6 +529,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -538,6 +578,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -582,6 +626,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -624,6 +672,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
@ -667,6 +719,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -711,6 +767,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -767,6 +827,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -818,6 +882,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -859,6 +927,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -904,6 +976,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -964,6 +1040,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1027,6 +1107,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1094,6 +1178,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1153,6 +1241,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1215,6 +1307,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1270,6 +1366,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -1315,6 +1415,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1358,6 +1462,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1401,6 +1509,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1446,6 +1558,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1471,6 +1587,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1496,6 +1616,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1521,6 +1645,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1548,6 +1676,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1575,6 +1707,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1618,6 +1754,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1688,6 +1828,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1725,6 +1869,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1772,6 +1920,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1834,6 +1986,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1890,6 +2046,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1928,6 +2088,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},

View file

@ -49,6 +49,10 @@ func TestDefaultRule(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -98,6 +102,10 @@ func TestDefaultRule(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -149,6 +157,10 @@ func TestDefaultRule(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -198,6 +210,10 @@ func TestDefaultRule(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -242,6 +258,10 @@ func TestDefaultRule(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -286,6 +306,10 @@ func TestDefaultRule(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -372,6 +396,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -406,6 +434,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -438,6 +470,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -502,6 +538,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -582,6 +622,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -635,6 +679,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -687,6 +735,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -737,6 +789,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
@ -788,6 +844,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -840,6 +900,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -896,6 +960,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -966,6 +1034,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1044,6 +1116,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1103,6 +1179,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1156,6 +1236,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1235,6 +1319,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1317,6 +1405,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1409,6 +1501,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1485,6 +1581,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1572,6 +1672,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1643,6 +1747,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -1714,6 +1822,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1769,6 +1881,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1820,6 +1936,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -1871,6 +1991,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1922,6 +2046,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1954,6 +2082,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1988,6 +2120,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -2021,6 +2157,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -2056,6 +2196,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -2091,6 +2235,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -2142,6 +2290,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -2220,6 +2372,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -2265,6 +2421,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -2320,6 +2480,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -2399,6 +2563,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -2463,6 +2631,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -2509,6 +2681,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -2551,6 +2727,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {

View file

@ -219,6 +219,10 @@ func (p *Provider) loadFileConfigFromDirectory(ctx context.Context, directory st
Stores: make(map[string]tls.Store),
Options: make(map[string]tls.Options),
},
UDP: &dynamic.UDPConfiguration{
Routers: make(map[string]*dynamic.UDPRouter),
Services: make(map[string]*dynamic.UDPService),
},
}
}
@ -288,6 +292,22 @@ func (p *Provider) loadFileConfigFromDirectory(ctx context.Context, directory st
}
}
for name, conf := range c.UDP.Routers {
if _, exists := configuration.UDP.Routers[name]; exists {
logger.WithField(log.RouterName, name).Warn("UDP router already configured, skipping")
} else {
configuration.UDP.Routers[name] = conf
}
}
for name, conf := range c.UDP.Services {
if _, exists := configuration.UDP.Services[name]; exists {
logger.WithField(log.ServiceName, name).Warn("UDP service already configured, skipping")
} else {
configuration.UDP.Services[name] = conf
}
}
for _, conf := range c.TLS.Certificates {
if _, exists := configTLSMaps[conf]; exists {
logger.Warnf("TLS configuration %v already configured, skipping", conf)
@ -392,6 +412,10 @@ func (p *Provider) decodeConfiguration(filePath string, content string) (*dynami
Stores: make(map[string]tls.Store),
Options: make(map[string]tls.Options),
},
UDP: &dynamic.UDPConfiguration{
Routers: make(map[string]*dynamic.UDPRouter),
Services: make(map[string]*dynamic.UDPService),
},
}
switch strings.ToLower(filepath.Ext(filePath)) {

View file

@ -91,8 +91,10 @@ func TestProvideWithoutWatch(t *testing.T) {
select {
case conf := <-configChan:
require.NotNil(t, conf.Configuration.HTTP)
assert.Len(t, conf.Configuration.HTTP.Services, test.expectedNumService)
assert.Len(t, conf.Configuration.HTTP.Routers, test.expectedNumRouter)
numServices := len(conf.Configuration.HTTP.Services) + len(conf.Configuration.TCP.Services) + len(conf.Configuration.UDP.Services)
numRouters := len(conf.Configuration.HTTP.Routers) + len(conf.Configuration.TCP.Routers) + len(conf.Configuration.UDP.Routers)
assert.Equal(t, numServices, test.expectedNumService)
assert.Equal(t, numRouters, test.expectedNumRouter)
require.NotNil(t, conf.Configuration.TLS)
assert.Len(t, conf.Configuration.TLS.Certificates, test.expectedNumTLSConf)
assert.Len(t, conf.Configuration.TLS.Options, test.expectedNumTLSOptions)
@ -119,8 +121,10 @@ func TestProvideWithWatch(t *testing.T) {
select {
case conf := <-configChan:
require.NotNil(t, conf.Configuration.HTTP)
assert.Len(t, conf.Configuration.HTTP.Services, 0)
assert.Len(t, conf.Configuration.HTTP.Routers, 0)
numServices := len(conf.Configuration.HTTP.Services) + len(conf.Configuration.TCP.Services) + len(conf.Configuration.UDP.Services)
numRouters := len(conf.Configuration.HTTP.Routers) + len(conf.Configuration.TCP.Routers) + len(conf.Configuration.UDP.Routers)
assert.Equal(t, numServices, 0)
assert.Equal(t, numRouters, 0)
require.NotNil(t, conf.Configuration.TLS)
assert.Len(t, conf.Configuration.TLS.Certificates, 0)
case <-timeout:
@ -145,8 +149,8 @@ func TestProvideWithWatch(t *testing.T) {
select {
case conf := <-configChan:
numUpdates++
numServices = len(conf.Configuration.HTTP.Services)
numRouters = len(conf.Configuration.HTTP.Routers)
numServices = len(conf.Configuration.HTTP.Services) + len(conf.Configuration.TCP.Services) + len(conf.Configuration.UDP.Services)
numRouters = len(conf.Configuration.HTTP.Routers) + len(conf.Configuration.TCP.Routers) + len(conf.Configuration.UDP.Routers)
numTLSConfs = len(conf.Configuration.TLS.Certificates)
t.Logf("received update #%d: services %d/%d, routers %d/%d, TLS configs %d/%d", numUpdates, numServices, test.expectedNumService, numRouters, test.expectedNumRouter, numTLSConfs, test.expectedNumTLSConf)
@ -170,6 +174,13 @@ func getTestCases() []ProvideTestCase {
expectedNumService: 6,
expectedNumTLSConf: 5,
},
{
desc: "simple file with tcp and udp",
filePath: "./fixtures/toml/simple_file_02.toml",
expectedNumRouter: 5,
expectedNumService: 8,
expectedNumTLSConf: 5,
},
{
desc: "simple file yaml",
filePath: "./fixtures/yaml/simple_file_01.yml",

View file

@ -9,9 +9,6 @@
[http.routers."router3"]
service = "application-3"
[http.routers."router4"]
service = "application-4"
[http.services]
[http.services.application-1.loadBalancer]
@ -19,8 +16,8 @@
url = "http://172.17.0.1:80"
[http.services.application-2.loadBalancer]
[[http.services.application-2.loadBalancer.servers]]
url = "http://172.17.0.2:80"
[[http.services.application-2.loadBalancer.servers]]
url = "http://172.17.0.2:80"
[http.services.application-3.loadBalancer]
[[http.services.application-3.loadBalancer.servers]]
@ -38,28 +35,46 @@
[[http.services.application-6.loadBalancer.servers]]
url = "http://172.17.0.6:80"
[http.services.application-7.loadBalancer]
[[http.services.application-7.loadBalancer.servers]]
url = "http://172.17.0.7:80"
[http.services.application-8.loadBalancer]
[[http.services.application-8.loadBalancer.servers]]
url = "http://172.17.0.8:80"
[tls]
[[tls.certificates]]
certFile = "integration/fixtures/https/snitest1.com.cert"
keyFile = "integration/fixtures/https/snitest1.com.key"
[[tls.certificates]]
certFile = "integration/fixtures/https/snitest1.com.cert"
keyFile = "integration/fixtures/https/snitest1.com.key"
[[tls.certificates]]
certFile = "integration/fixtures/https/snitest2.com.cert"
keyFile = "integration/fixtures/https/snitest2.com.key"
[[tls.certificates]]
certFile = "integration/fixtures/https/snitest2.com.cert"
keyFile = "integration/fixtures/https/snitest2.com.key"
[[tls.certificates]]
certFile = "integration/fixtures/https/snitest3.com.cert"
keyFile = "integration/fixtures/https/snitest3.com.key"
[[tls.certificates]]
certFile = "integration/fixtures/https/snitest3.com.cert"
keyFile = "integration/fixtures/https/snitest3.com.key"
[[tls.certificates]]
certFile = "integration/fixtures/https/snitest4.com.cert"
keyFile = "integration/fixtures/https/snitest4.com.key"
[[tls.certificates]]
certFile = "integration/fixtures/https/snitest4.com.cert"
keyFile = "integration/fixtures/https/snitest4.com.key"
[[tls.certificates]]
certFile = "integration/fixtures/https/snitest5.com.cert"
keyFile = "integration/fixtures/https/snitest5.com.key"
[tcp.routers]
[tcp.routers."routertcp1"]
service = "applicationtcp-1"
[tcp.services]
[tcp.services.applicationtcp-1.loadBalancer]
[[tcp.services.applicationtcp-1.loadBalancer.servers]]
url = "http://172.17.0.9:80"
[udp.routers]
[udp.routers."routerudp1"]
service = "applicationudp-1"
[udp.services]
[udp.services.applicationudp-1.loadBalancer]
[[udp.services.applicationudp-1.loadBalancer.servers]]
url = "http://172.17.0.10:80"

View file

@ -1,53 +0,0 @@
http:
routers:
router1:
service: application-1
router2:
service: application-2
router3:
service: application-3
router4:
service: application-4
services:
application-1:
loadBalancer:
servers:
- url: 'http://172.17.0.1:80'
application-2:
loadBalancer:
servers:
- url: 'http://172.17.0.2:80'
application-3:
loadBalancer:
servers:
- url: 'http://172.17.0.3:80'
application-4:
loadBalancer:
servers:
- url: 'http://172.17.0.4:80'
application-5:
loadBalancer:
servers:
- url: 'http://172.17.0.5:80'
application-6:
loadBalancer:
servers:
- url: 'http://172.17.0.6:80'
application-7:
loadBalancer:
servers:
- url: 'http://172.17.0.7:80'
application-8:
loadBalancer:
servers:
- url: 'http://172.17.0.8:80'
tls:
certificates:
- certFile: integration/fixtures/https/snitest1.com.cert
keyFile: integration/fixtures/https/snitest1.com.key
- certFile: integration/fixtures/https/snitest2.com.cert
keyFile: integration/fixtures/https/snitest2.com.key
- certFile: integration/fixtures/https/snitest3.com.cert
keyFile: integration/fixtures/https/snitest3.com.key
- certFile: integration/fixtures/https/snitest4.com.cert
keyFile: integration/fixtures/https/snitest4.com.key

View file

@ -50,6 +50,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
@ -84,6 +88,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -104,6 +112,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
@ -140,6 +152,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
@ -194,6 +210,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -243,6 +263,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -290,6 +314,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"foo": {
@ -336,6 +364,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
@ -376,6 +408,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
@ -413,6 +449,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -450,6 +490,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
@ -488,6 +532,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -527,6 +575,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -575,6 +627,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
@ -611,6 +667,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
@ -677,6 +737,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
@ -734,6 +798,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -784,6 +852,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -830,6 +902,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -872,6 +948,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
@ -910,6 +990,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
@ -948,6 +1032,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -989,6 +1077,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1010,26 +1102,9 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
},
},
{
desc: "one app with traefik.enable=false",
applications: withApplications(
application(
appID("/app"),
appPorts(80, 81),
withTasks(localhostTask()),
withLabel("traefik.enable", "false"),
)),
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
@ -1052,6 +1127,35 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
},
},
},
{
desc: "one app with traefik.enable=false",
applications: withApplications(
application(
appID("/app"),
appPorts(80, 81),
withTasks(localhostTask()),
withLabel("traefik.enable", "false"),
)),
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1074,6 +1178,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1096,6 +1204,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1118,6 +1230,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
@ -1156,6 +1272,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
@ -1193,6 +1313,10 @@ func TestBuildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"a_b_app": {
@ -1248,6 +1372,10 @@ func TestBuildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1280,6 +1408,10 @@ func TestBuildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1320,6 +1452,10 @@ func TestBuildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1361,6 +1497,10 @@ func TestBuildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -1402,6 +1542,10 @@ func TestBuildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {

View file

@ -36,6 +36,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -84,6 +88,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test1": {
@ -146,6 +154,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test1": {
@ -207,6 +219,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -246,6 +262,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -269,6 +289,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -295,6 +319,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Router1": {
@ -338,6 +366,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -365,6 +397,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -408,6 +444,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -460,6 +500,10 @@ func Test_buildConfiguration(t *testing.T) {
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -520,6 +564,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -557,6 +605,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -600,6 +652,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -649,6 +705,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
@ -705,6 +765,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
@ -743,6 +807,10 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},

View file

@ -18,6 +18,10 @@ func mergeConfiguration(configurations dynamic.Configurations) dynamic.Configura
Routers: make(map[string]*dynamic.TCPRouter),
Services: make(map[string]*dynamic.TCPService),
},
UDP: &dynamic.UDPConfiguration{
Routers: make(map[string]*dynamic.UDPRouter),
Services: make(map[string]*dynamic.UDPService),
},
TLS: &dynamic.TLSConfiguration{
Stores: make(map[string]tls.Store),
Options: make(map[string]tls.Options),
@ -47,6 +51,15 @@ func mergeConfiguration(configurations dynamic.Configurations) dynamic.Configura
}
}
if configuration.UDP != nil {
for routerName, router := range configuration.UDP.Routers {
conf.UDP.Routers[provider.MakeQualifiedName(pvd, routerName)] = router
}
for serviceName, service := range configuration.UDP.Services {
conf.UDP.Services[provider.MakeQualifiedName(pvd, serviceName)] = service
}
}
if configuration.TLS != nil {
conf.TLS.Certificates = append(conf.TLS.Certificates, configuration.TLS.Certificates...)

View file

@ -79,6 +79,10 @@ func TestNewConfigurationWatcher(t *testing.T) {
},
Stores: map[string]tls.Store{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
}
assert.Equal(t, expected, conf)
@ -222,6 +226,10 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
},
Stores: map[string]tls.Store{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
}
assert.Equal(t, expected, publishedProviderConfig)

View file

@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
)
func TestAddProviderInContext(t *testing.T) {
func TestAddInContext(t *testing.T) {
testCases := []struct {
desc string
ctx context.Context

View file

@ -0,0 +1,87 @@
package udp
import (
"context"
"errors"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/server/provider"
udpservice "github.com/containous/traefik/v2/pkg/server/service/udp"
"github.com/containous/traefik/v2/pkg/udp"
)
// NewManager Creates a new Manager
func NewManager(conf *runtime.Configuration,
serviceManager *udpservice.Manager,
) *Manager {
return &Manager{
serviceManager: serviceManager,
conf: conf,
}
}
// Manager is a route/router manager
type Manager struct {
serviceManager *udpservice.Manager
conf *runtime.Configuration
}
func (m *Manager) getUDPRouters(ctx context.Context, entryPoints []string) map[string]map[string]*runtime.UDPRouterInfo {
if m.conf != nil {
return m.conf.GetUDPRoutersByEntryPoints(ctx, entryPoints)
}
return make(map[string]map[string]*runtime.UDPRouterInfo)
}
// BuildHandlers builds the handlers for the given entrypoints
func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string) map[string]udp.Handler {
entryPointsRouters := m.getUDPRouters(rootCtx, entryPoints)
entryPointHandlers := make(map[string]udp.Handler)
for _, entryPointName := range entryPoints {
entryPointName := entryPointName
routers := entryPointsRouters[entryPointName]
ctx := log.With(rootCtx, log.Str(log.EntryPointName, entryPointName))
handler, err := m.buildEntryPointHandler(ctx, routers)
if err != nil {
log.FromContext(ctx).Error(err)
continue
}
entryPointHandlers[entryPointName] = handler
}
return entryPointHandlers
}
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*runtime.UDPRouterInfo) (udp.Handler, error) {
logger := log.FromContext(ctx)
if len(configs) > 1 {
logger.Warn("Warning: config has more than one udp router for a given entrypoint")
}
for routerName, routerConfig := range configs {
ctxRouter := log.With(provider.AddInContext(ctx, routerName), log.Str(log.RouterName, routerName))
logger := log.FromContext(ctxRouter)
if routerConfig.Service == "" {
err := errors.New("the service is missing on the udp router")
routerConfig.AddError(err, true)
logger.Error(err)
continue
}
handler, err := m.serviceManager.BuildUDP(ctxRouter, routerConfig.Service)
if err != nil {
routerConfig.AddError(err, true)
logger.Error(err)
continue
}
return handler, nil
}
return nil, nil
}

View file

@ -0,0 +1,144 @@
package udp
import (
"context"
"testing"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/server/service/udp"
"github.com/stretchr/testify/assert"
)
func TestRuntimeConfiguration(t *testing.T) {
testCases := []struct {
desc string
serviceConfig map[string]*runtime.UDPServiceInfo
routerConfig map[string]*runtime.UDPRouterInfo
expectedError int
}{
{
desc: "No error",
serviceConfig: map[string]*runtime.UDPServiceInfo{
"foo-service": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Port: "8085",
Address: "127.0.0.1:8085",
},
{
Address: "127.0.0.1:8086",
Port: "8086",
},
},
},
},
},
},
routerConfig: map[string]*runtime.UDPRouterInfo{
"foo": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service",
},
},
"bar": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service",
},
},
},
expectedError: 0,
},
{
desc: "Router with unknown service",
serviceConfig: map[string]*runtime.UDPServiceInfo{
"foo-service": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:80",
},
},
},
},
},
},
routerConfig: map[string]*runtime.UDPRouterInfo{
"foo": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "wrong-service",
},
},
"bar": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service",
},
},
},
expectedError: 1,
},
{
desc: "Router with broken service",
serviceConfig: map[string]*runtime.UDPServiceInfo{
"foo-service": {
UDPService: &dynamic.UDPService{
LoadBalancer: nil,
},
},
},
routerConfig: map[string]*runtime.UDPRouterInfo{
"bar": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service",
},
},
},
expectedError: 2,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
entryPoints := []string{"web"}
conf := &runtime.Configuration{
UDPServices: test.serviceConfig,
UDPRouters: test.routerConfig,
}
serviceManager := udp.NewManager(conf)
routerManager := NewManager(conf, serviceManager)
_ = routerManager.BuildHandlers(context.Background(), entryPoints)
// even though conf was passed by argument to the manager builders above,
// it's ok to use it as the result we check, because everything worth checking
// can be accessed by pointers in it.
var allErrors int
for _, v := range conf.UDPServices {
if v.Err != nil {
allErrors++
}
}
for _, v := range conf.UDPRouters {
if len(v.Err) > 0 {
allErrors++
}
}
assert.Equal(t, test.expectedError, allErrors)
})
}
}

View file

@ -0,0 +1,91 @@
package server
import (
"context"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/responsemodifiers"
"github.com/containous/traefik/v2/pkg/server/middleware"
"github.com/containous/traefik/v2/pkg/server/router"
routertcp "github.com/containous/traefik/v2/pkg/server/router/tcp"
routerudp "github.com/containous/traefik/v2/pkg/server/router/udp"
"github.com/containous/traefik/v2/pkg/server/service"
"github.com/containous/traefik/v2/pkg/server/service/tcp"
"github.com/containous/traefik/v2/pkg/server/service/udp"
tcpCore "github.com/containous/traefik/v2/pkg/tcp"
"github.com/containous/traefik/v2/pkg/tls"
udpCore "github.com/containous/traefik/v2/pkg/udp"
)
// RouterFactory the factory of TCP/UDP routers.
type RouterFactory struct {
entryPointsTCP []string
entryPointsUDP []string
managerFactory *service.ManagerFactory
chainBuilder *middleware.ChainBuilder
tlsManager *tls.Manager
}
// NewRouterFactory creates a new RouterFactory
func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, chainBuilder *middleware.ChainBuilder) *RouterFactory {
var entryPointsTCP, entryPointsUDP []string
for name, cfg := range staticConfiguration.EntryPoints {
protocol, err := cfg.GetProtocol()
if err != nil {
// Should never happen because Traefik should not start if protocol is invalid.
log.WithoutContext().Errorf("Invalid protocol: %v", err)
}
if protocol == "udp" {
entryPointsUDP = append(entryPointsUDP, name)
} else {
entryPointsTCP = append(entryPointsTCP, name)
}
}
return &RouterFactory{
entryPointsTCP: entryPointsTCP,
entryPointsUDP: entryPointsUDP,
managerFactory: managerFactory,
tlsManager: tlsManager,
chainBuilder: chainBuilder,
}
}
// CreateRouters creates new TCPRouters and UDPRouters
func (f *RouterFactory) CreateRouters(conf dynamic.Configuration) (map[string]*tcpCore.Router, map[string]udpCore.Handler) {
ctx := context.Background()
rtConf := runtime.NewConfig(conf)
// HTTP
serviceManager := f.managerFactory.Build(rtConf)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, f.chainBuilder)
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
// TCP
svcTCPManager := tcp.NewManager(rtConf)
rtTCPManager := routertcp.NewManager(rtConf, svcTCPManager, handlersNonTLS, handlersTLS, f.tlsManager)
routersTCP := rtTCPManager.BuildHandlers(ctx, f.entryPointsTCP)
// UDP
svcUDPManager := udp.NewManager(rtConf)
rtUDPManager := routerudp.NewManager(rtConf, svcUDPManager)
routersUDP := rtUDPManager.BuildHandlers(ctx, f.entryPointsUDP)
rtConf.PopulateUsedBy()
return routersTCP, routersUDP
}

View file

@ -49,9 +49,9 @@ func TestReuseService(t *testing.T) {
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
tlsManager := tls.NewManager()
factory := NewTCPRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil))
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil))
entryPointsHandlers := factory.CreateTCPRouters(dynamic.Configuration{HTTP: dynamicConfigs})
entryPointsHandlers, _ := factory.CreateRouters(dynamic.Configuration{HTTP: dynamicConfigs})
// Test that the /ok path returns a status 200.
responseRecorderOk := &httptest.ResponseRecorder{}
@ -183,9 +183,9 @@ func TestServerResponseEmptyBackend(t *testing.T) {
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
tlsManager := tls.NewManager()
factory := NewTCPRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil))
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil))
entryPointsHandlers := factory.CreateTCPRouters(dynamic.Configuration{HTTP: test.config(testServer.URL)})
entryPointsHandlers, _ := factory.CreateRouters(dynamic.Configuration{HTTP: test.config(testServer.URL)})
responseRecorder := &httptest.ResponseRecorder{}
request := httptest.NewRequest(http.MethodGet, testServer.URL+requestPath, nil)
@ -221,9 +221,9 @@ func TestInternalServices(t *testing.T) {
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
tlsManager := tls.NewManager()
factory := NewTCPRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil))
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil))
entryPointsHandlers := factory.CreateTCPRouters(dynamic.Configuration{HTTP: dynamicConfigs})
entryPointsHandlers, _ := factory.CreateRouters(dynamic.Configuration{HTTP: dynamicConfigs})
// Test that the /ok path returns a status 200.
responseRecorderOk := &httptest.ResponseRecorder{}

View file

@ -17,6 +17,7 @@ import (
type Server struct {
watcher *ConfigurationWatcher
tcpEntryPoints TCPEntryPoints
udpEntryPoints UDPEntryPoints
chainBuilder *middleware.ChainBuilder
accessLoggerMiddleware *accesslog.Handler
@ -28,7 +29,7 @@ type Server struct {
}
// NewServer returns an initialized Server.
func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, watcher *ConfigurationWatcher,
func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher,
chainBuilder *middleware.ChainBuilder, accessLoggerMiddleware *accesslog.Handler) *Server {
srv := &Server{
watcher: watcher,
@ -38,6 +39,7 @@ func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, watcher *Con
signals: make(chan os.Signal, 1),
stopChan: make(chan bool, 1),
routinesPool: routinesPool,
udpEntryPoints: entryPointsUDP,
}
srv.configureSignals()
@ -56,6 +58,7 @@ func (s *Server) Start(ctx context.Context) {
}()
s.tcpEntryPoints.Start()
s.udpEntryPoints.Start()
s.watcher.Start()
s.routinesPool.GoCtx(s.listenSignals)
@ -71,6 +74,7 @@ func (s *Server) Stop() {
defer log.WithoutContext().Info("Server stopped")
s.tcpEntryPoints.Stop()
s.udpEntryPoints.Stop()
s.stopChan <- true
}

View file

@ -55,9 +55,17 @@ type TCPEntryPoints map[string]*TCPEntryPoint
func NewTCPEntryPoints(entryPointsConfig static.EntryPoints) (TCPEntryPoints, error) {
serverEntryPointsTCP := make(TCPEntryPoints)
for entryPointName, config := range entryPointsConfig {
protocol, err := config.GetProtocol()
if err != nil {
return nil, fmt.Errorf("error while building entryPoint %s: %v", entryPointName, err)
}
if protocol != "tcp" {
continue
}
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
var err error
serverEntryPointsTCP[entryPointName], err = NewTCPEntryPoint(ctx, config)
if err != nil {
return nil, fmt.Errorf("error while building entryPoint %s: %v", entryPointName, err)
@ -70,7 +78,7 @@ func NewTCPEntryPoints(entryPointsConfig static.EntryPoints) (TCPEntryPoints, er
func (eps TCPEntryPoints) Start() {
for entryPointName, serverEntryPoint := range eps {
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
go serverEntryPoint.StartTCP(ctx)
go serverEntryPoint.Start(ctx)
}
}
@ -149,8 +157,8 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint) (*T
}, nil
}
// StartTCP starts the TCP server.
func (e *TCPEntryPoint) StartTCP(ctx context.Context) {
// Start starts the TCP server.
func (e *TCPEntryPoint) Start(ctx context.Context) {
logger := log.FromContext(ctx)
logger.Debugf("Start TCP Server")
@ -370,7 +378,7 @@ func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoi
}
func buildListener(ctx context.Context, entryPoint *static.EntryPoint) (net.Listener, error) {
listener, err := net.Listen("tcp", entryPoint.Address)
listener, err := net.Listen("tcp", entryPoint.GetAddress())
if err != nil {
return nil, fmt.Errorf("error opening listener: %v", err)

View file

@ -125,7 +125,7 @@ func testShutdown(t *testing.T, router *tcp.Router) {
}
func startEntrypoint(entryPoint *TCPEntryPoint, router *tcp.Router) (net.Conn, error) {
go entryPoint.StartTCP(context.Background())
go entryPoint.Start(context.Background())
entryPoint.SwitchRouter(router)

View file

@ -0,0 +1,135 @@
package server
import (
"context"
"fmt"
"net"
"sync"
"time"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/udp"
)
// UDPEntryPoints maps UDP entry points by their names.
type UDPEntryPoints map[string]*UDPEntryPoint
// NewUDPEntryPoints returns all the UDP entry points, keyed by name.
func NewUDPEntryPoints(cfg static.EntryPoints) (UDPEntryPoints, error) {
entryPoints := make(UDPEntryPoints)
for entryPointName, entryPoint := range cfg {
protocol, err := entryPoint.GetProtocol()
if err != nil {
return nil, fmt.Errorf("error while building entryPoint %s: %v", entryPointName, err)
}
if protocol != "udp" {
continue
}
ep, err := NewUDPEntryPoint(entryPoint)
if err != nil {
return nil, fmt.Errorf("error while building entryPoint %s: %v", entryPointName, err)
}
entryPoints[entryPointName] = ep
}
return entryPoints, nil
}
// Start commences the listening for all the entry points.
func (eps UDPEntryPoints) Start() {
for entryPointName, ep := range eps {
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
go ep.Start(ctx)
}
}
// Stop makes all the entry points stop listening, and release associated resources.
func (eps UDPEntryPoints) Stop() {
var wg sync.WaitGroup
for epn, ep := range eps {
wg.Add(1)
go func(entryPointName string, entryPoint *UDPEntryPoint) {
defer wg.Done()
ctx := log.With(context.Background(), log.Str(log.EntryPointName, entryPointName))
entryPoint.Shutdown(ctx)
log.FromContext(ctx).Debugf("Entry point %s closed", entryPointName)
}(epn, ep)
}
wg.Wait()
}
// Switch swaps out all the given handlers in their associated entrypoints.
func (eps UDPEntryPoints) Switch(handlers map[string]udp.Handler) {
for epName, handler := range handlers {
if ep, ok := eps[epName]; ok {
ep.Switch(handler)
continue
}
log.WithoutContext().Errorf("EntryPoint %q does not exist", epName)
}
}
// UDPEntryPoint is an entry point where we listen for UDP packets.
type UDPEntryPoint struct {
listener *udp.Listener
switcher *udp.HandlerSwitcher
transportConfiguration *static.EntryPointsTransport
}
// NewUDPEntryPoint returns a UDP entry point.
func NewUDPEntryPoint(cfg *static.EntryPoint) (*UDPEntryPoint, error) {
addr, err := net.ResolveUDPAddr("udp", cfg.GetAddress())
if err != nil {
return nil, err
}
listener, err := udp.Listen("udp", addr)
if err != nil {
return nil, err
}
return &UDPEntryPoint{listener: listener, switcher: &udp.HandlerSwitcher{}, transportConfiguration: cfg.Transport}, nil
}
// Start commences the listening for ep.
func (ep *UDPEntryPoint) Start(ctx context.Context) {
log.FromContext(ctx).Debug("Start UDP Server")
for {
conn, err := ep.listener.Accept()
if err != nil {
// Only errClosedListener can happen that's why we return
return
}
go ep.switcher.ServeUDP(conn)
}
}
// Shutdown closes ep's listener. It eventually closes all "sessions" and
// releases associated resources, but only after it has waited for a graceTimeout,
// if any was configured.
func (ep *UDPEntryPoint) Shutdown(ctx context.Context) {
logger := log.FromContext(ctx)
reqAcceptGraceTimeOut := time.Duration(ep.transportConfiguration.LifeCycle.RequestAcceptGraceTimeout)
if reqAcceptGraceTimeOut > 0 {
logger.Infof("Waiting %s for incoming requests to cease", reqAcceptGraceTimeOut)
time.Sleep(reqAcceptGraceTimeOut)
}
graceTimeOut := time.Duration(ep.transportConfiguration.LifeCycle.GraceTimeOut)
if err := ep.listener.Shutdown(graceTimeOut); err != nil {
logger.Error(err)
}
}
// Switch replaces ep's handler with the one given as argument.
func (ep *UDPEntryPoint) Switch(handler udp.Handler) {
ep.switcher.Switch(handler)
}

View file

@ -0,0 +1,123 @@
package server
import (
"context"
"io"
"net"
"testing"
"time"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/types"
"github.com/containous/traefik/v2/pkg/udp"
"github.com/stretchr/testify/require"
)
func TestShutdownUDPConn(t *testing.T) {
entryPoint, err := NewUDPEntryPoint(&static.EntryPoint{
Address: ":0",
Transport: &static.EntryPointsTransport{
LifeCycle: &static.LifeCycle{
GraceTimeOut: types.Duration(5 * time.Second),
},
},
})
require.NoError(t, err)
go entryPoint.Start(context.Background())
entryPoint.Switch(udp.HandlerFunc(func(conn *udp.Conn) {
for {
b := make([]byte, 1024*1024)
n, err := conn.Read(b)
require.NoError(t, err)
// We control the termination, otherwise we would block on the Read above, until
// conn is closed by a timeout. Which means we would get an error, and even though
// we are in a goroutine and the current test might be over, go test would still
// yell at us if this happens while other tests are still running.
if string(b[:n]) == "CLOSE" {
return
}
_, err = conn.Write(b[:n])
require.NoError(t, err)
}
}))
conn, err := net.Dial("udp", entryPoint.listener.Addr().String())
require.NoError(t, err)
// Start sending packets, to create a "session" with the server.
requireEcho(t, "TEST", conn, time.Second)
doneChan := make(chan struct{})
go func() {
entryPoint.Shutdown(context.Background())
close(doneChan)
}()
// Make sure that our session is still live even after the shutdown.
requireEcho(t, "TEST2", conn, time.Second)
// And make sure that on the other hand, opening new sessions is not possible anymore.
conn2, err := net.Dial("udp", entryPoint.listener.Addr().String())
require.NoError(t, err)
_, err = conn2.Write([]byte("TEST"))
// Packet is accepted, but dropped
require.NoError(t, err)
// Make sure that our session is yet again still live. This is specifically to
// make sure we don't create a regression in listener's readLoop, i.e. that we only
// terminate the listener's readLoop goroutine by closing its pConn.
requireEcho(t, "TEST3", conn, time.Second)
done := make(chan bool)
go func() {
defer close(done)
b := make([]byte, 1024*1024)
n, err := conn2.Read(b)
require.Error(t, err)
require.Equal(t, 0, n)
}()
conn2.Close()
select {
case <-done:
case <-time.Tick(time.Second):
t.Fatal("Timeout")
}
_, err = conn.Write([]byte("CLOSE"))
require.NoError(t, err)
select {
case <-doneChan:
case <-time.Tick(time.Second * 5):
// In case we introduce a regression that would make the test wait forever.
t.Fatal("Timeout during shutdown")
}
}
// requireEcho tests that the conn session is live and functional, by writing
// data through it, and expecting the same data as a response when reading on it.
// It fatals if the read blocks longer than timeout, which is useful to detect
// regressions that would make a test wait forever.
func requireEcho(t *testing.T, data string, conn io.ReadWriter, timeout time.Duration) {
_, err := conn.Write([]byte(data))
require.NoError(t, err)
doneChan := make(chan struct{})
go func() {
b := make([]byte, 1024*1024)
n, err := conn.Read(b)
require.NoError(t, err)
require.Equal(t, data, string(b[:n]))
close(doneChan)
}()
select {
case <-doneChan:
case <-time.Tick(timeout):
t.Fatalf("Timeout during echo for: %s", data)
}
}

View file

@ -81,7 +81,7 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han
}
return loadBalancer, nil
default:
err := fmt.Errorf("the service %q doesn't have any TCP load balancer", serviceQualifiedName)
err := fmt.Errorf("the service %q does not have any type defined", serviceQualifiedName)
conf.AddError(err, true)
return nil, err
}

View file

@ -33,7 +33,7 @@ func TestManager_BuildTCP(t *testing.T) {
TCPService: &dynamic.TCPService{},
},
},
expectedError: `the service "test" doesn't have any TCP load balancer`,
expectedError: `the service "test" does not have any type defined`,
},
{
desc: "no such host, server is skipped, error is logged",

View file

@ -0,0 +1,81 @@
package udp
import (
"context"
"errors"
"fmt"
"net"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/server/provider"
"github.com/containous/traefik/v2/pkg/udp"
)
// Manager handles UDP services creation.
type Manager struct {
configs map[string]*runtime.UDPServiceInfo
}
// NewManager creates a new manager
func NewManager(conf *runtime.Configuration) *Manager {
return &Manager{
configs: conf.UDPServices,
}
}
// BuildUDP creates the UDP handler for the given service name.
func (m *Manager) BuildUDP(rootCtx context.Context, serviceName string) (udp.Handler, error) {
serviceQualifiedName := provider.GetQualifiedName(rootCtx, serviceName)
ctx := provider.AddInContext(rootCtx, serviceQualifiedName)
ctx = log.With(ctx, log.Str(log.ServiceName, serviceName))
conf, ok := m.configs[serviceQualifiedName]
if !ok {
return nil, fmt.Errorf("the udp service %q does not exist", serviceQualifiedName)
}
if conf.LoadBalancer != nil && conf.Weighted != nil {
err := errors.New("cannot create service: multi-types service not supported, consider declaring two different pieces of service instead")
conf.AddError(err, true)
return nil, err
}
logger := log.FromContext(ctx)
switch {
case conf.LoadBalancer != nil:
loadBalancer := udp.NewWRRLoadBalancer()
for name, server := range conf.LoadBalancer.Servers {
if _, _, err := net.SplitHostPort(server.Address); err != nil {
logger.Errorf("In udp service %q: %v", serviceQualifiedName, err)
continue
}
handler, err := udp.NewProxy(server.Address)
if err != nil {
logger.Errorf("In udp service %q server %q: %v", serviceQualifiedName, server.Address, err)
continue
}
loadBalancer.AddServer(handler)
logger.WithField(log.ServerName, name).Debugf("Creating UDP server %d at %s", name, server.Address)
}
return loadBalancer, nil
case conf.Weighted != nil:
loadBalancer := udp.NewWRRLoadBalancer()
for _, service := range conf.Weighted.Services {
handler, err := m.BuildUDP(rootCtx, service.Name)
if err != nil {
logger.Errorf("In udp service %q: %v", serviceQualifiedName, err)
return nil, err
}
loadBalancer.AddWeightedServer(handler, service.Weight)
}
return loadBalancer, nil
default:
err := fmt.Errorf("the udp service %q does not have any type defined", serviceQualifiedName)
conf.AddError(err, true)
return nil, err
}
}

View file

@ -0,0 +1,201 @@
package udp
import (
"context"
"testing"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/server/provider"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestManager_BuildUDP(t *testing.T) {
testCases := []struct {
desc string
serviceName string
configs map[string]*runtime.UDPServiceInfo
providerName string
expectedError string
}{
{
desc: "without configuration",
serviceName: "test",
configs: nil,
expectedError: `the udp service "test" does not exist`,
},
{
desc: "missing lb configuration",
serviceName: "test",
configs: map[string]*runtime.UDPServiceInfo{
"test": {
UDPService: &dynamic.UDPService{},
},
},
expectedError: `the udp service "test" does not have any type defined`,
},
{
desc: "no such host, server is skipped, error is logged",
serviceName: "test",
configs: map[string]*runtime.UDPServiceInfo{
"test": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{Address: "test:31"},
},
},
},
},
},
},
{
desc: "invalid IP address, server is skipped, error is logged",
serviceName: "test",
configs: map[string]*runtime.UDPServiceInfo{
"test": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{Address: "foobar"},
},
},
},
},
},
},
{
desc: "Simple service name",
serviceName: "serviceName",
configs: map[string]*runtime.UDPServiceInfo{
"serviceName": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{},
},
},
},
},
{
desc: "Service name with provider",
serviceName: "serviceName@provider-1",
configs: map[string]*runtime.UDPServiceInfo{
"serviceName@provider-1": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{},
},
},
},
},
{
desc: "Service name with provider in context",
serviceName: "serviceName",
configs: map[string]*runtime.UDPServiceInfo{
"serviceName@provider-1": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{},
},
},
},
providerName: "provider-1",
},
{
desc: "Server with correct host:port as address",
serviceName: "serviceName",
configs: map[string]*runtime.UDPServiceInfo{
"serviceName@provider-1": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "foobar.com:80",
},
},
},
},
},
},
providerName: "provider-1",
},
{
desc: "Server with correct ip:port as address",
serviceName: "serviceName",
configs: map[string]*runtime.UDPServiceInfo{
"serviceName@provider-1": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "192.168.0.12:80",
},
},
},
},
},
},
providerName: "provider-1",
},
{
desc: "missing port in address with hostname, server is skipped, error is logged",
serviceName: "serviceName",
configs: map[string]*runtime.UDPServiceInfo{
"serviceName@provider-1": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "foobar.com",
},
},
},
},
},
},
providerName: "provider-1",
},
{
desc: "missing port in address with ip, server is skipped, error is logged",
serviceName: "serviceName",
configs: map[string]*runtime.UDPServiceInfo{
"serviceName@provider-1": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "192.168.0.12",
},
},
},
},
},
},
providerName: "provider-1",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
manager := NewManager(&runtime.Configuration{
UDPServices: test.configs,
})
ctx := context.Background()
if len(test.providerName) > 0 {
ctx = provider.AddInContext(ctx, "foobar@"+test.providerName)
}
handler, err := manager.BuildUDP(ctx, test.serviceName)
if test.expectedError != "" {
assert.EqualError(t, err, test.expectedError)
require.Nil(t, handler)
} else {
assert.Nil(t, err)
require.NotNil(t, handler)
}
})
}
}

View file

@ -1,70 +0,0 @@
package server
import (
"context"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/responsemodifiers"
"github.com/containous/traefik/v2/pkg/server/middleware"
"github.com/containous/traefik/v2/pkg/server/router"
routertcp "github.com/containous/traefik/v2/pkg/server/router/tcp"
"github.com/containous/traefik/v2/pkg/server/service"
"github.com/containous/traefik/v2/pkg/server/service/tcp"
tcpCore "github.com/containous/traefik/v2/pkg/tcp"
"github.com/containous/traefik/v2/pkg/tls"
)
// TCPRouterFactory the factory of TCP routers.
type TCPRouterFactory struct {
entryPoints []string
managerFactory *service.ManagerFactory
chainBuilder *middleware.ChainBuilder
tlsManager *tls.Manager
}
// NewTCPRouterFactory creates a new TCPRouterFactory
func NewTCPRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, chainBuilder *middleware.ChainBuilder) *TCPRouterFactory {
var entryPoints []string
for name := range staticConfiguration.EntryPoints {
entryPoints = append(entryPoints, name)
}
return &TCPRouterFactory{
entryPoints: entryPoints,
managerFactory: managerFactory,
tlsManager: tlsManager,
chainBuilder: chainBuilder,
}
}
// CreateTCPRouters creates new TCPRouters
func (f *TCPRouterFactory) CreateTCPRouters(conf dynamic.Configuration) map[string]*tcpCore.Router {
ctx := context.Background()
rtConf := runtime.NewConfig(conf)
// HTTP
serviceManager := f.managerFactory.Build(rtConf)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, f.chainBuilder)
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPoints, false)
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPoints, true)
// TCP
svcTCPManager := tcp.NewManager(rtConf)
rtTCPManager := routertcp.NewManager(rtConf, svcTCPManager, handlersNonTLS, handlersTLS, f.tlsManager)
routersTCP := rtTCPManager.BuildHandlers(ctx, f.entryPoints)
rtConf.PopulateUsedBy()
return routersTCP
}

265
pkg/udp/conn.go Normal file
View file

@ -0,0 +1,265 @@
package udp
import (
"errors"
"io"
"net"
"sync"
"time"
)
const receiveMTU = 8192
const closeRetryInterval = 500 * time.Millisecond
// connTimeout determines how long to wait on an idle session,
// before releasing all resources related to that session.
const connTimeout = time.Second * 3
var errClosedListener = errors.New("udp: listener closed")
// Listener augments a session-oriented Listener over a UDP PacketConn.
type Listener struct {
pConn *net.UDPConn
mu sync.RWMutex
conns map[string]*Conn
// accepting signifies whether the listener is still accepting new sessions.
// It also serves as a sentinel for Shutdown to be idempotent.
accepting bool
acceptCh chan *Conn // no need for a Once, already indirectly guarded by accepting.
}
// Listen creates a new listener.
func Listen(network string, laddr *net.UDPAddr) (*Listener, error) {
conn, err := net.ListenUDP(network, laddr)
if err != nil {
return nil, err
}
l := &Listener{
pConn: conn,
acceptCh: make(chan *Conn),
conns: make(map[string]*Conn),
accepting: true,
}
go l.readLoop()
return l, nil
}
// Accept waits for and returns the next connection to the listener.
func (l *Listener) Accept() (*Conn, error) {
c := <-l.acceptCh
if c == nil {
// l.acceptCh got closed
return nil, errClosedListener
}
return c, nil
}
// Addr returns the listener's network address.
func (l *Listener) Addr() net.Addr {
return l.pConn.LocalAddr()
}
// Close closes the listener.
// It is like Shutdown with a zero graceTimeout.
func (l *Listener) Close() error {
return l.Shutdown(0)
}
// close should not be called more than once.
func (l *Listener) close() error {
l.mu.Lock()
defer l.mu.Unlock()
err := l.pConn.Close()
for k, v := range l.conns {
v.close()
delete(l.conns, k)
}
close(l.acceptCh)
return err
}
// Shutdown closes the listener.
// It immediately stops accepting new sessions,
// and it waits for all existing sessions to terminate,
// and a maximum of graceTimeout.
// Then it forces close any session left.
func (l *Listener) Shutdown(graceTimeout time.Duration) error {
l.mu.Lock()
if !l.accepting {
l.mu.Unlock()
return nil
}
l.accepting = false
l.mu.Unlock()
retryInterval := closeRetryInterval
if retryInterval > graceTimeout {
retryInterval = graceTimeout
}
start := time.Now()
end := start.Add(graceTimeout)
for {
if time.Now().After(end) {
break
}
l.mu.RLock()
if len(l.conns) == 0 {
l.mu.RUnlock()
break
}
l.mu.RUnlock()
time.Sleep(retryInterval)
}
return l.close()
}
// readLoop receives all packets from all remotes.
// If a packet comes from a remote that is already known to us (i.e. a "session"),
// we find that session, and otherwise we create a new one.
// We then send the data the session's readLoop.
func (l *Listener) readLoop() {
buf := make([]byte, receiveMTU)
for {
n, raddr, err := l.pConn.ReadFrom(buf)
if err != nil {
return
}
conn, err := l.getConn(raddr)
if err != nil {
continue
}
select {
case conn.receiveCh <- buf[:n]:
case <-conn.doneCh:
continue
}
}
}
// getConn returns the ongoing session with raddr if it exists, or creates a new
// one otherwise.
func (l *Listener) getConn(raddr net.Addr) (*Conn, error) {
l.mu.Lock()
defer l.mu.Unlock()
conn, ok := l.conns[raddr.String()]
if ok {
return conn, nil
}
if !l.accepting {
return nil, errClosedListener
}
conn = l.newConn(raddr)
l.conns[raddr.String()] = conn
l.acceptCh <- conn
go conn.readLoop()
return conn, nil
}
func (l *Listener) newConn(rAddr net.Addr) *Conn {
return &Conn{
listener: l,
rAddr: rAddr,
receiveCh: make(chan []byte),
readCh: make(chan []byte),
sizeCh: make(chan int),
doneCh: make(chan struct{}),
timer: time.NewTimer(connTimeout),
}
}
// Conn represents an on-going session with a client, over UDP packets.
type Conn struct {
listener *Listener
rAddr net.Addr
receiveCh chan []byte // to receive the data from the listener's readLoop
readCh chan []byte // to receive the buffer into which we should Read
sizeCh chan int // to synchronize with the end of a Read
msgs [][]byte // to store data from listener, to be consumed by Reads
timer *time.Timer // for timeouts
doneOnce sync.Once
doneCh chan struct{}
}
// readLoop waits for data to come from the listener's readLoop.
// It then waits for a Read operation to be ready to consume said data,
// that is to say it waits on readCh to receive the slice of bytes that the Read operation wants to read onto.
// The Read operation receives the signal that the data has been written to the slice of bytes through the sizeCh.
func (c *Conn) readLoop() {
for {
if len(c.msgs) == 0 {
select {
case msg := <-c.receiveCh:
c.msgs = append(c.msgs, msg)
case <-c.timer.C:
c.Close()
return
}
}
select {
case cBuf := <-c.readCh:
msg := c.msgs[0]
c.msgs = c.msgs[1:]
n := copy(cBuf, msg)
c.sizeCh <- n
case msg := <-c.receiveCh:
c.msgs = append(c.msgs, msg)
case <-c.timer.C:
c.Close()
return
}
}
}
// Read implements io.Reader for a Conn.
func (c *Conn) Read(p []byte) (int, error) {
select {
case c.readCh <- p:
n := <-c.sizeCh
c.timer.Reset(connTimeout)
return n, nil
case <-c.doneCh:
return 0, io.EOF
}
}
// Write implements io.Writer for a Conn.
func (c *Conn) Write(p []byte) (n int, err error) {
l := c.listener
if l == nil {
return 0, io.EOF
}
c.timer.Reset(connTimeout)
return l.pConn.WriteTo(p, c.rAddr)
}
func (c *Conn) close() {
c.doneOnce.Do(func() {
close(c.doneCh)
})
}
// Close releases resources related to the Conn.
func (c *Conn) Close() error {
c.close()
c.listener.mu.Lock()
defer c.listener.mu.Unlock()
delete(c.listener.conns, c.rAddr.String())
return nil
}

270
pkg/udp/conn_test.go Normal file
View file

@ -0,0 +1,270 @@
package udp
import (
"io"
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestListenNotBlocking(t *testing.T) {
addr, err := net.ResolveUDPAddr("udp", ":0")
require.NoError(t, err)
ln, err := Listen("udp", addr)
require.NoError(t, err)
defer func() {
err := ln.Close()
require.NoError(t, err)
}()
go func() {
for {
conn, err := ln.Accept()
if err == errClosedListener {
return
}
require.NoError(t, err)
go func() {
b := make([]byte, 2048)
n, err := conn.Read(b)
require.NoError(t, err)
_, err = conn.Write(b[:n])
require.NoError(t, err)
n, err = conn.Read(b)
require.NoError(t, err)
_, err = conn.Write(b[:n])
require.NoError(t, err)
// This should not block second call
time.Sleep(time.Second * 10)
}()
}
}()
udpConn, err := net.Dial("udp", ln.Addr().String())
require.NoError(t, err)
_, err = udpConn.Write([]byte("TEST"))
require.NoError(t, err)
b := make([]byte, 2048)
n, err := udpConn.Read(b)
require.NoError(t, err)
require.Equal(t, "TEST", string(b[:n]))
_, err = udpConn.Write([]byte("TEST2"))
require.NoError(t, err)
n, err = udpConn.Read(b)
require.NoError(t, err)
require.Equal(t, "TEST2", string(b[:n]))
_, err = udpConn.Write([]byte("TEST"))
require.NoError(t, err)
done := make(chan struct{})
go func() {
udpConn2, err := net.Dial("udp", ln.Addr().String())
require.NoError(t, err)
_, err = udpConn2.Write([]byte("TEST"))
require.NoError(t, err)
n, err = udpConn2.Read(b)
require.NoError(t, err)
assert.Equal(t, "TEST", string(b[:n]))
_, err = udpConn2.Write([]byte("TEST2"))
require.NoError(t, err)
n, err = udpConn2.Read(b)
require.NoError(t, err)
assert.Equal(t, "TEST2", string(b[:n]))
close(done)
}()
select {
case <-time.Tick(time.Second):
t.Error("Timeout")
case <-done:
}
}
func TestTimeoutWithRead(t *testing.T) {
testTimeout(t, true)
}
func TestTimeoutWithoutRead(t *testing.T) {
testTimeout(t, false)
}
func testTimeout(t *testing.T, withRead bool) {
addr, err := net.ResolveUDPAddr("udp", ":0")
require.NoError(t, err)
ln, err := Listen("udp", addr)
require.NoError(t, err)
defer func() {
err := ln.Close()
require.NoError(t, err)
}()
go func() {
for {
conn, err := ln.Accept()
if err == errClosedListener {
return
}
require.NoError(t, err)
if withRead {
buf := make([]byte, 1024)
_, err = conn.Read(buf)
require.NoError(t, err)
}
}
}()
for i := 0; i < 10; i++ {
udpConn2, err := net.Dial("udp", ln.Addr().String())
require.NoError(t, err)
_, err = udpConn2.Write([]byte("TEST"))
require.NoError(t, err)
}
time.Sleep(10 * time.Millisecond)
assert.Equal(t, 10, len(ln.conns))
time.Sleep(3 * time.Second)
assert.Equal(t, 0, len(ln.conns))
}
func TestShutdown(t *testing.T) {
addr, err := net.ResolveUDPAddr("udp", ":0")
require.NoError(t, err)
l, err := Listen("udp", addr)
require.NoError(t, err)
go func() {
for {
conn, err := l.Accept()
if err != nil {
return
}
go func() {
conn := conn
for {
b := make([]byte, 1024*1024)
n, err := conn.Read(b)
require.NoError(t, err)
// We control the termination,
// otherwise we would block on the Read above,
// until conn is closed by a timeout.
// Which means we would get an error,
// and even though we are in a goroutine and the current test might be over,
// go test would still yell at us if this happens while other tests are still running.
if string(b[:n]) == "CLOSE" {
return
}
_, err = conn.Write(b[:n])
require.NoError(t, err)
}
}()
}
}()
conn, err := net.Dial("udp", l.Addr().String())
require.NoError(t, err)
// Start sending packets, to create a "session" with the server.
requireEcho(t, "TEST", conn, time.Second)
doneChan := make(chan struct{})
go func() {
err := l.Shutdown(5 * time.Second)
require.NoError(t, err)
close(doneChan)
}()
// Make sure that our session is still live even after the shutdown.
requireEcho(t, "TEST2", conn, time.Second)
// And make sure that on the other hand, opening new sessions is not possible anymore.
conn2, err := net.Dial("udp", l.Addr().String())
require.NoError(t, err)
_, err = conn2.Write([]byte("TEST"))
// Packet is accepted, but dropped
require.NoError(t, err)
// Make sure that our session is yet again still live.
// This is specifically to make sure we don't create a regression in listener's readLoop,
// i.e. that we only terminate the listener's readLoop goroutine by closing its pConn.
requireEcho(t, "TEST3", conn, time.Second)
done := make(chan bool)
go func() {
defer close(done)
b := make([]byte, 1024*1024)
n, err := conn2.Read(b)
require.Error(t, err)
assert.Equal(t, 0, n)
}()
conn2.Close()
select {
case <-done:
case <-time.Tick(time.Second):
t.Fatal("Timeout")
}
_, err = conn.Write([]byte("CLOSE"))
require.NoError(t, err)
select {
case <-doneChan:
case <-time.Tick(time.Second * 5):
// In case we introduce a regression that would make the test wait forever.
t.Fatal("Timeout during shutdown")
}
}
// requireEcho tests that the conn session is live and functional,
// by writing data through it, and expecting the same data as a response when reading on it.
// It fatals if the read blocks longer than timeout,
// which is useful to detect regressions that would make a test wait forever.
func requireEcho(t *testing.T, data string, conn io.ReadWriter, timeout time.Duration) {
_, err := conn.Write([]byte(data))
require.NoError(t, err)
doneChan := make(chan struct{})
go func() {
b := make([]byte, 1024*1024)
n, err := conn.Read(b)
require.NoError(t, err)
assert.Equal(t, data, string(b[:n]))
close(doneChan)
}()
select {
case <-doneChan:
case <-time.Tick(timeout):
t.Fatalf("Timeout during echo for: %s", data)
}
}

14
pkg/udp/handler.go Normal file
View file

@ -0,0 +1,14 @@
package udp
// Handler is the UDP counterpart of the usual HTTP handler.
type Handler interface {
ServeUDP(conn *Conn)
}
// The HandlerFunc type is an adapter to allow the use of ordinary functions as handlers.
type HandlerFunc func(conn *Conn)
// ServeUDP implements the Handler interface for UDP.
func (f HandlerFunc) ServeUDP(conn *Conn) {
f(conn)
}

56
pkg/udp/proxy.go Normal file
View file

@ -0,0 +1,56 @@
package udp
import (
"io"
"net"
"github.com/containous/traefik/v2/pkg/log"
)
// Proxy is a reverse-proxy implementation of the Handler interface.
type Proxy struct {
// TODO: maybe optimize by pre-resolving it at proxy creation time
target string
}
// NewProxy creates a new Proxy
func NewProxy(address string) (*Proxy, error) {
return &Proxy{target: address}, nil
}
// ServeUDP implements the Handler interface.
func (p *Proxy) ServeUDP(conn *Conn) {
log.Debugf("Handling connection from %s", conn.rAddr)
// needed because of e.g. server.trackedConnection
defer conn.Close()
connBackend, err := net.Dial("udp", p.target)
if err != nil {
log.Errorf("Error while connecting to backend: %v", err)
return
}
// maybe not needed, but just in case
defer connBackend.Close()
errChan := make(chan error)
go p.connCopy(conn, connBackend, errChan)
go p.connCopy(connBackend, conn, errChan)
err = <-errChan
if err != nil {
log.WithoutContext().Errorf("Error while serving UDP: %v", err)
}
<-errChan
}
func (p Proxy) connCopy(dst io.WriteCloser, src io.Reader, errCh chan error) {
_, err := io.Copy(dst, src)
errCh <- err
if err := dst.Close(); err != nil {
log.WithoutContext().Debugf("Error while terminating connection: %v", err)
}
}

55
pkg/udp/proxy_test.go Normal file
View file

@ -0,0 +1,55 @@
package udp
import (
"net"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUDPProxy(t *testing.T) {
backendAddr := ":8081"
go newServer(t, ":8081", HandlerFunc(func(conn *Conn) {
for {
b := make([]byte, 1024*1024)
n, err := conn.Read(b)
require.NoError(t, err)
_, err = conn.Write(b[:n])
require.NoError(t, err)
}
}))
proxy, err := NewProxy(backendAddr)
require.NoError(t, err)
proxyAddr := ":8080"
go newServer(t, proxyAddr, proxy)
time.Sleep(time.Second)
udpConn, err := net.Dial("udp", proxyAddr)
require.NoError(t, err)
_, err = udpConn.Write([]byte("DATAWRITE"))
require.NoError(t, err)
b := make([]byte, 1024*1024)
n, err := udpConn.Read(b)
require.NoError(t, err)
assert.Equal(t, "DATAWRITE", string(b[:n]))
}
func newServer(t *testing.T, addr string, handler Handler) {
addrL, err := net.ResolveUDPAddr("udp", addr)
require.NoError(t, err)
listener, err := Listen("udp", addrL)
require.NoError(t, err)
for {
conn, err := listener.Accept()
require.NoError(t, err)
go handler.ServeUDP(conn)
}
}

26
pkg/udp/switcher.go Normal file
View file

@ -0,0 +1,26 @@
package udp
import (
"github.com/containous/traefik/v2/pkg/safe"
)
// HandlerSwitcher is a switcher implementation of the Handler interface.
type HandlerSwitcher struct {
handler safe.Safe
}
// ServeUDP implements the Handler interface.
func (s *HandlerSwitcher) ServeUDP(conn *Conn) {
handler := s.handler.Get()
h, ok := handler.(Handler)
if ok {
h.ServeUDP(conn)
} else {
conn.Close()
}
}
// Switch replaces s handler with the given handler.
func (s *HandlerSwitcher) Switch(handler Handler) {
s.handler.Set(handler)
}

View file

@ -0,0 +1,122 @@
package udp
import (
"fmt"
"sync"
"github.com/containous/traefik/v2/pkg/log"
)
type server struct {
Handler
weight int
}
// WRRLoadBalancer is a naive RoundRobin load balancer for UDP services
type WRRLoadBalancer struct {
servers []server
lock sync.RWMutex
currentWeight int
index int
}
// NewWRRLoadBalancer creates a new WRRLoadBalancer
func NewWRRLoadBalancer() *WRRLoadBalancer {
return &WRRLoadBalancer{
index: -1,
}
}
// ServeUDP forwards the connection to the right service
func (b *WRRLoadBalancer) ServeUDP(conn *Conn) {
if len(b.servers) == 0 {
log.WithoutContext().Error("no available server")
return
}
next, err := b.next()
if err != nil {
log.WithoutContext().Errorf("Error during load balancing: %v", err)
conn.Close()
}
next.ServeUDP(conn)
}
// AddServer appends a handler to the existing list
func (b *WRRLoadBalancer) AddServer(serverHandler Handler) {
w := 1
b.AddWeightedServer(serverHandler, &w)
}
// AddWeightedServer appends a handler to the existing list with a weight
func (b *WRRLoadBalancer) AddWeightedServer(serverHandler Handler, weight *int) {
w := 1
if weight != nil {
w = *weight
}
b.servers = append(b.servers, server{Handler: serverHandler, weight: w})
}
func (b *WRRLoadBalancer) maxWeight() int {
max := -1
for _, s := range b.servers {
if s.weight > max {
max = s.weight
}
}
return max
}
func (b *WRRLoadBalancer) weightGcd() int {
divisor := -1
for _, s := range b.servers {
if divisor == -1 {
divisor = s.weight
} else {
divisor = gcd(divisor, s.weight)
}
}
return divisor
}
func gcd(a, b int) int {
for b != 0 {
a, b = b, a%b
}
return a
}
func (b *WRRLoadBalancer) next() (Handler, error) {
b.lock.Lock()
defer b.lock.Unlock()
if len(b.servers) == 0 {
return nil, fmt.Errorf("no servers in the pool")
}
// The algorithm below may look messy,
// but is actually very simple it calculates the GCD and subtracts it on every iteration,
// what interleaves servers and allows us not to build an iterator every time we readjust weights.
// GCD across all enabled servers
gcd := b.weightGcd()
// Maximum weight across all enabled servers
max := b.maxWeight()
for {
b.index = (b.index + 1) % len(b.servers)
if b.index == 0 {
b.currentWeight -= gcd
if b.currentWeight <= 0 {
b.currentWeight = max
if b.currentWeight == 0 {
return nil, fmt.Errorf("all servers have 0 weight")
}
}
}
srv := b.servers[b.index]
if srv.weight >= b.currentWeight {
return srv, nil
}
}
}