UDP support
Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
This commit is contained in:
parent
8988c8f9af
commit
115d42e0f0
72 changed files with 4730 additions and 321 deletions
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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` |
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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`"
|
||||
|
||||
|
|
|
@ -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)."
|
||||
|
|
|
@ -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"
|
||||
```
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
53
integration/fixtures/udp/wrr.toml
Normal file
53
integration/fixtures/udp/wrr.toml
Normal 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}}"
|
|
@ -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{})
|
||||
}
|
||||
|
|
14
integration/resources/compose/udp.yml
Normal file
14
integration/resources/compose/udp.yml
Normal 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
|
|
@ -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
107
integration/udp_test.go
Normal 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")
|
||||
}
|
||||
}
|
|
@ -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
164
pkg/api/handler_udp.go
Normal 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
537
pkg/api/handler_udp_test.go
Normal 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
12
pkg/api/testdata/udprouter-bar.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"name": "bar@myprovider",
|
||||
"provider": "myprovider",
|
||||
"service": "foo-service@myprovider",
|
||||
"status": "enabled",
|
||||
"using": [
|
||||
"web"
|
||||
]
|
||||
}
|
1
pkg/api/testdata/udprouters-empty.json
vendored
Normal file
1
pkg/api/testdata/udprouters-empty.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
[]
|
14
pkg/api/testdata/udprouters-filtered-search.json
vendored
Normal file
14
pkg/api/testdata/udprouters-filtered-search.json
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"entryPoints": [
|
||||
"web"
|
||||
],
|
||||
"name": "bar@myprovider",
|
||||
"provider": "myprovider",
|
||||
"service": "foo-service@myprovider",
|
||||
"status": "warning",
|
||||
"using": [
|
||||
"web"
|
||||
]
|
||||
}
|
||||
]
|
14
pkg/api/testdata/udprouters-filtered-status.json
vendored
Normal file
14
pkg/api/testdata/udprouters-filtered-status.json
vendored
Normal 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
14
pkg/api/testdata/udprouters-page2.json
vendored
Normal 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
38
pkg/api/testdata/udprouters.json
vendored
Normal 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
17
pkg/api/testdata/udpservice-bar.json
vendored
Normal 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"
|
||||
]
|
||||
}
|
1
pkg/api/testdata/udpservices-empty.json
vendored
Normal file
1
pkg/api/testdata/udpservices-empty.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
[]
|
18
pkg/api/testdata/udpservices-filtered-search.json
vendored
Normal file
18
pkg/api/testdata/udpservices-filtered-search.json
vendored
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
19
pkg/api/testdata/udpservices-filtered-status.json
vendored
Normal file
19
pkg/api/testdata/udpservices-filtered-status.json
vendored
Normal 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
18
pkg/api/testdata/udpservices-page2.json
vendored
Normal 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
51
pkg/api/testdata/udpservices.json
vendored
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
|
@ -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"`
|
||||
}
|
||||
|
||||
|
|
82
pkg/config/dynamic/udp_config.go
Normal file
82
pkg/config/dynamic/udp_config.go
Normal 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:"-"`
|
||||
}
|
|
@ -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 {
|
||||
|
|
114
pkg/config/runtime/runtime_udp.go
Normal file
114
pkg/config/runtime/runtime_udp.go
Normal 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
|
||||
}
|
||||
}
|
201
pkg/config/runtime/runtime_udp_test.go
Normal file
201
pkg/config/runtime/runtime_udp_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
67
pkg/config/static/entrypoints_test.go
Normal file
67
pkg/config/static/entrypoints_test.go
Normal 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())
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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{}{}
|
||||
|
|
|
@ -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{},
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -9,9 +9,6 @@
|
|||
[http.routers."router3"]
|
||||
service = "application-3"
|
||||
|
||||
[http.routers."router4"]
|
||||
service = "application-4"
|
||||
|
||||
[http.services]
|
||||
|
||||
[http.services.application-1.loadBalancer]
|
||||
|
@ -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]]
|
||||
[[tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest1.com.cert"
|
||||
keyFile = "integration/fixtures/https/snitest1.com.key"
|
||||
|
||||
[[tls.certificates]]
|
||||
[[tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest2.com.cert"
|
||||
keyFile = "integration/fixtures/https/snitest2.com.key"
|
||||
|
||||
[[tls.certificates]]
|
||||
[[tls.certificates]]
|
||||
certFile = "integration/fixtures/https/snitest3.com.cert"
|
||||
keyFile = "integration/fixtures/https/snitest3.com.key"
|
||||
|
||||
[[tls.certificates]]
|
||||
[[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"
|
||||
|
|
|
@ -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
|
|
@ -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": {
|
||||
|
|
|
@ -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{},
|
||||
|
|
|
@ -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...)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
87
pkg/server/router/udp/router.go
Normal file
87
pkg/server/router/udp/router.go
Normal 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
|
||||
}
|
144
pkg/server/router/udp/router_test.go
Normal file
144
pkg/server/router/udp/router_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
91
pkg/server/routerfactory.go
Normal file
91
pkg/server/routerfactory.go
Normal 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
|
||||
}
|
|
@ -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{}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
135
pkg/server/server_entrypoint_udp.go
Normal file
135
pkg/server/server_entrypoint_udp.go
Normal 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)
|
||||
}
|
123
pkg/server/server_entrypoint_udp_test.go
Normal file
123
pkg/server/server_entrypoint_udp_test.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
81
pkg/server/service/udp/service.go
Normal file
81
pkg/server/service/udp/service.go
Normal 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
|
||||
}
|
||||
}
|
201
pkg/server/service/udp/service_test.go
Normal file
201
pkg/server/service/udp/service_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
265
pkg/udp/conn.go
Normal 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
270
pkg/udp/conn_test.go
Normal 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
14
pkg/udp/handler.go
Normal 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
56
pkg/udp/proxy.go
Normal 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
55
pkg/udp/proxy_test.go
Normal 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
26
pkg/udp/switcher.go
Normal 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)
|
||||
}
|
122
pkg/udp/wrr_load_balancer.go
Normal file
122
pkg/udp/wrr_load_balancer.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue