Fix internal handlers ServiceBuilder composition

This commit is contained in:
Julien Salleyron 2024-11-19 14:52:04 +01:00 committed by GitHub
parent 8ffd1854db
commit cc80568d9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 80 additions and 27 deletions

View file

@ -8,11 +8,6 @@ import (
"strings" "strings"
) )
type serviceManager interface {
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
LaunchHealthCheck()
}
// InternalHandlers is the internal HTTP handlers builder. // InternalHandlers is the internal HTTP handlers builder.
type InternalHandlers struct { type InternalHandlers struct {
api http.Handler api http.Handler
@ -21,26 +16,24 @@ type InternalHandlers struct {
prometheus http.Handler prometheus http.Handler
ping http.Handler ping http.Handler
acmeHTTP http.Handler acmeHTTP http.Handler
serviceManager
} }
// NewInternalHandlers creates a new InternalHandlers. // NewInternalHandlers creates a new InternalHandlers.
func NewInternalHandlers(next serviceManager, apiHandler, rest, metricsHandler, pingHandler, dashboard, acmeHTTP http.Handler) *InternalHandlers { func NewInternalHandlers(apiHandler, rest, metricsHandler, pingHandler, dashboard, acmeHTTP http.Handler) *InternalHandlers {
return &InternalHandlers{ return &InternalHandlers{
api: apiHandler, api: apiHandler,
dashboard: dashboard, dashboard: dashboard,
rest: rest, rest: rest,
prometheus: metricsHandler, prometheus: metricsHandler,
ping: pingHandler, ping: pingHandler,
acmeHTTP: acmeHTTP, acmeHTTP: acmeHTTP,
serviceManager: next,
} }
} }
// BuildHTTP builds an HTTP handler. // BuildHTTP builds an HTTP handler.
func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) { func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) {
if !strings.HasSuffix(serviceName, "@internal") { if !strings.HasSuffix(serviceName, "@internal") {
return m.serviceManager.BuildHTTP(rootCtx, serviceName) return nil, nil
} }
internalHandler, err := m.get(serviceName) internalHandler, err := m.get(serviceName)

View file

@ -71,13 +71,12 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s
} }
// Build creates a service manager. // Build creates a service manager.
func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers { func (f *ManagerFactory) Build(configuration *runtime.Configuration) *Manager {
svcManager := NewManager(configuration.Services, f.metricsRegistry, f.routinesPool, f.roundTripperManager)
var apiHandler http.Handler var apiHandler http.Handler
if f.api != nil { if f.api != nil {
apiHandler = f.api(configuration) apiHandler = f.api(configuration)
} }
return NewInternalHandlers(svcManager, apiHandler, f.restHandler, f.metricsHandler, f.pingHandler, f.dashboardHandler, f.acmeHTTPHandler) internalHandlers := NewInternalHandlers(apiHandler, f.restHandler, f.metricsHandler, f.pingHandler, f.dashboardHandler, f.acmeHTTPHandler)
return NewManager(configuration.Services, f.metricsRegistry, f.routinesPool, f.roundTripperManager, internalHandlers)
} }

View file

@ -34,22 +34,28 @@ import (
const ( const (
defaultHealthCheckInterval = 30 * time.Second defaultHealthCheckInterval = 30 * time.Second
defaultHealthCheckTimeout = 5 * time.Second defaultHealthCheckTimeout = 5 * time.Second
)
const defaultMaxBodySize int64 = -1 defaultMaxBodySize int64 = -1
)
// RoundTripperGetter is a roundtripper getter interface. // RoundTripperGetter is a roundtripper getter interface.
type RoundTripperGetter interface { type RoundTripperGetter interface {
Get(name string) (http.RoundTripper, error) Get(name string) (http.RoundTripper, error)
} }
// ServiceBuilder is a Service builder.
type ServiceBuilder interface {
BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error)
}
// NewManager creates a new Manager. // NewManager creates a new Manager.
func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager { func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter, serviceBuilders ...ServiceBuilder) *Manager {
return &Manager{ return &Manager{
routinePool: routinePool, routinePool: routinePool,
metricsRegistry: metricsRegistry, metricsRegistry: metricsRegistry,
bufferPool: newBufferPool(), bufferPool: newBufferPool(),
roundTripperManager: roundTripperManager, roundTripperManager: roundTripperManager,
serviceBuilders: serviceBuilders,
balancers: make(map[string]healthcheck.Balancers), balancers: make(map[string]healthcheck.Balancers),
configs: configs, configs: configs,
rand: rand.New(rand.NewSource(time.Now().UnixNano())), rand: rand.New(rand.NewSource(time.Now().UnixNano())),
@ -62,6 +68,8 @@ type Manager struct {
metricsRegistry metrics.Registry metricsRegistry metrics.Registry
bufferPool httputil.BufferPool bufferPool httputil.BufferPool
roundTripperManager RoundTripperGetter roundTripperManager RoundTripperGetter
serviceBuilders []ServiceBuilder
// balancers is the map of all Balancers, keyed by service name. // balancers is the map of all Balancers, keyed by service name.
// There is one Balancer per service handler, and there is one service handler per reference to a service // There is one Balancer per service handler, and there is one service handler per reference to a service
// (e.g. if 2 routers refer to the same service name, 2 service handlers are created), // (e.g. if 2 routers refer to the same service name, 2 service handlers are created),
@ -78,6 +86,17 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.H
serviceName = provider.GetQualifiedName(ctx, serviceName) serviceName = provider.GetQualifiedName(ctx, serviceName)
ctx = provider.AddInContext(ctx, serviceName) ctx = provider.AddInContext(ctx, serviceName)
// Must be before we get configs to handle services without config.
for _, builder := range m.serviceBuilders {
handler, err := builder.BuildHTTP(rootCtx, serviceName)
if err != nil {
return nil, err
}
if handler != nil {
return handler, nil
}
}
conf, ok := m.configs[serviceName] conf, ok := m.configs[serviceName]
if !ok { if !ok {
return nil, fmt.Errorf("the service %q does not exist", serviceName) return nil, fmt.Errorf("the service %q does not exist", serviceName)

View file

@ -18,9 +18,9 @@ import (
"github.com/traefik/traefik/v2/pkg/testhelpers" "github.com/traefik/traefik/v2/pkg/testhelpers"
) )
type MockForwarder struct{} type mockForwarder struct{}
func (MockForwarder) ServeHTTP(http.ResponseWriter, *http.Request) { func (mockForwarder) ServeHTTP(http.ResponseWriter, *http.Request) {
panic("implement me") panic("implement me")
} }
@ -44,14 +44,14 @@ func TestGetLoadBalancer(t *testing.T) {
}, },
}, },
}, },
fwd: &MockForwarder{}, fwd: &mockForwarder{},
expectError: true, expectError: true,
}, },
{ {
desc: "Succeeds when there are no servers", desc: "Succeeds when there are no servers",
serviceName: "test", serviceName: "test",
service: &dynamic.ServersLoadBalancer{}, service: &dynamic.ServersLoadBalancer{},
fwd: &MockForwarder{}, fwd: &mockForwarder{},
expectError: false, expectError: false,
}, },
{ {
@ -60,7 +60,7 @@ func TestGetLoadBalancer(t *testing.T) {
service: &dynamic.ServersLoadBalancer{ service: &dynamic.ServersLoadBalancer{
Sticky: &dynamic.Sticky{Cookie: &dynamic.Cookie{}}, Sticky: &dynamic.Sticky{Cookie: &dynamic.Cookie{}},
}, },
fwd: &MockForwarder{}, fwd: &mockForwarder{},
expectError: false, expectError: false,
}, },
} }
@ -476,6 +476,48 @@ func Test1xxResponses(t *testing.T) {
} }
} }
type serviceBuilderFunc func(ctx context.Context, serviceName string) (http.Handler, error)
func (s serviceBuilderFunc) BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error) {
return s(ctx, serviceName)
}
type internalHandler struct{}
func (internalHandler) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {}
func TestManager_ServiceBuilders(t *testing.T) {
var internalHandler internalHandler
manager := NewManager(map[string]*runtime.ServiceInfo{
"test@test": {
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{},
},
},
}, nil, nil, &RoundTripperManager{
roundTrippers: map[string]http.RoundTripper{
"default@internal": http.DefaultTransport,
},
}, serviceBuilderFunc(func(rootCtx context.Context, serviceName string) (http.Handler, error) {
if strings.HasSuffix(serviceName, "@internal") {
return internalHandler, nil
}
return nil, nil
}))
h, err := manager.BuildHTTP(context.Background(), "test@internal")
require.NoError(t, err)
assert.Equal(t, internalHandler, h)
h, err = manager.BuildHTTP(context.Background(), "test@test")
require.NoError(t, err)
assert.NotNil(t, h)
_, err = manager.BuildHTTP(context.Background(), "wrong@test")
assert.Error(t, err)
}
func TestManager_Build(t *testing.T) { func TestManager_Build(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string