From bb4de11c517dfa4a6f6ca446732f4b55f771cb49 Mon Sep 17 00:00:00 2001 From: Julien Salleyron Date: Thu, 20 Feb 2020 22:24:05 +0100 Subject: [PATCH] Add UDP in providers with labels --- .../routing/providers/consul-catalog.md | 44 ++++ docs/content/routing/providers/docker.md | 48 ++++ docs/content/routing/providers/marathon.md | 49 ++++ docs/content/routing/providers/rancher.md | 48 ++++ pkg/config/label/label.go | 3 +- pkg/config/label/label_test.go | 118 +++++++++- pkg/provider/configuration.go | 79 +++++++ pkg/provider/consulcatalog/config.go | 72 +++++- pkg/provider/consulcatalog/config_test.go | 212 ++++++++++++++++++ pkg/provider/docker/config.go | 75 ++++++- pkg/provider/docker/config_test.go | 200 ++++++++++++++--- pkg/provider/kv/kv_test.go | 40 ++++ pkg/provider/marathon/config.go | 99 +++++++- pkg/provider/marathon/config_test.go | 139 ++++++++++++ pkg/provider/rancher/config.go | 82 ++++++- pkg/provider/rancher/config_test.go | 200 +++++++++++++++++ 16 files changed, 1440 insertions(+), 68 deletions(-) diff --git a/docs/content/routing/providers/consul-catalog.md b/docs/content/routing/providers/consul-catalog.md index 22b4978d6..835a8899f 100644 --- a/docs/content/routing/providers/consul-catalog.md +++ b/docs/content/routing/providers/consul-catalog.md @@ -365,6 +365,50 @@ You can declare TCP Routers and/or Services using tags. 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..entrypoints`" + + See [entry points](../routers/index.md#entrypoints_2) for more information. + + ```yaml + traefik.udp.routers.myudprouter.entrypoints=ep1,ep2 + ``` + +??? info "`traefik.udp.routers..service`" + + See [service](../routers/index.md#services_1) for more information. + + ```yaml + traefik.udp.routers.myudprouter.service=myservice + ``` + +#### UDP Services + +??? info "`traefik.udp.services..loadbalancer.server.port`" + + Registers a port of the application. + + ```yaml + traefik.udp.services.myudpservice.loadbalancer.server.port=423 + ``` + ### Specific Provider Options #### `traefik.enable` diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md index f9b417eea..b0ad61431 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -508,6 +508,54 @@ You can declare TCP Routers and/or Services using labels. - "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..entrypoints`" + + See [entry points](../routers/index.md#entrypoints_2) for more information. + + ```yaml + - "traefik.udp.routers.myudprouter.entrypoints=ep1,ep2" + ``` + +??? info "`traefik.udp.routers..service`" + + See [service](../routers/index.md#services_1) for more information. + + ```yaml + - "traefik.udp.routers.myudprouter.service=myservice" + ``` + +#### UDP Services + +??? info "`traefik.udp.services..loadbalancer.server.port`" + + Registers a port of the application. + + ```yaml + - "traefik.udp.services.myudpservice.loadbalancer.server.port=423" + ``` + ### Specific Provider Options #### `traefik.enable` diff --git a/docs/content/routing/providers/marathon.md b/docs/content/routing/providers/marathon.md index 7bc79a6b7..3c1600a8d 100644 --- a/docs/content/routing/providers/marathon.md +++ b/docs/content/routing/providers/marathon.md @@ -405,6 +405,55 @@ You can declare TCP Routers and/or Services using labels. "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..entrypoints`" + + See [entry points](../routers/index.md#entrypoints_2) for more information. + + ```json + "traefik.udp.routers.myudprouter.entrypoints": "ep1,ep2" + ``` + +??? info "`traefik.udp.routers..service`" + + See [service](../routers/index.md#services_1) for more information. + + ```json + "traefik.udp.routers.myudprouter.service": "myservice" + ``` + +#### UDP Services + +??? info "`traefik.udp.services..loadbalancer.server.port`" + + Registers a port of the application. + + ```json + "traefik.udp.services.myudpservice.loadbalancer.server.port": "423" + ``` + ### Specific Provider Options #### `traefik.enable` diff --git a/docs/content/routing/providers/rancher.md b/docs/content/routing/providers/rancher.md index c0178a910..4b0cbd9bc 100644 --- a/docs/content/routing/providers/rancher.md +++ b/docs/content/routing/providers/rancher.md @@ -408,6 +408,54 @@ You can declare TCP Routers and/or Services using labels. - "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..entrypoints`" + + See [entry points](../routers/index.md#entrypoints_2) for more information. + + ```yaml + - "traefik.udp.routers.myudprouter.entrypoints=ep1,ep2" + ``` + +??? info "`traefik.udp.routers..service`" + + See [service](../routers/index.md#services_1) for more information. + + ```yaml + - "traefik.udp.routers.myudprouter.service=myservice" + ``` + +#### UDP Services + +??? info "`traefik.udp.services..loadbalancer.server.port`" + + Registers a port of the application. + + ```yaml + - "traefik.udp.services.myudpservice.loadbalancer.server.port=423" + ``` + ### Specific Provider Options #### `traefik.enable` diff --git a/pkg/config/label/label.go b/pkg/config/label/label.go index c3fcc94fd..e53410115 100644 --- a/pkg/config/label/label.go +++ b/pkg/config/label/label.go @@ -11,9 +11,10 @@ func DecodeConfiguration(labels map[string]string) (*dynamic.Configuration, erro conf := &dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{}, 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 { return nil, err } diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go index 219270c54..b4d74d789 100644 --- a/pkg/config/label/label_test.go +++ b/pkg/config/label/label_test.go @@ -177,6 +177,13 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.tcp.services.Service0.loadbalancer.TerminationDelay": "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", } 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{ Routers: map[string]*dynamic.Router{ "Router0": { @@ -627,6 +672,7 @@ func TestEncodeConfiguration(t *testing.T) { Port: "42", }, }, + TerminationDelay: func(i int) *int { return &i }(42), }, }, "Service1": { @@ -636,6 +682,45 @@ func TestEncodeConfiguration(t *testing.T) { 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", + }, + }, }, }, }, @@ -1147,18 +1232,27 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Services.Service1.LoadBalancer.server.Scheme": "foobar", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Headers.name0": "foobar", - "traefik.TCP.Routers.Router0.Rule": "foobar", - "traefik.TCP.Routers.Router0.EntryPoints": "foobar, fiibar", - "traefik.TCP.Routers.Router0.Service": "foobar", - "traefik.TCP.Routers.Router0.TLS.Passthrough": "false", - "traefik.TCP.Routers.Router0.TLS.Options": "foo", - "traefik.TCP.Routers.Router1.Rule": "foobar", - "traefik.TCP.Routers.Router1.EntryPoints": "foobar, fiibar", - "traefik.TCP.Routers.Router1.Service": "foobar", - "traefik.TCP.Routers.Router1.TLS.Passthrough": "false", - "traefik.TCP.Routers.Router1.TLS.Options": "foo", - "traefik.TCP.Services.Service0.LoadBalancer.server.Port": "42", - "traefik.TCP.Services.Service1.LoadBalancer.server.Port": "42", + "traefik.TCP.Routers.Router0.Rule": "foobar", + "traefik.TCP.Routers.Router0.EntryPoints": "foobar, fiibar", + "traefik.TCP.Routers.Router0.Service": "foobar", + "traefik.TCP.Routers.Router0.TLS.Passthrough": "false", + "traefik.TCP.Routers.Router0.TLS.Options": "foo", + "traefik.TCP.Routers.Router1.Rule": "foobar", + "traefik.TCP.Routers.Router1.EntryPoints": "foobar, fiibar", + "traefik.TCP.Routers.Router1.Service": "foobar", + "traefik.TCP.Routers.Router1.TLS.Passthrough": "false", + "traefik.TCP.Routers.Router1.TLS.Options": "foo", + "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.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 { diff --git a/pkg/provider/configuration.go b/pkg/provider/configuration.go index 86ad8f449..083f6a2c7 100644 --- a/pkg/provider/configuration.go +++ b/pkg/provider/configuration.go @@ -46,6 +46,12 @@ func Merge(ctx context.Context, configurations map[string]*dynamic.Configuration routersTCPToDelete := map[string]struct{}{} routersTCP := map[string][]string{} + servicesUDPToDelete := map[string]struct{}{} + servicesUDP := map[string][]string{} + + routersUDPToDelete := map[string]struct{}{} + routersUDP := map[string][]string{} + middlewaresToDelete := map[string]struct{}{} 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 { middlewares[middlewareName] = append(middlewares[middlewareName], root) 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) } + 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 { logger.WithField(log.MiddlewareName, 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 } +// 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. func AddRouterTCP(configuration *dynamic.TCPConfiguration, routerName string, router *dynamic.TCPRouter) bool { 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) } +// 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. func AddService(configuration *dynamic.HTTPConfiguration, serviceName string, service *dynamic.Service) bool { 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. func BuildRouterConfiguration(ctx context.Context, configuration *dynamic.HTTPConfiguration, defaultRouterName string, defaultRuleTpl *template.Template, model interface{}) { if len(configuration.Routers) == 0 { diff --git a/pkg/provider/consulcatalog/config.go b/pkg/provider/consulcatalog/config.go index 1d0407eea..151b31df5 100644 --- a/pkg/provider/consulcatalog/config.go +++ b/pkg/provider/consulcatalog/config.go @@ -33,21 +33,34 @@ func (p *Provider) buildConfiguration(ctx context.Context, items []itemData) *dy continue } + var tcpOrUDP bool if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 { + tcpOrUDP = true + err := p.buildTCPServiceConfiguration(ctxSvc, item, confFromLabel.TCP) if err != nil { logger.Error(err) continue } - provider.BuildTCPRouterConfiguration(ctxSvc, confFromLabel.TCP) + } - if len(confFromLabel.HTTP.Routers) == 0 && - len(confFromLabel.HTTP.Middlewares) == 0 && - len(confFromLabel.HTTP.Services) == 0 { - configurations[svcName] = confFromLabel + 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.Services) == 0 { + configurations[svcName] = confFromLabel + continue } err = p.buildServiceConfiguration(ctxSvc, item, confFromLabel.HTTP) @@ -121,6 +134,28 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, item itemDa 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 { if len(configuration.Services) == 0 { configuration.Services = make(map[string]*dynamic.Service) @@ -171,6 +206,33 @@ func (p *Provider) addServerTCP(ctx context.Context, item itemData, loadBalancer 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 { if loadBalancer == nil { return errors.New("load-balancer is not defined") diff --git a/pkg/provider/consulcatalog/config_test.go b/pkg/provider/consulcatalog/config_test.go index fce1bd88f..7e36e7fcf 100644 --- a/pkg/provider/consulcatalog/config_test.go +++ b/pkg/provider/consulcatalog/config_test.go @@ -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", 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", 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", 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", items: []itemData{ diff --git a/pkg/provider/docker/config.go b/pkg/provider/docker/config.go index 3108e0c3d..dc7eb4f95 100644 --- a/pkg/provider/docker/config.go +++ b/pkg/provider/docker/config.go @@ -34,19 +34,34 @@ func (p *Provider) buildConfiguration(ctx context.Context, containersInspected [ continue } + var tcpOrUDP bool if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 { + tcpOrUDP = true + err := p.buildTCPServiceConfiguration(ctxContainer, container, confFromLabel.TCP) if err != nil { logger.Error(err) continue } provider.BuildTCPRouterConfiguration(ctxContainer, confFromLabel.TCP) - if len(confFromLabel.HTTP.Routers) == 0 && - len(confFromLabel.HTTP.Middlewares) == 0 && - len(confFromLabel.HTTP.Services) == 0 { - configurations[containerName] = confFromLabel + } + + 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.Services) == 0 { + configurations[containerName] = confFromLabel + continue } err = p.buildServiceConfiguration(ctxContainer, container, confFromLabel.HTTP) @@ -96,6 +111,28 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container d 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 { serviceName := getServiceName(container) @@ -175,6 +212,36 @@ func (p *Provider) addServerTCP(ctx context.Context, container dockerData, loadB 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 { if loadBalancer == nil { return errors.New("load-balancer is not defined") diff --git a/pkg/provider/docker/config_test.go b/pkg/provider/docker/config_test.go index beeabd9b2..1617bbd8b 100644 --- a/pkg/provider/docker/config_test.go +++ b/pkg/provider/docker/config_test.go @@ -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", 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", 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{ { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.tcp.routers.foo.rule": "HostSNI(`foo.bar`)", - "traefik.tcp.routers.foo.tls": "true", - "traefik.tcp.services.foo.loadbalancer.server.port": "8080", + "traefik.udp.routers.foo.entrypoints": "mydns", + "traefik.udp.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", }, NetworkSettings: networkSettings{ @@ -2520,9 +2664,8 @@ func Test_buildConfiguration(t *testing.T) { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.tcp.routers.foo.rule": "HostSNI(`foo.bar`)", - "traefik.tcp.routers.foo.tls": "true", - "traefik.tcp.services.foo.loadbalancer.server.port": "8080", + "traefik.udp.routers.foo.entrypoints": "mydns", + "traefik.udp.services.foo.loadbalancer.server.port": "8080", "traefik.http.services.Service1.loadbalancer.passhostheader": "true", }, NetworkSettings: networkSettings{ @@ -2539,18 +2682,17 @@ func Test_buildConfiguration(t *testing.T) { }, }, expected: &dynamic.Configuration{ - TCP: &dynamic.TCPConfiguration{ - Routers: map[string]*dynamic.TCPRouter{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ "foo": { - Service: "foo", - Rule: "HostSNI(`foo.bar`)", - TLS: &dynamic.RouterTCPTLSConfig{}, + Service: "foo", + EntryPoints: []string{"mydns"}, }, }, - Services: map[string]*dynamic.TCPService{ + Services: map[string]*dynamic.UDPService{ "foo": { - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ { Address: "127.0.0.1:8080", }, @@ -2558,14 +2700,13 @@ func Test_buildConfiguration(t *testing.T) { Address: "127.0.0.2:8080", }, }, - TerminationDelay: Int(100), }, }, }, }, - UDP: &dynamic.UDPConfiguration{ - Routers: map[string]*dynamic.UDPRouter{}, - Services: map[string]*dynamic.UDPService{}, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ 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{ { ServiceName: "Test", Name: "Test", Labels: map[string]string{ - "traefik.tcp.services.foo.loadbalancer.server.port": "8080", + "traefik.udp.services.foo.loadbalancer.server.port": "8080", }, NetworkSettings: networkSettings{ Ports: nat.PortMap{ @@ -2616,24 +2757,23 @@ func Test_buildConfiguration(t *testing.T) { }, }, 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{ "foo": { - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{ + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ { Address: "127.0.0.1:8080", }, }, - TerminationDelay: Int(100), }, }, }, }, - UDP: &dynamic.UDPConfiguration{ - Routers: map[string]*dynamic.UDPRouter{}, - Services: map[string]*dynamic.UDPService{}, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, }, HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{}, diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go index a00ae8e28..3f6cc7fca 100644 --- a/pkg/provider/kv/kv_test.go +++ b/pkg/provider/kv/kv_test.go @@ -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/1/name": "foobar", "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/maxVersion": "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{ Certificates: []*tls.CertAndStores{ { diff --git a/pkg/provider/marathon/config.go b/pkg/provider/marathon/config.go index 9e76640e6..6d16efa7c 100644 --- a/pkg/provider/marathon/config.go +++ b/pkg/provider/marathon/config.go @@ -49,21 +49,36 @@ func (p *Provider) buildConfiguration(ctx context.Context, applications *maratho continue } + var tcpOrUDP bool if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 { + tcpOrUDP = true + err := p.buildTCPServiceConfiguration(ctxApp, app, extraConf, confFromLabel.TCP) if err != nil { logger.Error(err) continue } provider.BuildTCPRouterConfiguration(ctxApp, confFromLabel.TCP) - if len(confFromLabel.HTTP.Routers) == 0 && - len(confFromLabel.HTTP.Middlewares) == 0 && - len(confFromLabel.HTTP.Services) == 0 { - configurations[app.ID] = confFromLabel - continue + } + + 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.Services) == 0 { + configurations[app.ID] = confFromLabel + continue + } + err = p.buildServiceConfiguration(ctxApp, app, extraConf, confFromLabel.HTTP) if err != nil { logger.Error(err) @@ -116,14 +131,15 @@ func (p *Provider) buildServiceConfiguration(ctx context.Context, app marathon.A } for _, task := range app.Tasks { - if p.taskFilter(ctx, *task, app) { - server, err := p.getServer(app, *task, extraConf, defaultServer) - if err != nil { - log.FromContext(appCtx).Errorf("Skip task: %v", err) - continue - } - servers = append(servers, server) + if !p.taskFilter(ctx, *task, app) { + continue } + server, err := p.getServer(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) @@ -175,6 +191,47 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, app maratho 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 { logger := log.FromContext(ctx) @@ -229,6 +286,24 @@ func (p *Provider) getTCPServer(app marathon.Application, task marathon.Task, ex 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) { host, err := p.getServerHost(task, app, extraConf) if len(host) == 0 { diff --git a/pkg/provider/marathon/config_test.go b/pkg/provider/marathon/config_test.go index f6d93635b..cb553b47c 100644 --- a/pkg/provider/marathon/config_test.go +++ b/pkg/provider/marathon/config_test.go @@ -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", 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", 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 { diff --git a/pkg/provider/rancher/config.go b/pkg/provider/rancher/config.go index c37cef01c..4f7fd8685 100644 --- a/pkg/provider/rancher/config.go +++ b/pkg/provider/rancher/config.go @@ -32,19 +32,34 @@ func (p *Provider) buildConfiguration(ctx context.Context, services []rancherDat continue } + var tcpOrUDP bool if len(confFromLabel.TCP.Routers) > 0 || len(confFromLabel.TCP.Services) > 0 { + tcpOrUDP = true + err := p.buildTCPServiceConfiguration(ctxService, service, confFromLabel.TCP) if err != nil { logger.Error(err) continue } provider.BuildTCPRouterConfiguration(ctxService, confFromLabel.TCP) - if len(confFromLabel.HTTP.Routers) == 0 && - len(confFromLabel.HTTP.Middlewares) == 0 && - len(confFromLabel.HTTP.Services) == 0 { - configurations[service.Name] = confFromLabel + } + + 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.Services) == 0 { + configurations[service.Name] = confFromLabel + continue } err = p.buildServiceConfiguration(ctx, service, confFromLabel.HTTP) @@ -91,6 +106,28 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, service ran 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 { serviceName := service.Name @@ -182,6 +219,43 @@ func (p *Provider) addServerTCP(ctx context.Context, service rancherData, loadBa 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 { log.FromContext(ctx).Debugf("Trying to add servers for service %s \n", service.Name) diff --git a/pkg/provider/rancher/config_test.go b/pkg/provider/rancher/config_test.go index c1b3f0a84..4c834cfc0 100644 --- a/pkg/provider/rancher/config_test.go +++ b/pkg/provider/rancher/config_test.go @@ -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", 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", 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", 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", containers: []rancherData{