Add UDP in providers with labels

This commit is contained in:
Julien Salleyron 2020-02-20 22:24:05 +01:00 committed by GitHub
parent a20a5f1a44
commit bb4de11c51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1440 additions and 68 deletions

View file

@ -365,6 +365,50 @@ You can declare TCP Routers and/or Services using tags.
traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100 traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100
``` ```
### UDP
You can declare UDP Routers and/or Services using tags.
??? example "Declaring UDP Routers and Services"
```yaml
traefik.udp.routers.my-router.entrypoints=udp
traefik.udp.services.my-service.loadbalancer.server.port=4123
```
!!! warning "UDP and HTTP"
If you declare a UDP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no UDP Router/Service is defined).
You can declare both a UDP Router/Service and an HTTP Router/Service for the same consul service (but you have to do so manually).
#### UDP Routers
??? info "`traefik.udp.routers.<router_name>.entrypoints`"
See [entry points](../routers/index.md#entrypoints_2) for more information.
```yaml
traefik.udp.routers.myudprouter.entrypoints=ep1,ep2
```
??? info "`traefik.udp.routers.<router_name>.service`"
See [service](../routers/index.md#services_1) for more information.
```yaml
traefik.udp.routers.myudprouter.service=myservice
```
#### UDP Services
??? info "`traefik.udp.services.<service_name>.loadbalancer.server.port`"
Registers a port of the application.
```yaml
traefik.udp.services.myudpservice.loadbalancer.server.port=423
```
### Specific Provider Options ### Specific Provider Options
#### `traefik.enable` #### `traefik.enable`

View file

@ -508,6 +508,54 @@ You can declare TCP Routers and/or Services using labels.
- "traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100" - "traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100"
``` ```
### UDP
You can declare UDP Routers and/or Services using labels.
??? example "Declaring UDP Routers and Services"
```yaml
services:
my-container:
# ...
labels:
- "traefik.udp.routers.my-router.entrypoint=udp"
- "traefik.udp.services.my-service.loadbalancer.server.port=4123"
```
!!! warning "UDP and HTTP"
If you declare a UDP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no UDP Router/Service is defined).
You can declare both a UDP Router/Service and an HTTP Router/Service for the same container (but you have to do so manually).
#### UDP Routers
??? info "`traefik.udp.routers.<router_name>.entrypoints`"
See [entry points](../routers/index.md#entrypoints_2) for more information.
```yaml
- "traefik.udp.routers.myudprouter.entrypoints=ep1,ep2"
```
??? info "`traefik.udp.routers.<router_name>.service`"
See [service](../routers/index.md#services_1) for more information.
```yaml
- "traefik.udp.routers.myudprouter.service=myservice"
```
#### UDP Services
??? info "`traefik.udp.services.<service_name>.loadbalancer.server.port`"
Registers a port of the application.
```yaml
- "traefik.udp.services.myudpservice.loadbalancer.server.port=423"
```
### Specific Provider Options ### Specific Provider Options
#### `traefik.enable` #### `traefik.enable`

View file

@ -405,6 +405,55 @@ You can declare TCP Routers and/or Services using labels.
"traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay": "100" "traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay": "100"
``` ```
### UDP
You can declare UDP Routers and/or Services using labels.
??? example "Declaring UDP Routers and Services"
```json
{
...
"labels": {
"traefik.udp.routers.my-router.entrypoints": "udp",
"traefik.udp.services.my-service.loadbalancer.server.port": "4123"
}
}
```
!!! warning "UDP and HTTP"
If you declare a UDP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no UDP Router/Service is defined).
You can declare both a UDP Router/Service and an HTTP Router/Service for the same container (but you have to do so manually).
#### UDP Routers
??? info "`traefik.udp.routers.<router_name>.entrypoints`"
See [entry points](../routers/index.md#entrypoints_2) for more information.
```json
"traefik.udp.routers.myudprouter.entrypoints": "ep1,ep2"
```
??? info "`traefik.udp.routers.<router_name>.service`"
See [service](../routers/index.md#services_1) for more information.
```json
"traefik.udp.routers.myudprouter.service": "myservice"
```
#### UDP Services
??? info "`traefik.udp.services.<service_name>.loadbalancer.server.port`"
Registers a port of the application.
```json
"traefik.udp.services.myudpservice.loadbalancer.server.port": "423"
```
### Specific Provider Options ### Specific Provider Options
#### `traefik.enable` #### `traefik.enable`

View file

@ -408,6 +408,54 @@ You can declare TCP Routers and/or Services using labels.
- "traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100" - "traefik.tcp.services.mytcpservice.loadbalancer.terminationdelay=100"
``` ```
### UDP
You can declare UDP Routers and/or Services using labels.
??? example "Declaring UDP Routers and Services"
```yaml
services:
my-container:
# ...
labels:
- "traefik.udp.routers.my-router.entrypoints=udp"
- "traefik.udp.services.my-service.loadbalancer.server.port=4123"
```
!!! warning "UDP and HTTP"
If you declare a UDP Router/Service, it will prevent Traefik from automatically creating an HTTP Router/Service (like it does by default if no UDP Router/Service is defined).
You can declare both a UDP Router/Service and an HTTP Router/Service for the same container (but you have to do so manually).
#### UDP Routers
??? info "`traefik.udp.routers.<router_name>.entrypoints`"
See [entry points](../routers/index.md#entrypoints_2) for more information.
```yaml
- "traefik.udp.routers.myudprouter.entrypoints=ep1,ep2"
```
??? info "`traefik.udp.routers.<router_name>.service`"
See [service](../routers/index.md#services_1) for more information.
```yaml
- "traefik.udp.routers.myudprouter.service=myservice"
```
#### UDP Services
??? info "`traefik.udp.services.<service_name>.loadbalancer.server.port`"
Registers a port of the application.
```yaml
- "traefik.udp.services.myudpservice.loadbalancer.server.port=423"
```
### Specific Provider Options ### Specific Provider Options
#### `traefik.enable` #### `traefik.enable`

View file

@ -11,9 +11,10 @@ func DecodeConfiguration(labels map[string]string) (*dynamic.Configuration, erro
conf := &dynamic.Configuration{ conf := &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{}, HTTP: &dynamic.HTTPConfiguration{},
TCP: &dynamic.TCPConfiguration{}, TCP: &dynamic.TCPConfiguration{},
UDP: &dynamic.UDPConfiguration{},
} }
err := parser.Decode(labels, conf, parser.DefaultRootName, "traefik.http", "traefik.tcp") err := parser.Decode(labels, conf, parser.DefaultRootName, "traefik.http", "traefik.tcp", "traefik.udp")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -177,6 +177,13 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.tcp.services.Service0.loadbalancer.TerminationDelay": "42", "traefik.tcp.services.Service0.loadbalancer.TerminationDelay": "42",
"traefik.tcp.services.Service1.loadbalancer.server.Port": "42", "traefik.tcp.services.Service1.loadbalancer.server.Port": "42",
"traefik.tcp.services.Service1.loadbalancer.TerminationDelay": "42", "traefik.tcp.services.Service1.loadbalancer.TerminationDelay": "42",
"traefik.udp.routers.Router0.entrypoints": "foobar, fiibar",
"traefik.udp.routers.Router0.service": "foobar",
"traefik.udp.routers.Router1.entrypoints": "foobar, fiibar",
"traefik.udp.routers.Router1.service": "foobar",
"traefik.udp.services.Service0.loadbalancer.server.Port": "42",
"traefik.udp.services.Service1.loadbalancer.server.Port": "42",
} }
configuration, err := DecodeConfiguration(labels) configuration, err := DecodeConfiguration(labels)
@ -233,6 +240,44 @@ func TestDecodeConfiguration(t *testing.T) {
}, },
}, },
}, },
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"Router0": {
EntryPoints: []string{
"foobar",
"fiibar",
},
Service: "foobar",
},
"Router1": {
EntryPoints: []string{
"foobar",
"fiibar",
},
Service: "foobar",
},
},
Services: map[string]*dynamic.UDPService{
"Service0": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Port: "42",
},
},
},
},
"Service1": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Port: "42",
},
},
},
},
},
},
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
"Router0": { "Router0": {
@ -627,6 +672,7 @@ func TestEncodeConfiguration(t *testing.T) {
Port: "42", Port: "42",
}, },
}, },
TerminationDelay: func(i int) *int { return &i }(42),
}, },
}, },
"Service1": { "Service1": {
@ -636,6 +682,45 @@ func TestEncodeConfiguration(t *testing.T) {
Port: "42", Port: "42",
}, },
}, },
TerminationDelay: func(i int) *int { return &i }(42),
},
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"Router0": {
EntryPoints: []string{
"foobar",
"fiibar",
},
Service: "foobar",
},
"Router1": {
EntryPoints: []string{
"foobar",
"fiibar",
},
Service: "foobar",
},
},
Services: map[string]*dynamic.UDPService{
"Service0": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Port: "42",
},
},
},
},
"Service1": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Port: "42",
},
},
}, },
}, },
}, },
@ -1158,7 +1243,16 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.TCP.Routers.Router1.TLS.Passthrough": "false", "traefik.TCP.Routers.Router1.TLS.Passthrough": "false",
"traefik.TCP.Routers.Router1.TLS.Options": "foo", "traefik.TCP.Routers.Router1.TLS.Options": "foo",
"traefik.TCP.Services.Service0.LoadBalancer.server.Port": "42", "traefik.TCP.Services.Service0.LoadBalancer.server.Port": "42",
"traefik.TCP.Services.Service0.LoadBalancer.TerminationDelay": "42",
"traefik.TCP.Services.Service1.LoadBalancer.server.Port": "42", "traefik.TCP.Services.Service1.LoadBalancer.server.Port": "42",
"traefik.TCP.Services.Service1.LoadBalancer.TerminationDelay": "42",
"traefik.UDP.Routers.Router0.EntryPoints": "foobar, fiibar",
"traefik.UDP.Routers.Router0.Service": "foobar",
"traefik.UDP.Routers.Router1.EntryPoints": "foobar, fiibar",
"traefik.UDP.Routers.Router1.Service": "foobar",
"traefik.UDP.Services.Service0.LoadBalancer.server.Port": "42",
"traefik.UDP.Services.Service1.LoadBalancer.server.Port": "42",
} }
for key, val := range expected { for key, val := range expected {

View file

@ -46,6 +46,12 @@ func Merge(ctx context.Context, configurations map[string]*dynamic.Configuration
routersTCPToDelete := map[string]struct{}{} routersTCPToDelete := map[string]struct{}{}
routersTCP := map[string][]string{} routersTCP := map[string][]string{}
servicesUDPToDelete := map[string]struct{}{}
servicesUDP := map[string][]string{}
routersUDPToDelete := map[string]struct{}{}
routersUDP := map[string][]string{}
middlewaresToDelete := map[string]struct{}{} middlewaresToDelete := map[string]struct{}{}
middlewares := map[string][]string{} middlewares := map[string][]string{}
@ -85,6 +91,20 @@ func Merge(ctx context.Context, configurations map[string]*dynamic.Configuration
} }
} }
for serviceName, service := range conf.UDP.Services {
servicesUDP[serviceName] = append(servicesUDP[serviceName], root)
if !AddServiceUDP(configuration.UDP, serviceName, service) {
servicesUDPToDelete[serviceName] = struct{}{}
}
}
for routerName, router := range conf.UDP.Routers {
routersUDP[routerName] = append(routersUDP[routerName], root)
if !AddRouterUDP(configuration.UDP, routerName, router) {
routersUDPToDelete[routerName] = struct{}{}
}
}
for middlewareName, middleware := range conf.HTTP.Middlewares { for middlewareName, middleware := range conf.HTTP.Middlewares {
middlewares[middlewareName] = append(middlewares[middlewareName], root) middlewares[middlewareName] = append(middlewares[middlewareName], root)
if !AddMiddleware(configuration.HTTP, middlewareName, middleware) { if !AddMiddleware(configuration.HTTP, middlewareName, middleware) {
@ -117,6 +137,18 @@ func Merge(ctx context.Context, configurations map[string]*dynamic.Configuration
delete(configuration.TCP.Routers, routerName) delete(configuration.TCP.Routers, routerName)
} }
for serviceName := range servicesUDPToDelete {
logger.WithField(log.ServiceName, serviceName).
Errorf("UDP service defined multiple times with different configurations in %v", servicesUDP[serviceName])
delete(configuration.UDP.Services, serviceName)
}
for routerName := range routersUDPToDelete {
logger.WithField(log.RouterName, routerName).
Errorf("UDP router defined multiple times with different configurations in %v", routersUDP[routerName])
delete(configuration.UDP.Routers, routerName)
}
for middlewareName := range middlewaresToDelete { for middlewareName := range middlewaresToDelete {
logger.WithField(log.MiddlewareName, middlewareName). logger.WithField(log.MiddlewareName, middlewareName).
Errorf("Middleware defined multiple times with different configurations in %v", middlewares[middlewareName]) Errorf("Middleware defined multiple times with different configurations in %v", middlewares[middlewareName])
@ -141,6 +173,21 @@ func AddServiceTCP(configuration *dynamic.TCPConfiguration, serviceName string,
return true return true
} }
// AddServiceUDP adds a service to a configuration.
func AddServiceUDP(configuration *dynamic.UDPConfiguration, serviceName string, service *dynamic.UDPService) bool {
if _, ok := configuration.Services[serviceName]; !ok {
configuration.Services[serviceName] = service
return true
}
if !configuration.Services[serviceName].LoadBalancer.Mergeable(service.LoadBalancer) {
return false
}
configuration.Services[serviceName].LoadBalancer.Servers = append(configuration.Services[serviceName].LoadBalancer.Servers, service.LoadBalancer.Servers...)
return true
}
// AddRouterTCP Adds a router to a configurations. // AddRouterTCP Adds a router to a configurations.
func AddRouterTCP(configuration *dynamic.TCPConfiguration, routerName string, router *dynamic.TCPRouter) bool { func AddRouterTCP(configuration *dynamic.TCPConfiguration, routerName string, router *dynamic.TCPRouter) bool {
if _, ok := configuration.Routers[routerName]; !ok { if _, ok := configuration.Routers[routerName]; !ok {
@ -151,6 +198,16 @@ func AddRouterTCP(configuration *dynamic.TCPConfiguration, routerName string, ro
return reflect.DeepEqual(configuration.Routers[routerName], router) return reflect.DeepEqual(configuration.Routers[routerName], router)
} }
// AddRouterUDP adds a router to a configuration.
func AddRouterUDP(configuration *dynamic.UDPConfiguration, routerName string, router *dynamic.UDPRouter) bool {
if _, ok := configuration.Routers[routerName]; !ok {
configuration.Routers[routerName] = router
return true
}
return reflect.DeepEqual(configuration.Routers[routerName], router)
}
// AddService Adds a service to a configurations. // AddService Adds a service to a configurations.
func AddService(configuration *dynamic.HTTPConfiguration, serviceName string, service *dynamic.Service) bool { func AddService(configuration *dynamic.HTTPConfiguration, serviceName string, service *dynamic.Service) bool {
if _, ok := configuration.Services[serviceName]; !ok { if _, ok := configuration.Services[serviceName]; !ok {
@ -223,6 +280,28 @@ func BuildTCPRouterConfiguration(ctx context.Context, configuration *dynamic.TCP
} }
} }
// BuildUDPRouterConfiguration Builds a router configuration.
func BuildUDPRouterConfiguration(ctx context.Context, configuration *dynamic.UDPConfiguration) {
for routerName, router := range configuration.Routers {
loggerRouter := log.FromContext(ctx).WithField(log.RouterName, routerName)
if len(router.Service) > 0 {
continue
}
if len(configuration.Services) > 1 {
delete(configuration.Routers, routerName)
loggerRouter.
Error("Could not define the service name for the router: too many services")
continue
}
for serviceName := range configuration.Services {
router.Service = serviceName
break
}
}
}
// BuildRouterConfiguration Builds a router configuration. // BuildRouterConfiguration Builds a router configuration.
func BuildRouterConfiguration(ctx context.Context, configuration *dynamic.HTTPConfiguration, defaultRouterName string, defaultRuleTpl *template.Template, model interface{}) { func BuildRouterConfiguration(ctx context.Context, configuration *dynamic.HTTPConfiguration, defaultRouterName string, defaultRuleTpl *template.Template, model interface{}) {
if len(configuration.Routers) == 0 { if len(configuration.Routers) == 0 {

View file

@ -33,22 +33,35 @@ func (p *Provider) buildConfiguration(ctx context.Context, items []itemData) *dy
continue continue
} }
var tcpOrUDP bool
if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 { if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 {
tcpOrUDP = true
err := p.buildTCPServiceConfiguration(ctxSvc, item, confFromLabel.TCP) err := p.buildTCPServiceConfiguration(ctxSvc, item, confFromLabel.TCP)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
continue continue
} }
provider.BuildTCPRouterConfiguration(ctxSvc, confFromLabel.TCP) provider.BuildTCPRouterConfiguration(ctxSvc, confFromLabel.TCP)
}
if len(confFromLabel.HTTP.Routers) == 0 && if len(confFromLabel.UDP.Routers) > 0 || len(confFromLabel.UDP.Services) > 0 {
tcpOrUDP = true
err := p.buildUDPServiceConfiguration(ctxSvc, item, confFromLabel.UDP)
if err != nil {
logger.Error(err)
continue
}
provider.BuildUDPRouterConfiguration(ctxSvc, confFromLabel.UDP)
}
if tcpOrUDP && len(confFromLabel.HTTP.Routers) == 0 &&
len(confFromLabel.HTTP.Middlewares) == 0 && len(confFromLabel.HTTP.Middlewares) == 0 &&
len(confFromLabel.HTTP.Services) == 0 { len(confFromLabel.HTTP.Services) == 0 {
configurations[svcName] = confFromLabel configurations[svcName] = confFromLabel
continue continue
} }
}
err = p.buildServiceConfiguration(ctxSvc, item, confFromLabel.HTTP) err = p.buildServiceConfiguration(ctxSvc, item, confFromLabel.HTTP)
if err != nil { if err != nil {
@ -121,6 +134,28 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, item itemDa
return nil return nil
} }
func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, item itemData, configuration *dynamic.UDPConfiguration) error {
if len(configuration.Services) == 0 {
configuration.Services = make(map[string]*dynamic.UDPService)
lb := &dynamic.UDPServersLoadBalancer{}
configuration.Services[item.Name] = &dynamic.UDPService{
LoadBalancer: lb,
}
}
for name, service := range configuration.Services {
ctxSvc := log.With(ctx, log.Str(log.ServiceName, name))
err := p.addServerUDP(ctxSvc, item, service.LoadBalancer)
if err != nil {
return err
}
}
return nil
}
func (p *Provider) buildServiceConfiguration(ctx context.Context, item itemData, configuration *dynamic.HTTPConfiguration) error { func (p *Provider) buildServiceConfiguration(ctx context.Context, item itemData, configuration *dynamic.HTTPConfiguration) error {
if len(configuration.Services) == 0 { if len(configuration.Services) == 0 {
configuration.Services = make(map[string]*dynamic.Service) configuration.Services = make(map[string]*dynamic.Service)
@ -171,6 +206,33 @@ func (p *Provider) addServerTCP(ctx context.Context, item itemData, loadBalancer
return nil return nil
} }
func (p *Provider) addServerUDP(ctx context.Context, item itemData, loadBalancer *dynamic.UDPServersLoadBalancer) error {
if loadBalancer == nil {
return errors.New("load-balancer is not defined")
}
if len(loadBalancer.Servers) == 0 {
loadBalancer.Servers = []dynamic.UDPServer{{}}
}
var port string
if item.Port != "" {
port = item.Port
loadBalancer.Servers[0].Port = ""
}
if port == "" {
return errors.New("port is missing")
}
if item.Address == "" {
return errors.New("address is missing")
}
loadBalancer.Servers[0].Address = net.JoinHostPort(item.Address, port)
return nil
}
func (p *Provider) addServer(ctx context.Context, item itemData, loadBalancer *dynamic.ServersLoadBalancer) error { func (p *Provider) addServer(ctx context.Context, item itemData, loadBalancer *dynamic.ServersLoadBalancer) error {
if loadBalancer == nil { if loadBalancer == nil {
return errors.New("load-balancer is not defined") return errors.New("load-balancer is not defined")

View file

@ -1839,6 +1839,51 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "udp with label",
items: []itemData{
{
ID: "Test",
Name: "Test",
Labels: map[string]string{
"traefik.udp.routers.foo.entrypoints": "mydns",
},
Address: "127.0.0.1",
Port: "80",
Status: api.HealthPassing,
},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
Service: "Test",
EntryPoints: []string{"mydns"},
},
},
Services: map[string]*dynamic.UDPService{
"Test": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:80",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
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: "tcp with label without rule", desc: "tcp with label without rule",
items: []itemData{ items: []itemData{
@ -1931,6 +1976,52 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "udp with label and port",
items: []itemData{
{
ID: "Test",
Name: "Test",
Labels: map[string]string{
"traefik.udp.routers.foo.entrypoints": "mydns",
"traefik.udp.services.foo.loadbalancer.server.port": "80",
},
Address: "127.0.0.1",
Port: "80",
Status: api.HealthPassing,
},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
Service: "foo",
EntryPoints: []string{"mydns"},
},
},
Services: map[string]*dynamic.UDPService{
"foo": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:80",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
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: "tcp with label and port and http service", desc: "tcp with label and port and http service",
items: []itemData{ items: []itemData{
@ -2016,6 +2107,87 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "udp with label and port and http service",
items: []itemData{
{
ID: "1",
Name: "Test",
Labels: map[string]string{
"traefik.udp.routers.foo.entrypoints": "mydns",
"traefik.udp.services.foo.loadbalancer.server.port": "80",
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
},
Address: "127.0.0.1",
Port: "80",
Status: api.HealthPassing,
},
{
ID: "2",
Name: "Test",
Labels: map[string]string{
"traefik.udp.routers.foo.entrypoints": "mydns",
"traefik.udp.services.foo.loadbalancer.server.port": "80",
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
},
Address: "127.0.0.2",
Port: "80",
Status: api.HealthPassing,
},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
Service: "foo",
EntryPoints: []string{"mydns"},
},
},
Services: map[string]*dynamic.UDPService{
"foo": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:80",
},
{
Address: "127.0.0.2:80",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
Service: "Service1",
Rule: "Host(`Test.traefik.wtf`)",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"Service1": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1:80",
},
{
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
},
},
{ {
desc: "tcp with label for tcp service", desc: "tcp with label for tcp service",
items: []itemData{ items: []itemData{
@ -2057,6 +2229,46 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "udp with label for tcp service",
items: []itemData{
{
ID: "Test",
Name: "Test",
Labels: map[string]string{
"traefik.udp.services.foo.loadbalancer.server.port": "80",
},
Address: "127.0.0.1",
Port: "80",
Status: api.HealthPassing,
},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{
"foo": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:80",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
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: "tcp with label for tcp service, with termination delay", desc: "tcp with label for tcp service, with termination delay",
items: []itemData{ items: []itemData{

View file

@ -34,20 +34,35 @@ func (p *Provider) buildConfiguration(ctx context.Context, containersInspected [
continue continue
} }
var tcpOrUDP bool
if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 { if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 {
tcpOrUDP = true
err := p.buildTCPServiceConfiguration(ctxContainer, container, confFromLabel.TCP) err := p.buildTCPServiceConfiguration(ctxContainer, container, confFromLabel.TCP)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
continue continue
} }
provider.BuildTCPRouterConfiguration(ctxContainer, confFromLabel.TCP) provider.BuildTCPRouterConfiguration(ctxContainer, confFromLabel.TCP)
if len(confFromLabel.HTTP.Routers) == 0 && }
if len(confFromLabel.UDP.Routers) > 0 || len(confFromLabel.UDP.Services) > 0 {
tcpOrUDP = true
err := p.buildUDPServiceConfiguration(ctxContainer, container, confFromLabel.UDP)
if err != nil {
logger.Error(err)
continue
}
provider.BuildUDPRouterConfiguration(ctxContainer, confFromLabel.UDP)
}
if tcpOrUDP && len(confFromLabel.HTTP.Routers) == 0 &&
len(confFromLabel.HTTP.Middlewares) == 0 && len(confFromLabel.HTTP.Middlewares) == 0 &&
len(confFromLabel.HTTP.Services) == 0 { len(confFromLabel.HTTP.Services) == 0 {
configurations[containerName] = confFromLabel configurations[containerName] = confFromLabel
continue continue
} }
}
err = p.buildServiceConfiguration(ctxContainer, container, confFromLabel.HTTP) err = p.buildServiceConfiguration(ctxContainer, container, confFromLabel.HTTP)
if err != nil { if err != nil {
@ -96,6 +111,28 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container d
return nil return nil
} }
func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.UDPConfiguration) error {
serviceName := getServiceName(container)
if len(configuration.Services) == 0 {
configuration.Services = make(map[string]*dynamic.UDPService)
lb := &dynamic.UDPServersLoadBalancer{}
configuration.Services[serviceName] = &dynamic.UDPService{
LoadBalancer: lb,
}
}
for name, service := range configuration.Services {
ctxSvc := log.With(ctx, log.Str(log.ServiceName, name))
err := p.addServerUDP(ctxSvc, container, service.LoadBalancer)
if err != nil {
return fmt.Errorf("service %q error: %w", name, err)
}
}
return nil
}
func (p *Provider) buildServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.HTTPConfiguration) error { func (p *Provider) buildServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.HTTPConfiguration) error {
serviceName := getServiceName(container) serviceName := getServiceName(container)
@ -175,6 +212,36 @@ func (p *Provider) addServerTCP(ctx context.Context, container dockerData, loadB
return nil return nil
} }
func (p *Provider) addServerUDP(ctx context.Context, container dockerData, loadBalancer *dynamic.UDPServersLoadBalancer) error {
if loadBalancer == nil {
return errors.New("load-balancer is not defined")
}
var serverPort string
if len(loadBalancer.Servers) > 0 {
serverPort = loadBalancer.Servers[0].Port
loadBalancer.Servers[0].Port = ""
}
ip, port, err := p.getIPPort(ctx, container, serverPort)
if err != nil {
return err
}
if len(loadBalancer.Servers) == 0 {
server := dynamic.UDPServer{}
loadBalancer.Servers = []dynamic.UDPServer{server}
}
if port == "" {
return errors.New("port is missing")
}
loadBalancer.Servers[0].Address = net.JoinHostPort(ip, port)
return nil
}
func (p *Provider) addServer(ctx context.Context, container dockerData, loadBalancer *dynamic.ServersLoadBalancer) error { func (p *Provider) addServer(ctx context.Context, container dockerData, loadBalancer *dynamic.ServersLoadBalancer) error {
if loadBalancer == nil { if loadBalancer == nil {
return errors.New("load-balancer is not defined") return errors.New("load-balancer is not defined")

View file

@ -445,6 +445,44 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "invalid UDP service definition",
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{
"traefik.udp.services.test": "",
},
NetworkSettings: networkSettings{
Ports: nat.PortMap{
nat.Port("80/udp"): []nat.PortBinding{},
},
Networks: map[string]*networkData{
"bridge": {
Name: "bridge",
Addr: "127.0.0.1",
},
},
},
},
},
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{},
Services: map[string]*dynamic.Service{},
},
},
},
{ {
desc: "one container no label", desc: "one container no label",
containers: []dockerData{ containers: []dockerData{
@ -2383,6 +2421,59 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "udp with label",
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{
"traefik.udp.routers.foo.entrypoints": "mydns",
},
NetworkSettings: networkSettings{
Ports: nat.PortMap{
nat.Port("80/tcp"): []nat.PortBinding{},
},
Networks: map[string]*networkData{
"bridge": {
Name: "bridge",
Addr: "127.0.0.1",
},
},
},
},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
Service: "Test",
EntryPoints: []string{"mydns"},
},
},
Services: map[string]*dynamic.UDPService{
"Test": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:80",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
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: "tcp with label without rule", desc: "tcp with label without rule",
containers: []dockerData{ containers: []dockerData{
@ -2492,15 +2583,68 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
{ {
desc: "tcp with label and port and http service", desc: "udp with label and port",
containers: []dockerData{ containers: []dockerData{
{ {
ServiceName: "Test", ServiceName: "Test",
Name: "Test", Name: "Test",
Labels: map[string]string{ Labels: map[string]string{
"traefik.tcp.routers.foo.rule": "HostSNI(`foo.bar`)", "traefik.udp.routers.foo.entrypoints": "mydns",
"traefik.tcp.routers.foo.tls": "true", "traefik.udp.services.foo.loadbalancer.server.port": "8080",
"traefik.tcp.services.foo.loadbalancer.server.port": "8080", },
NetworkSettings: networkSettings{
Ports: nat.PortMap{
nat.Port("80/tcp"): []nat.PortBinding{},
},
Networks: map[string]*networkData{
"bridge": {
Name: "bridge",
Addr: "127.0.0.1",
},
},
},
},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
Service: "foo",
EntryPoints: []string{"mydns"},
},
},
Services: map[string]*dynamic.UDPService{
"foo": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:8080",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
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: "udp with label and port and http service",
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{
"traefik.udp.routers.foo.entrypoints": "mydns",
"traefik.udp.services.foo.loadbalancer.server.port": "8080",
"traefik.http.services.Service1.loadbalancer.passhostheader": "true", "traefik.http.services.Service1.loadbalancer.passhostheader": "true",
}, },
NetworkSettings: networkSettings{ NetworkSettings: networkSettings{
@ -2520,9 +2664,8 @@ func Test_buildConfiguration(t *testing.T) {
ServiceName: "Test", ServiceName: "Test",
Name: "Test", Name: "Test",
Labels: map[string]string{ Labels: map[string]string{
"traefik.tcp.routers.foo.rule": "HostSNI(`foo.bar`)", "traefik.udp.routers.foo.entrypoints": "mydns",
"traefik.tcp.routers.foo.tls": "true", "traefik.udp.services.foo.loadbalancer.server.port": "8080",
"traefik.tcp.services.foo.loadbalancer.server.port": "8080",
"traefik.http.services.Service1.loadbalancer.passhostheader": "true", "traefik.http.services.Service1.loadbalancer.passhostheader": "true",
}, },
NetworkSettings: networkSettings{ NetworkSettings: networkSettings{
@ -2539,18 +2682,17 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{ UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.TCPRouter{ Routers: map[string]*dynamic.UDPRouter{
"foo": { "foo": {
Service: "foo", Service: "foo",
Rule: "HostSNI(`foo.bar`)", EntryPoints: []string{"mydns"},
TLS: &dynamic.RouterTCPTLSConfig{},
}, },
}, },
Services: map[string]*dynamic.TCPService{ Services: map[string]*dynamic.UDPService{
"foo": { "foo": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{ LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.TCPServer{ Servers: []dynamic.UDPServer{
{ {
Address: "127.0.0.1:8080", Address: "127.0.0.1:8080",
}, },
@ -2558,14 +2700,13 @@ func Test_buildConfiguration(t *testing.T) {
Address: "127.0.0.2:8080", Address: "127.0.0.2:8080",
}, },
}, },
TerminationDelay: Int(100),
}, },
}, },
}, },
}, },
UDP: &dynamic.UDPConfiguration{ TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.UDPRouter{}, Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.UDPService{}, Services: map[string]*dynamic.TCPService{},
}, },
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{ Routers: map[string]*dynamic.Router{
@ -2594,13 +2735,13 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
{ {
desc: "tcp with label for tcp service", desc: "udp with label for tcp service",
containers: []dockerData{ containers: []dockerData{
{ {
ServiceName: "Test", ServiceName: "Test",
Name: "Test", Name: "Test",
Labels: map[string]string{ Labels: map[string]string{
"traefik.tcp.services.foo.loadbalancer.server.port": "8080", "traefik.udp.services.foo.loadbalancer.server.port": "8080",
}, },
NetworkSettings: networkSettings{ NetworkSettings: networkSettings{
Ports: nat.PortMap{ Ports: nat.PortMap{
@ -2616,24 +2757,23 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{ UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.TCPRouter{}, Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.TCPService{ Services: map[string]*dynamic.UDPService{
"foo": { "foo": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{ LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.TCPServer{ Servers: []dynamic.UDPServer{
{ {
Address: "127.0.0.1:8080", Address: "127.0.0.1:8080",
}, },
}, },
TerminationDelay: Int(100),
}, },
}, },
}, },
}, },
UDP: &dynamic.UDPConfiguration{ TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.UDPRouter{}, Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.UDPService{}, Services: map[string]*dynamic.TCPService{},
}, },
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{}, Routers: map[string]*dynamic.Router{},

View file

@ -224,6 +224,16 @@ func Test_buildConfiguration(t *testing.T) {
"traefik/tcp/services/TCPService02/weighted/services/0/weight": "42", "traefik/tcp/services/TCPService02/weighted/services/0/weight": "42",
"traefik/tcp/services/TCPService02/weighted/services/1/name": "foobar", "traefik/tcp/services/TCPService02/weighted/services/1/name": "foobar",
"traefik/tcp/services/TCPService02/weighted/services/1/weight": "43", "traefik/tcp/services/TCPService02/weighted/services/1/weight": "43",
"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/loadBalancer/servers/0/address": "foobar",
"traefik/udp/services/UDPService02/loadBalancer/servers/1/address": "foobar",
"traefik/tls/options/Options0/minVersion": "foobar", "traefik/tls/options/Options0/minVersion": "foobar",
"traefik/tls/options/Options0/maxVersion": "foobar", "traefik/tls/options/Options0/maxVersion": "foobar",
"traefik/tls/options/Options0/cipherSuites/0": "foobar", "traefik/tls/options/Options0/cipherSuites/0": "foobar",
@ -740,6 +750,36 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
}, },
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"UDPRouter0": {
EntryPoints: []string{"foobar", "foobar"},
Service: "foobar",
},
"UDPRouter1": {
EntryPoints: []string{"foobar", "foobar"},
Service: "foobar",
},
},
Services: map[string]*dynamic.UDPService{
"UDPService01": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{Address: "foobar"},
{Address: "foobar"},
},
},
},
"UDPService02": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{Address: "foobar"},
{Address: "foobar"},
},
},
},
},
},
TLS: &dynamic.TLSConfiguration{ TLS: &dynamic.TLSConfiguration{
Certificates: []*tls.CertAndStores{ Certificates: []*tls.CertAndStores{
{ {

View file

@ -49,20 +49,35 @@ func (p *Provider) buildConfiguration(ctx context.Context, applications *maratho
continue continue
} }
var tcpOrUDP bool
if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 { if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 {
tcpOrUDP = true
err := p.buildTCPServiceConfiguration(ctxApp, app, extraConf, confFromLabel.TCP) err := p.buildTCPServiceConfiguration(ctxApp, app, extraConf, confFromLabel.TCP)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
continue continue
} }
provider.BuildTCPRouterConfiguration(ctxApp, confFromLabel.TCP) provider.BuildTCPRouterConfiguration(ctxApp, confFromLabel.TCP)
if len(confFromLabel.HTTP.Routers) == 0 && }
if len(confFromLabel.UDP.Routers) > 0 || len(confFromLabel.UDP.Services) > 0 {
tcpOrUDP = true
err := p.buildUDPServiceConfiguration(ctxApp, app, extraConf, confFromLabel.UDP)
if err != nil {
logger.Error(err)
} else {
provider.BuildUDPRouterConfiguration(ctxApp, confFromLabel.UDP)
}
}
if tcpOrUDP && len(confFromLabel.HTTP.Routers) == 0 &&
len(confFromLabel.HTTP.Middlewares) == 0 && len(confFromLabel.HTTP.Middlewares) == 0 &&
len(confFromLabel.HTTP.Services) == 0 { len(confFromLabel.HTTP.Services) == 0 {
configurations[app.ID] = confFromLabel configurations[app.ID] = confFromLabel
continue continue
} }
}
err = p.buildServiceConfiguration(ctxApp, app, extraConf, confFromLabel.HTTP) err = p.buildServiceConfiguration(ctxApp, app, extraConf, confFromLabel.HTTP)
if err != nil { if err != nil {
@ -116,7 +131,9 @@ func (p *Provider) buildServiceConfiguration(ctx context.Context, app marathon.A
} }
for _, task := range app.Tasks { for _, task := range app.Tasks {
if p.taskFilter(ctx, *task, app) { if !p.taskFilter(ctx, *task, app) {
continue
}
server, err := p.getServer(app, *task, extraConf, defaultServer) server, err := p.getServer(app, *task, extraConf, defaultServer)
if err != nil { if err != nil {
log.FromContext(appCtx).Errorf("Skip task: %v", err) log.FromContext(appCtx).Errorf("Skip task: %v", err)
@ -124,7 +141,6 @@ func (p *Provider) buildServiceConfiguration(ctx context.Context, app marathon.A
} }
servers = append(servers, server) servers = append(servers, server)
} }
}
if len(servers) == 0 { if len(servers) == 0 {
return fmt.Errorf("no server for the service %s", serviceName) return fmt.Errorf("no server for the service %s", serviceName)
} }
@ -175,6 +191,47 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, app maratho
return nil return nil
} }
func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, app marathon.Application, extraConf configuration, conf *dynamic.UDPConfiguration) error {
appName := getServiceName(app)
appCtx := log.With(ctx, log.Str("ApplicationID", appName))
if len(conf.Services) == 0 {
conf.Services = make(map[string]*dynamic.UDPService)
lb := &dynamic.UDPServersLoadBalancer{}
conf.Services[appName] = &dynamic.UDPService{
LoadBalancer: lb,
}
}
for serviceName, service := range conf.Services {
var servers []dynamic.UDPServer
defaultServer := dynamic.UDPServer{}
if len(service.LoadBalancer.Servers) > 0 {
defaultServer = service.LoadBalancer.Servers[0]
}
for _, task := range app.Tasks {
if p.taskFilter(ctx, *task, app) {
server, err := p.getUDPServer(app, *task, extraConf, defaultServer)
if err != nil {
log.FromContext(appCtx).Errorf("Skip task: %v", err)
continue
}
servers = append(servers, server)
}
}
if len(servers) == 0 {
return fmt.Errorf("no server for the service %s", serviceName)
}
service.LoadBalancer.Servers = servers
}
return nil
}
func (p *Provider) keepApplication(ctx context.Context, extraConf configuration, labels map[string]string) bool { func (p *Provider) keepApplication(ctx context.Context, extraConf configuration, labels map[string]string) bool {
logger := log.FromContext(ctx) logger := log.FromContext(ctx)
@ -229,6 +286,24 @@ func (p *Provider) getTCPServer(app marathon.Application, task marathon.Task, ex
return server, nil return server, nil
} }
func (p *Provider) getUDPServer(app marathon.Application, task marathon.Task, extraConf configuration, defaultServer dynamic.UDPServer) (dynamic.UDPServer, error) {
host, err := p.getServerHost(task, app, extraConf)
if len(host) == 0 {
return dynamic.UDPServer{}, err
}
port, err := getPort(task, app, defaultServer.Port)
if err != nil {
return dynamic.UDPServer{}, err
}
server := dynamic.UDPServer{
Address: net.JoinHostPort(host, port),
}
return server, nil
}
func (p *Provider) getServer(app marathon.Application, task marathon.Task, extraConf configuration, defaultServer dynamic.Server) (dynamic.Server, error) { func (p *Provider) getServer(app marathon.Application, task marathon.Task, extraConf configuration, defaultServer dynamic.Server) (dynamic.Server, error) {
host, err := p.getServerHost(task, app, extraConf) host, err := p.getServerHost(task, app, extraConf)
if len(host) == 0 { if len(host) == 0 {

View file

@ -1383,6 +1383,46 @@ func TestBuildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "one app with udp labels",
applications: withApplications(
application(
appID("/app"),
appPorts(80, 81),
withTasks(localhostTask(taskPorts(80, 81))),
withLabel("traefik.udp.routers.foo.entrypoints", "mydns"),
)),
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
Service: "app",
EntryPoints: []string{"mydns"},
},
},
Services: map[string]*dynamic.UDPService{
"app": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "localhost:80",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
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 tcp labels without rule", desc: "one app with tcp labels without rule",
applications: withApplications( applications: withApplications(
@ -1463,6 +1503,47 @@ func TestBuildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "one app with udp labels with port",
applications: withApplications(
application(
appID("/app"),
appPorts(80, 81),
withTasks(localhostTask(taskPorts(80, 81))),
withLabel("traefik.udp.routers.foo.entrypoints", "mydns"),
withLabel("traefik.udp.services.foo.loadbalancer.server.port", "8080"),
)),
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
Service: "foo",
EntryPoints: []string{"mydns"},
},
},
Services: map[string]*dynamic.UDPService{
"foo": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "localhost:8080",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
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 tcp labels with port, with termination delay", desc: "one app with tcp labels with port, with termination delay",
applications: withApplications( applications: withApplications(
@ -1569,6 +1650,64 @@ func TestBuildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "one app with udp labels with port and http service",
applications: withApplications(
application(
appID("/app"),
appPorts(80, 81),
withTasks(localhostTask(taskPorts(80, 81))),
withLabel("traefik.udp.routers.foo.entrypoints", "mydns"),
withLabel("traefik.udp.services.foo.loadbalancer.server.port", "8080"),
withLabel("traefik.http.services.bar.loadbalancer.passhostheader", "true"),
)),
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
Service: "foo",
EntryPoints: []string{"mydns"},
},
},
Services: map[string]*dynamic.UDPService{
"foo": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "localhost:8080",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"app": {
Service: "bar",
Rule: "Host(`app.marathon.localhost`)",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"bar": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://localhost:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
},
},
} }
for _, test := range testCases { for _, test := range testCases {

View file

@ -32,20 +32,35 @@ func (p *Provider) buildConfiguration(ctx context.Context, services []rancherDat
continue continue
} }
var tcpOrUDP bool
if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 { if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 {
tcpOrUDP = true
err := p.buildTCPServiceConfiguration(ctxService, service, confFromLabel.TCP) err := p.buildTCPServiceConfiguration(ctxService, service, confFromLabel.TCP)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
continue continue
} }
provider.BuildTCPRouterConfiguration(ctxService, confFromLabel.TCP) provider.BuildTCPRouterConfiguration(ctxService, confFromLabel.TCP)
if len(confFromLabel.HTTP.Routers) == 0 && }
if len(confFromLabel.UDP.Routers) > 0 || len(confFromLabel.UDP.Services) > 0 {
tcpOrUDP = true
err := p.buildUDPServiceConfiguration(ctxService, service, confFromLabel.UDP)
if err != nil {
logger.Error(err)
continue
}
provider.BuildUDPRouterConfiguration(ctxService, confFromLabel.UDP)
}
if tcpOrUDP && len(confFromLabel.HTTP.Routers) == 0 &&
len(confFromLabel.HTTP.Middlewares) == 0 && len(confFromLabel.HTTP.Middlewares) == 0 &&
len(confFromLabel.HTTP.Services) == 0 { len(confFromLabel.HTTP.Services) == 0 {
configurations[service.Name] = confFromLabel configurations[service.Name] = confFromLabel
continue continue
} }
}
err = p.buildServiceConfiguration(ctx, service, confFromLabel.HTTP) err = p.buildServiceConfiguration(ctx, service, confFromLabel.HTTP)
if err != nil { if err != nil {
@ -91,6 +106,28 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, service ran
return nil return nil
} }
func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, service rancherData, configuration *dynamic.UDPConfiguration) error {
serviceName := service.Name
if len(configuration.Services) == 0 {
configuration.Services = make(map[string]*dynamic.UDPService)
lb := &dynamic.UDPServersLoadBalancer{}
configuration.Services[serviceName] = &dynamic.UDPService{
LoadBalancer: lb,
}
}
for _, confService := range configuration.Services {
err := p.addServerUDP(ctx, service, confService.LoadBalancer)
if err != nil {
return err
}
}
return nil
}
func (p *Provider) buildServiceConfiguration(ctx context.Context, service rancherData, configuration *dynamic.HTTPConfiguration) error { func (p *Provider) buildServiceConfiguration(ctx context.Context, service rancherData, configuration *dynamic.HTTPConfiguration) error {
serviceName := service.Name serviceName := service.Name
@ -182,6 +219,43 @@ func (p *Provider) addServerTCP(ctx context.Context, service rancherData, loadBa
return nil return nil
} }
func (p *Provider) addServerUDP(ctx context.Context, service rancherData, loadBalancer *dynamic.UDPServersLoadBalancer) error {
log.FromContext(ctx).Debugf("Trying to add servers for service %s \n", service.Name)
serverPort := ""
if loadBalancer != nil && len(loadBalancer.Servers) > 0 {
serverPort = loadBalancer.Servers[0].Port
}
port := getServicePort(service)
if len(loadBalancer.Servers) == 0 {
server := dynamic.UDPServer{}
loadBalancer.Servers = []dynamic.UDPServer{server}
}
if serverPort != "" {
port = serverPort
loadBalancer.Servers[0].Port = ""
}
if port == "" {
return errors.New("port is missing")
}
var servers []dynamic.UDPServer
for _, containerIP := range service.Containers {
servers = append(servers, dynamic.UDPServer{
Address: net.JoinHostPort(containerIP, port),
})
}
loadBalancer.Servers = servers
return nil
}
func (p *Provider) addServers(ctx context.Context, service rancherData, loadBalancer *dynamic.ServersLoadBalancer) error { func (p *Provider) addServers(ctx context.Context, service rancherData, loadBalancer *dynamic.ServersLoadBalancer) error {
log.FromContext(ctx).Debugf("Trying to add servers for service %s \n", service.Name) log.FromContext(ctx).Debugf("Trying to add servers for service %s \n", service.Name)

View file

@ -575,6 +575,51 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "udp with label",
containers: []rancherData{
{
Name: "Test",
Labels: map[string]string{
"traefik.udp.routers.foo.entrypoints": "mydns",
},
Port: "80/tcp",
Containers: []string{"127.0.0.1"},
Health: "",
State: "",
},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
Service: "Test",
EntryPoints: []string{"mydns"},
},
},
Services: map[string]*dynamic.UDPService{
"Test": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:80",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
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: "tcp with label without rule", desc: "tcp with label without rule",
containers: []rancherData{ containers: []rancherData{
@ -663,6 +708,52 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "udp with label and port",
containers: []rancherData{
{
Name: "Test",
Labels: map[string]string{
"traefik.udp.routers.foo.entrypoints": "mydns",
"traefik.udp.services.foo.loadbalancer.server.port": "8080",
},
Port: "80/tcp",
Containers: []string{"127.0.0.1"},
Health: "",
State: "",
},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
Service: "foo",
EntryPoints: []string{"mydns"},
},
},
Services: map[string]*dynamic.UDPService{
"foo": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:8080",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
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: "tcp with label and port and http service", desc: "tcp with label and port and http service",
containers: []rancherData{ containers: []rancherData{
@ -735,6 +826,75 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "udp with label and port and http service",
containers: []rancherData{
{
Name: "Test",
Labels: map[string]string{
"traefik.udp.routers.foo.entrypoints": "mydns",
"traefik.udp.services.foo.loadbalancer.server.port": "8080",
"traefik.http.services.Service1.loadbalancer.passhostheader": "true",
},
Port: "80/tcp",
Containers: []string{"127.0.0.1", "127.0.0.2"},
Health: "",
State: "",
},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
Service: "foo",
EntryPoints: []string{"mydns"},
},
},
Services: map[string]*dynamic.UDPService{
"foo": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:8080",
},
{
Address: "127.0.0.2:8080",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"Test": {
Service: "Service1",
Rule: "Host(`Test.traefik.wtf`)",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"Service1": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1:80",
},
{
URL: "http://127.0.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
},
},
},
},
{ {
desc: "tcp with label for tcp service", desc: "tcp with label for tcp service",
containers: []rancherData{ containers: []rancherData{
@ -776,6 +936,46 @@ func Test_buildConfiguration(t *testing.T) {
}, },
}, },
}, },
{
desc: "udp with label for tcp service",
containers: []rancherData{
{
Name: "Test",
Labels: map[string]string{
"traefik.udp.services.foo.loadbalancer.server.port": "8080",
},
Port: "80/tcp",
Containers: []string{"127.0.0.1"},
Health: "",
State: "",
},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{
"foo": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:8080",
},
},
},
},
},
},
TCP: &dynamic.TCPConfiguration{
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: "tcp with label for tcp service, with termination delay", desc: "tcp with label for tcp service, with termination delay",
containers: []rancherData{ containers: []rancherData{