From ec0075e0d0dcdd35e65bb7db361b8baf4b4e28b6 Mon Sep 17 00:00:00 2001 From: Fabian Gruber Date: Wed, 16 Dec 2020 12:32:03 +0100 Subject: [PATCH] Extend marathon port discovery to allow port names as identifier --- docs/content/user-guides/marathon.md | 20 +++++++++---- pkg/provider/marathon/builder_test.go | 3 +- pkg/provider/marathon/config.go | 41 ++++++++++++++++++++++++-- pkg/provider/marathon/config_test.go | 42 ++++++++++++++++++++++++++- 4 files changed, 95 insertions(+), 11 deletions(-) diff --git a/docs/content/user-guides/marathon.md b/docs/content/user-guides/marathon.md index 8fa26aecc..2b1836cf5 100644 --- a/docs/content/user-guides/marathon.md +++ b/docs/content/user-guides/marathon.md @@ -14,13 +14,21 @@ Traefik tries to detect the configured mode and route traffic to the right IP ad ## Port detection -Traefik also attempts to determine the right port (which is a [non-trivial matter in Marathon](https://mesosphere.github.io/marathon/docs/ports.html)). -Following is the order by which Traefik tries to identify the port (the first one that yields a positive result will be used): +Traefik also attempts to determine the right port (which is a [non-trivial matter in Marathon](https://mesosphere.github.io/marathon/docs/ports.html)) from the following sources: -1. A arbitrary port specified through the `traefik.http.services.serviceName.loadbalancer.server.port=8080` -1. The task port (possibly indexed through the `traefik.http.services.serviceName.loadbalancer.server.port=index:0` label, otherwise the first one). -1. The port from the application's `portDefinitions` field (possibly indexed through the `traefik.http.services.serviceName.loadbalancer.server.port=index:0` label, otherwise the first one). -1. The port from the application's `ipAddressPerTask` field (possibly indexed through the `traefik.http.services.serviceName.loadbalancer.server.port=index:0` label, otherwise the first one). +1. An arbitrary port specified through label `traefik.http.services.serviceName.loadbalancer.server.port=8080` +1. The task port. +1. The port from the application's `portDefinitions` field. +1. The port from the application's `ipAddressPerTask` field. + +### Port label syntax + +To select a port, you can either + +- specify the port directly: `traefik.http.services.serviceName.loadbalancer.server.port=8080` +- specify a port index: `traefik.http.services.serviceName.loadbalancer.server.port=index:0` +- specify a port name: `traefik.http.services.serviceName.loadbalancer.server.port=name:http` +- otherwise the first one is selected. ## Achieving high availability diff --git a/pkg/provider/marathon/builder_test.go b/pkg/provider/marathon/builder_test.go index a63758499..e18110787 100644 --- a/pkg/provider/marathon/builder_test.go +++ b/pkg/provider/marathon/builder_test.go @@ -53,10 +53,11 @@ func constraint(value string) func(*marathon.Application) { } } -func portDefinition(port int) func(*marathon.Application) { +func portDefinition(port int, name string) func(*marathon.Application) { return func(app *marathon.Application) { app.AddPortDefinition(marathon.PortDefinition{ Port: &port, + Name: name, }) } } diff --git a/pkg/provider/marathon/config.go b/pkg/provider/marathon/config.go index 3b8c94ad7..4f9216e9b 100644 --- a/pkg/provider/marathon/config.go +++ b/pkg/provider/marathon/config.go @@ -373,7 +373,7 @@ func getPort(task marathon.Task, app marathon.Application, serverPort string) (s // one of the available port. The first such found port is returned unless an // optional index is provided. func processPorts(app marathon.Application, task marathon.Task, serverPort string) (int, error) { - if len(serverPort) > 0 && !strings.HasPrefix(serverPort, "index:") { + if len(serverPort) > 0 && !(strings.HasPrefix(serverPort, "index:") || strings.HasPrefix(serverPort, "name:")) { port, err := strconv.Atoi(serverPort) if err != nil { return 0, err @@ -386,6 +386,17 @@ func processPorts(app marathon.Application, task marathon.Task, serverPort strin } } + if strings.HasPrefix(serverPort, "name:") { + name := strings.TrimPrefix(serverPort, "name:") + port := retrieveNamedPort(app, name) + + if port == 0 { + return 0, fmt.Errorf("no port with name %s", name) + } + + return port, nil + } + ports := retrieveAvailablePorts(app, task) if len(ports) == 0 { return 0, errors.New("no port found") @@ -393,8 +404,8 @@ func processPorts(app marathon.Application, task marathon.Task, serverPort strin portIndex := 0 if strings.HasPrefix(serverPort, "index:") { - split := strings.SplitN(serverPort, ":", 2) - index, err := strconv.Atoi(split[1]) + indexString := strings.TrimPrefix(serverPort, "index:") + index, err := strconv.Atoi(indexString) if err != nil { return 0, err } @@ -402,11 +413,35 @@ func processPorts(app marathon.Application, task marathon.Task, serverPort strin if index < 0 || index > len(ports)-1 { return 0, fmt.Errorf("index %d must be within range (0, %d)", index, len(ports)-1) } + portIndex = index } + return ports[portIndex], nil } +func retrieveNamedPort(app marathon.Application, name string) int { + // Using port definition if available + if app.PortDefinitions != nil && len(*app.PortDefinitions) > 0 { + for _, def := range *app.PortDefinitions { + if def.Port != nil && *def.Port > 0 && def.Name == name { + return *def.Port + } + } + } + + // If using IP-per-task using this port definition + if app.IPAddressPerTask != nil && app.IPAddressPerTask.Discovery != nil && len(*(app.IPAddressPerTask.Discovery.Ports)) > 0 { + for _, def := range *(app.IPAddressPerTask.Discovery.Ports) { + if def.Number > 0 && def.Name == name { + return def.Number + } + } + } + + return 0 +} + func retrieveAvailablePorts(app marathon.Application, task marathon.Task) []int { // Using default port configuration if len(task.Ports) > 0 { diff --git a/pkg/provider/marathon/config_test.go b/pkg/provider/marathon/config_test.go index f69b47530..872e18018 100644 --- a/pkg/provider/marathon/config_test.go +++ b/pkg/provider/marathon/config_test.go @@ -1941,13 +1941,53 @@ func TestGetServer(t *testing.T) { error: `unable to process ports for /app taskID: strconv.Atoi: parsing "aaa": invalid syntax`, }, }, + { + desc: "with port name", + provider: Provider{}, + app: application( + appID("/app"), + portDefinition(80, "fist-port"), + portDefinition(81, "second-port"), + portDefinition(82, "third-port"), + withTasks(localhostTask()), + ), + extraConf: configuration{}, + defaultServer: dynamic.Server{ + Scheme: "http", + Port: "name:third-port", + }, + expected: expected{ + server: dynamic.Server{ + URL: "http://localhost:82", + }, + }, + }, + { + desc: "with port name not found", + provider: Provider{}, + app: application( + appID("/app"), + portDefinition(80, "fist-port"), + portDefinition(81, "second-port"), + portDefinition(82, "third-port"), + withTasks(localhostTask()), + ), + extraConf: configuration{}, + defaultServer: dynamic.Server{ + Scheme: "http", + Port: "name:other-name", + }, + expected: expected{ + error: `unable to process ports for /app taskID: no port with name other-name`, + }, + }, { desc: "with application port and no task port", provider: Provider{}, app: application( appID("/app"), appPorts(80), - portDefinition(80), + portDefinition(80, "http"), withTasks(localhostTask()), ), extraConf: configuration{},