Avoid recursion with services

This commit is contained in:
Julien Salleyron
2026-01-26 10:28:04 +01:00
committed by GitHub
parent 9ac5d3ac1c
commit 85cd5485b7
8 changed files with 249 additions and 81 deletions
+12 -12
View File
@@ -451,13 +451,13 @@ func TestPrometheusMetricRemoval(t *testing.T) {
th.WithRouter("foo@providerName", th.WithServiceName("bar")), th.WithRouter("foo@providerName", th.WithServiceName("bar")),
th.WithRouter("router2", th.WithServiceName("bar@providerName")), th.WithRouter("router2", th.WithServiceName("bar@providerName")),
), ),
th.WithLoadBalancerServices( th.WithServices(
th.WithService("bar@providerName", th.WithServers( th.WithService("bar@providerName", th.WithServiceServersLoadBalancer(th.WithServers(
th.WithServer("http://localhost:9000"), th.WithServer("http://localhost:9000"),
th.WithServer("http://localhost:9999"), th.WithServer("http://localhost:9999"),
th.WithServer("http://localhost:9998"), th.WithServer("http://localhost:9998"),
)), ))),
th.WithService("service1", th.WithServers(th.WithServer("http://localhost:9000"))), th.WithService("service1", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9000")))),
), ),
), ),
} }
@@ -467,8 +467,8 @@ func TestPrometheusMetricRemoval(t *testing.T) {
th.WithRouters( th.WithRouters(
th.WithRouter("foo@providerName", th.WithServiceName("bar")), th.WithRouter("foo@providerName", th.WithServiceName("bar")),
), ),
th.WithLoadBalancerServices( th.WithServices(
th.WithService("bar@providerName", th.WithServers(th.WithServer("http://localhost:9000"))), th.WithService("bar@providerName", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9000")))),
), ),
), ),
} }
@@ -538,8 +538,8 @@ func TestPrometheusMetricRemoveEndpointForRecoveredService(t *testing.T) {
conf1 := dynamic.Configuration{ conf1 := dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithLoadBalancerServices( th.WithServices(
th.WithService("service1", th.WithServers(th.WithServer("http://localhost:9000"))), th.WithService("service1", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9000")))),
), ),
), ),
} }
@@ -550,8 +550,8 @@ func TestPrometheusMetricRemoveEndpointForRecoveredService(t *testing.T) {
conf3 := dynamic.Configuration{ conf3 := dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithLoadBalancerServices( th.WithServices(
th.WithService("service1", th.WithServers(th.WithServer("http://localhost:9001"))), th.WithService("service1", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9001")))),
), ),
), ),
} }
@@ -577,8 +577,8 @@ func TestPrometheusRemovedMetricsReset(t *testing.T) {
conf1 := dynamic.Configuration{ conf1 := dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithLoadBalancerServices(th.WithService("service", th.WithServices(
th.WithServers(th.WithServer("http://localhost:9000"))), th.WithService("service", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer("http://localhost:9000")))),
), ),
), ),
} }
+67 -25
View File
@@ -86,7 +86,7 @@ func TestNewConfigurationWatcher(t *testing.T) {
th.WithEntryPoints("e"), th.WithEntryPoints("e"),
th.WithServiceName("scv"))), th.WithServiceName("scv"))),
th.WithMiddlewares(), th.WithMiddlewares(),
th.WithLoadBalancerServices(), th.WithServices(),
), ),
TCP: &dynamic.TCPConfiguration{ TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{}, Routers: map[string]*dynamic.TCPRouter{},
@@ -123,7 +123,9 @@ func TestWaitForRequiredProvider(t *testing.T) {
config := &dynamic.Configuration{ config := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar")), th.WithServices(
th.WithService("bar", th.WithServiceServersLoadBalancer()),
),
), ),
} }
@@ -167,14 +169,18 @@ func TestIgnoreTransientConfiguration(t *testing.T) {
config := &dynamic.Configuration{ config := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar")), th.WithServices(
th.WithService("bar", th.WithServiceServersLoadBalancer()),
),
), ),
} }
expectedConfig := dynamic.Configuration{ expectedConfig := dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar@mock")), th.WithServices(
th.WithService("bar@mock", th.WithServiceServersLoadBalancer()),
),
th.WithMiddlewares(), th.WithMiddlewares(),
), ),
TCP: &dynamic.TCPConfiguration{ TCP: &dynamic.TCPConfiguration{
@@ -197,7 +203,9 @@ func TestIgnoreTransientConfiguration(t *testing.T) {
expectedConfig3 := dynamic.Configuration{ expectedConfig3 := dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar-config3@mock")), th.WithServices(
th.WithService("bar-config3@mock", th.WithServiceServersLoadBalancer()),
),
th.WithMiddlewares(), th.WithMiddlewares(),
), ),
TCP: &dynamic.TCPConfiguration{ TCP: &dynamic.TCPConfiguration{
@@ -220,14 +228,18 @@ func TestIgnoreTransientConfiguration(t *testing.T) {
config2 := &dynamic.Configuration{ config2 := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("baz", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("baz", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("toto")), th.WithServices(
th.WithService("toto", th.WithServiceServersLoadBalancer()),
),
), ),
} }
config3 := &dynamic.Configuration{ config3 := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar-config3")), th.WithServices(
th.WithService("bar-config3", th.WithServiceServersLoadBalancer()),
),
), ),
} }
watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{}, "") watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{}, "")
@@ -311,7 +323,9 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) {
Configuration: &dynamic.Configuration{ Configuration: &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i), th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i), th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar")), th.WithServices(
th.WithService("bar", th.WithServiceServersLoadBalancer()),
),
), ),
}, },
}) })
@@ -371,7 +385,9 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) {
Configuration: &dynamic.Configuration{ Configuration: &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar")), th.WithServices(
th.WithService("bar", th.WithServiceServersLoadBalancer()),
),
), ),
}, },
} }
@@ -403,14 +419,18 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
configuration := &dynamic.Configuration{ configuration := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar")), th.WithServices(
th.WithService("bar", th.WithServiceServersLoadBalancer()),
),
), ),
} }
transientConfiguration := &dynamic.Configuration{ transientConfiguration := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bad")), th.WithServices(
th.WithService("bad", th.WithServiceServersLoadBalancer()),
),
), ),
} }
@@ -442,7 +462,9 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
expected := dynamic.Configuration{ expected := dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar@mock")), th.WithServices(
th.WithService("bar@mock", th.WithServiceServersLoadBalancer()),
),
th.WithMiddlewares(), th.WithMiddlewares(),
), ),
TCP: &dynamic.TCPConfiguration{ TCP: &dynamic.TCPConfiguration{
@@ -471,14 +493,18 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) {
configuration := &dynamic.Configuration{ configuration := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar")), th.WithServices(
th.WithService("bar", th.WithServiceServersLoadBalancer()),
),
), ),
} }
transientConfiguration := &dynamic.Configuration{ transientConfiguration := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bad")), th.WithServices(
th.WithService("bad", th.WithServiceServersLoadBalancer()),
),
), ),
} }
@@ -531,7 +557,9 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) {
expected := dynamic.Configuration{ expected := dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo@mock", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar@mock")), th.WithServices(
th.WithService("bar@mock", th.WithServiceServersLoadBalancer()),
),
th.WithMiddlewares(), th.WithMiddlewares(),
), ),
TCP: &dynamic.TCPConfiguration{ TCP: &dynamic.TCPConfiguration{
@@ -570,7 +598,9 @@ func TestApplyConfigUnderStress(t *testing.T) {
case watcher.allProvidersConfigs <- dynamic.Message{ProviderName: "mock", Configuration: &dynamic.Configuration{ case watcher.allProvidersConfigs <- dynamic.Message{ProviderName: "mock", Configuration: &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i), th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo"+strconv.Itoa(i), th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar")), th.WithServices(
th.WithService("bar", th.WithServiceServersLoadBalancer()),
),
), ),
}}: }}:
} }
@@ -605,28 +635,36 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) {
configuration := &dynamic.Configuration{ configuration := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar")), th.WithServices(
th.WithService("bar", th.WithServiceServersLoadBalancer()),
),
), ),
} }
transientConfiguration := &dynamic.Configuration{ transientConfiguration := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("bad", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bad")), th.WithServices(
th.WithService("bad", th.WithServiceServersLoadBalancer()),
),
), ),
} }
transientConfiguration2 := &dynamic.Configuration{ transientConfiguration2 := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("bad2", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("bad2", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bad2")), th.WithServices(
th.WithService("bad2", th.WithServiceServersLoadBalancer()),
),
), ),
} }
finalConfiguration := &dynamic.Configuration{ finalConfiguration := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("final", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("final", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("final")), th.WithServices(
th.WithService("final", th.WithServiceServersLoadBalancer()),
),
), ),
} }
@@ -665,7 +703,9 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) {
expected := dynamic.Configuration{ expected := dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("final@mock", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("final@mock", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("final@mock")), th.WithServices(
th.WithService("final@mock", th.WithServiceServersLoadBalancer()),
),
th.WithMiddlewares(), th.WithMiddlewares(),
), ),
TCP: &dynamic.TCPConfiguration{ TCP: &dynamic.TCPConfiguration{
@@ -696,7 +736,9 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
configuration := &dynamic.Configuration{ configuration := &dynamic.Configuration{
HTTP: th.BuildConfiguration( HTTP: th.BuildConfiguration(
th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))), th.WithRouters(th.WithRouter("foo", th.WithEntryPoints("ep"))),
th.WithLoadBalancerServices(th.WithService("bar")), th.WithServices(
th.WithService("bar", th.WithServiceServersLoadBalancer()),
),
), ),
} }
@@ -729,9 +771,9 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
th.WithRouter("foo@mock", th.WithEntryPoints("ep")), th.WithRouter("foo@mock", th.WithEntryPoints("ep")),
th.WithRouter("foo@mock2", th.WithEntryPoints("ep")), th.WithRouter("foo@mock2", th.WithEntryPoints("ep")),
), ),
th.WithLoadBalancerServices( th.WithServices(
th.WithService("bar@mock"), th.WithService("bar@mock", th.WithServiceServersLoadBalancer()),
th.WithService("bar@mock2"), th.WithService("bar@mock2", th.WithServiceServersLoadBalancer()),
), ),
th.WithMiddlewares(), th.WithMiddlewares(),
), ),
+2 -20
View File
@@ -6,8 +6,6 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"reflect" "reflect"
"slices"
"strings"
"github.com/containous/alice" "github.com/containous/alice"
"github.com/traefik/traefik/v2/pkg/config/runtime" "github.com/traefik/traefik/v2/pkg/config/runtime"
@@ -33,12 +31,7 @@ import (
"github.com/traefik/traefik/v2/pkg/middlewares/stripprefixregex" "github.com/traefik/traefik/v2/pkg/middlewares/stripprefixregex"
"github.com/traefik/traefik/v2/pkg/middlewares/tracing" "github.com/traefik/traefik/v2/pkg/middlewares/tracing"
"github.com/traefik/traefik/v2/pkg/server/provider" "github.com/traefik/traefik/v2/pkg/server/provider"
) "github.com/traefik/traefik/v2/pkg/server/recursion"
type middlewareStackType int
const (
middlewareStackKey middlewareStackType = iota
) )
// Builder the middleware builder. // Builder the middleware builder.
@@ -70,7 +63,7 @@ func (b *Builder) BuildChain(ctx context.Context, middlewares []string) *alice.C
} }
var err error var err error
if constructorContext, err = checkRecursion(constructorContext, middlewareName); err != nil { if constructorContext, err = recursion.CheckRecursion(constructorContext, "middleware", middlewareName); err != nil {
b.configs[middlewareName].AddError(err, true) b.configs[middlewareName].AddError(err, true)
return nil, err return nil, err
} }
@@ -93,17 +86,6 @@ func (b *Builder) BuildChain(ctx context.Context, middlewares []string) *alice.C
return &chain return &chain
} }
func checkRecursion(ctx context.Context, middlewareName string) (context.Context, error) {
currentStack, ok := ctx.Value(middlewareStackKey).([]string)
if !ok {
currentStack = []string{}
}
if slices.Contains(currentStack, middlewareName) {
return ctx, fmt.Errorf("could not instantiate middleware %s: recursion detected in %s", middlewareName, strings.Join(append(currentStack, middlewareName), "->"))
}
return context.WithValue(ctx, middlewareStackKey, append(currentStack, middlewareName)), nil
}
// it is the responsibility of the caller to make sure that b.configs[middlewareName].Middleware exists. // it is the responsibility of the caller to make sure that b.configs[middlewareName].Middleware exists.
func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (alice.Constructor, error) { func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (alice.Constructor, error) {
config := b.configs[middlewareName] config := b.configs[middlewareName]
+6 -5
View File
@@ -172,7 +172,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) {
}, },
}, },
}, },
expectedError: errors.New("could not instantiate middleware m1: recursion detected in m1->m2->m3->m1"), expectedError: errors.New("could not instantiate middleware m1: recursion detected in middleware:m1->middleware:m2->middleware:m3->middleware:m1"),
}, },
{ {
desc: "Detects recursion in Middleware chain", desc: "Detects recursion in Middleware chain",
@@ -197,9 +197,10 @@ func TestBuilder_BuildChainWithContext(t *testing.T) {
}, },
}, },
}, },
expectedError: errors.New("could not instantiate middleware m1@provider: recursion detected in m1@provider->m2@provider2->m3@provider->m1@provider"), expectedError: errors.New("could not instantiate middleware m1@provider: recursion detected in middleware:m1@provider->middleware:m2@provider2->middleware:m3@provider->middleware:m1@provider"),
}, },
{ {
desc: "Detects recursion in Middleware chain",
buildChain: []string{"ok", "m0"}, buildChain: []string{"ok", "m0"},
configuration: map[string]*dynamic.Middleware{ configuration: map[string]*dynamic.Middleware{
"ok": { "ok": {
@@ -211,7 +212,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) {
}, },
}, },
}, },
expectedError: errors.New("could not instantiate middleware m0: recursion detected in m0->m0"), expectedError: errors.New("could not instantiate middleware m0: recursion detected in middleware:m0->middleware:m0"),
}, },
{ {
desc: "Detects MiddlewareChain that references a Chain that references a Chain with a missing middleware", desc: "Detects MiddlewareChain that references a Chain that references a Chain with a missing middleware",
@@ -238,7 +239,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) {
}, },
}, },
}, },
expectedError: errors.New("could not instantiate middleware m2: recursion detected in m0->m1->m2->m3->m2"), expectedError: errors.New("could not instantiate middleware m2: recursion detected in middleware:m0->middleware:m1->middleware:m2->middleware:m3->middleware:m2"),
}, },
{ {
desc: "--", desc: "--",
@@ -250,7 +251,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) {
}, },
}, },
}, },
expectedError: errors.New("could not instantiate middleware m0: recursion detected in m0->m0"), expectedError: errors.New("could not instantiate middleware m0: recursion detected in middleware:m0->middleware:m0"),
}, },
} }
+26
View File
@@ -0,0 +1,26 @@
package recursion
import (
"context"
"fmt"
"slices"
"strings"
)
type stackType int
const (
stackKey stackType = iota
)
func CheckRecursion(ctx context.Context, itemType, itemName string) (context.Context, error) {
currentStack, ok := ctx.Value(stackKey).([]string)
if !ok {
currentStack = []string{}
}
name := itemType + ":" + itemName
if slices.Contains(currentStack, name) {
return ctx, fmt.Errorf("could not instantiate %s %s: recursion detected in %s", itemType, itemName, strings.Join(append(currentStack, name), "->"))
}
return context.WithValue(ctx, stackKey, append(currentStack, name)), nil
}
+68 -10
View File
@@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/config/runtime" "github.com/traefik/traefik/v2/pkg/config/runtime"
"github.com/traefik/traefik/v2/pkg/config/static" "github.com/traefik/traefik/v2/pkg/config/static"
@@ -43,8 +44,8 @@ func TestReuseService(t *testing.T) {
th.WithMiddlewares(th.WithMiddleware("basicauth", th.WithMiddlewares(th.WithMiddleware("basicauth",
th.WithBasicAuth(&dynamic.BasicAuth{Users: []string{"foo:bar"}}), th.WithBasicAuth(&dynamic.BasicAuth{Users: []string{"foo:bar"}}),
)), )),
th.WithLoadBalancerServices(th.WithService("bar", th.WithServices(
th.WithServers(th.WithServer(testServer.URL))), th.WithService("bar", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer(testServer.URL)))),
), ),
) )
@@ -91,8 +92,8 @@ func TestServerResponseEmptyBackend(t *testing.T) {
th.WithServiceName("bar"), th.WithServiceName("bar"),
th.WithRule(routeRule)), th.WithRule(routeRule)),
), ),
th.WithLoadBalancerServices(th.WithService("bar", th.WithServices(
th.WithServers(th.WithServer(testServerURL))), th.WithService("bar", th.WithServiceServersLoadBalancer(th.WithServers(th.WithServer(testServerURL)))),
), ),
) )
}, },
@@ -114,7 +115,9 @@ func TestServerResponseEmptyBackend(t *testing.T) {
th.WithServiceName("bar"), th.WithServiceName("bar"),
th.WithRule(routeRule)), th.WithRule(routeRule)),
), ),
th.WithLoadBalancerServices(th.WithService("bar")), th.WithServices(
th.WithService("bar", th.WithServiceServersLoadBalancer()),
),
) )
}, },
expectedStatusCode: http.StatusServiceUnavailable, expectedStatusCode: http.StatusServiceUnavailable,
@@ -128,8 +131,8 @@ func TestServerResponseEmptyBackend(t *testing.T) {
th.WithServiceName("bar"), th.WithServiceName("bar"),
th.WithRule(routeRule)), th.WithRule(routeRule)),
), ),
th.WithLoadBalancerServices(th.WithService("bar", th.WithServices(
th.WithSticky("test")), th.WithService("bar", th.WithServiceServersLoadBalancer(th.WithSticky("test"))),
), ),
) )
}, },
@@ -144,7 +147,9 @@ func TestServerResponseEmptyBackend(t *testing.T) {
th.WithServiceName("bar"), th.WithServiceName("bar"),
th.WithRule(routeRule)), th.WithRule(routeRule)),
), ),
th.WithLoadBalancerServices(th.WithService("bar")), th.WithServices(
th.WithService("bar", th.WithServiceServersLoadBalancer()),
),
) )
}, },
expectedStatusCode: http.StatusServiceUnavailable, expectedStatusCode: http.StatusServiceUnavailable,
@@ -158,8 +163,8 @@ func TestServerResponseEmptyBackend(t *testing.T) {
th.WithServiceName("bar"), th.WithServiceName("bar"),
th.WithRule(routeRule)), th.WithRule(routeRule)),
), ),
th.WithLoadBalancerServices(th.WithService("bar", th.WithServices(
th.WithSticky("test")), th.WithService("bar", th.WithServiceServersLoadBalancer(th.WithSticky("test"))),
), ),
) )
}, },
@@ -241,3 +246,56 @@ func TestInternalServices(t *testing.T) {
assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code") assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code")
} }
func TestRecursionService(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
defer testServer.Close()
staticConfig := static.Configuration{
EntryPoints: map[string]*static.EntryPoint{
"web": {},
},
}
dynamicConfigs := th.BuildConfiguration(
th.WithRouters(
th.WithRouter("foo@provider1",
th.WithEntryPoints("web"),
th.WithServiceName("bar"),
th.WithRule("Path(`/ok`)")),
),
th.WithMiddlewares(th.WithMiddleware("customerror",
th.WithErrorPage(&dynamic.ErrorPage{Service: "bar"}),
)),
th.WithServices(
th.WithService("bar@provider1", th.WithServiceWRR(th.WithWRRServices(th.WithWRRService("foo")))),
th.WithService("foo@provider1", th.WithServiceWRR(th.WithWRRServices(th.WithWRRService("bar")))),
),
)
roundTripperManager := service.NewRoundTripperManager()
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager, nil)
tlsManager := tls.NewManager()
voidRegistry := metrics.NewVoidRegistry()
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(voidRegistry, nil, nil), nil, voidRegistry)
rtConf := runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs})
entryPointsHandlers, _ := factory.CreateRouters(rtConf)
// Test that the /ok path returns a status 404.
responseRecorderOk := &httptest.ResponseRecorder{}
requestOk := httptest.NewRequest(http.MethodGet, testServer.URL+"/ok", nil)
entryPointsHandlers["web"].GetHTTPHandler().ServeHTTP(responseRecorderOk, requestOk)
assert.Equal(t, http.StatusNotFound, responseRecorderOk.Result().StatusCode, "status code")
require.NotNil(t, rtConf.Routers["foo@provider1"])
assert.Contains(t, rtConf.Routers["foo@provider1"].Err, "could not instantiate service bar@provider1: recursion detected in service:bar@provider1->service:foo@provider1->service:bar@provider1")
require.NotNil(t, rtConf.Services["bar@provider1"])
assert.Contains(t, rtConf.Services["bar@provider1"].Err, "could not instantiate service bar@provider1: recursion detected in service:bar@provider1->service:foo@provider1->service:bar@provider1")
}
+7
View File
@@ -25,6 +25,7 @@ import (
"github.com/traefik/traefik/v2/pkg/safe" "github.com/traefik/traefik/v2/pkg/safe"
"github.com/traefik/traefik/v2/pkg/server/cookie" "github.com/traefik/traefik/v2/pkg/server/cookie"
"github.com/traefik/traefik/v2/pkg/server/provider" "github.com/traefik/traefik/v2/pkg/server/provider"
"github.com/traefik/traefik/v2/pkg/server/recursion"
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/failover" "github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/failover"
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror" "github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror"
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/wrr" "github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/wrr"
@@ -116,6 +117,12 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H
return nil, err return nil, err
} }
var errRecursion error
if ctx, errRecursion = recursion.CheckRecursion(ctx, "service", serviceName); errRecursion != nil {
conf.AddError(errRecursion, true)
return nil, errRecursion
}
var lb http.Handler var lb http.Handler
switch { switch {
+61 -9
View File
@@ -53,30 +53,75 @@ func WithServiceName(serviceName string) func(*dynamic.Router) {
} }
} }
// WithLoadBalancerServices is a helper to create a configuration. // WithServices is a helper to create a configuration.
func WithLoadBalancerServices(opts ...func(service *dynamic.ServersLoadBalancer) string) func(*dynamic.HTTPConfiguration) { func WithServices(opts ...func(service *dynamic.Service) string) func(*dynamic.HTTPConfiguration) {
return func(c *dynamic.HTTPConfiguration) { return func(c *dynamic.HTTPConfiguration) {
c.Services = make(map[string]*dynamic.Service) c.Services = make(map[string]*dynamic.Service)
for _, opt := range opts { for _, opt := range opts {
b := &dynamic.ServersLoadBalancer{} b := &dynamic.Service{}
name := opt(b) name := opt(b)
c.Services[name] = &dynamic.Service{ c.Services[name] = b
LoadBalancer: b,
}
} }
} }
} }
// WithService is a helper to create a configuration. // WithService is a helper to create a configuration.
func WithService(name string, opts ...func(*dynamic.ServersLoadBalancer)) func(*dynamic.ServersLoadBalancer) string { func WithService(name string, opts ...func(*dynamic.Service)) func(*dynamic.Service) string {
return func(r *dynamic.ServersLoadBalancer) string { return func(s *dynamic.Service) string {
for _, opt := range opts { for _, opt := range opts {
opt(r) opt(s)
} }
return name return name
} }
} }
func WithServiceServersLoadBalancer(opts ...func(*dynamic.ServersLoadBalancer)) func(*dynamic.Service) {
return func(s *dynamic.Service) {
b := &dynamic.ServersLoadBalancer{}
b.SetDefaults()
for _, opt := range opts {
opt(b)
}
s.LoadBalancer = b
}
}
func WithServiceWRR(opts ...func(*dynamic.WeightedRoundRobin)) func(*dynamic.Service) {
return func(s *dynamic.Service) {
b := &dynamic.WeightedRoundRobin{}
for _, opt := range opts {
opt(b)
}
s.Weighted = b
}
}
// WithWRRServices is a helper to create a configuration.
func WithWRRServices(opts ...func(*dynamic.WRRService)) func(*dynamic.WeightedRoundRobin) {
return func(b *dynamic.WeightedRoundRobin) {
for _, opt := range opts {
service := dynamic.WRRService{}
opt(&service)
b.Services = append(b.Services, service)
}
}
}
// WithWRRService is a helper to create a configuration.
func WithWRRService(name string, opts ...func(*dynamic.WRRService)) func(*dynamic.WRRService) {
return func(s *dynamic.WRRService) {
for _, opt := range opts {
opt(s)
}
s.Name = name
}
}
// WithMiddlewares is a helper to create a configuration. // WithMiddlewares is a helper to create a configuration.
func WithMiddlewares(opts ...func(*dynamic.Middleware) string) func(*dynamic.HTTPConfiguration) { func WithMiddlewares(opts ...func(*dynamic.Middleware) string) func(*dynamic.HTTPConfiguration) {
return func(c *dynamic.HTTPConfiguration) { return func(c *dynamic.HTTPConfiguration) {
@@ -106,6 +151,13 @@ func WithBasicAuth(auth *dynamic.BasicAuth) func(*dynamic.Middleware) {
} }
} }
// WithErrorPage is a helper to create a configuration.
func WithErrorPage(errorPage *dynamic.ErrorPage) func(*dynamic.Middleware) {
return func(r *dynamic.Middleware) {
r.Errors = errorPage
}
}
// WithEntryPoints is a helper to create a configuration. // WithEntryPoints is a helper to create a configuration.
func WithEntryPoints(eps ...string) func(*dynamic.Router) { func WithEntryPoints(eps ...string) func(*dynamic.Router) {
return func(f *dynamic.Router) { return func(f *dynamic.Router) {