Fix internal handlers ServiceBuilder composition
This commit is contained in:
parent
8ffd1854db
commit
cc80568d9e
4 changed files with 80 additions and 27 deletions
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue