1
0
Fork 0

UDP support

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

View file

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

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

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

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

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

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

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

View file

@ -0,0 +1 @@
[]

View file

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

View file

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

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

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

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

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

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

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

View file

@ -0,0 +1 @@
[]

View file

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

View file

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

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

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

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

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

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

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

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

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

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

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

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

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

View file

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