Add internal provider
Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
This commit is contained in:
parent
2ee2e29262
commit
424e2a9439
71 changed files with 2523 additions and 1469 deletions
89
pkg/server/service/internalhandler.go
Normal file
89
pkg/server/service/internalhandler.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/runtime"
|
||||
)
|
||||
|
||||
type serviceManager interface {
|
||||
BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error)
|
||||
LaunchHealthCheck()
|
||||
}
|
||||
|
||||
// InternalHandlers is the internal HTTP handlers builder.
|
||||
type InternalHandlers struct {
|
||||
api http.Handler
|
||||
dashboard http.Handler
|
||||
rest http.Handler
|
||||
prometheus http.Handler
|
||||
ping http.Handler
|
||||
serviceManager
|
||||
}
|
||||
|
||||
// NewInternalHandlers creates a new InternalHandlers.
|
||||
func NewInternalHandlers(api func(configuration *runtime.Configuration) http.Handler, configuration *runtime.Configuration, rest http.Handler, metricsHandler http.Handler, pingHandler http.Handler, dashboard http.Handler, next serviceManager) *InternalHandlers {
|
||||
var apiHandler http.Handler
|
||||
if api != nil {
|
||||
apiHandler = api(configuration)
|
||||
}
|
||||
|
||||
return &InternalHandlers{
|
||||
api: apiHandler,
|
||||
dashboard: dashboard,
|
||||
rest: rest,
|
||||
prometheus: metricsHandler,
|
||||
ping: pingHandler,
|
||||
serviceManager: next,
|
||||
}
|
||||
}
|
||||
|
||||
// BuildHTTP builds an HTTP handler.
|
||||
func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) {
|
||||
if strings.HasSuffix(serviceName, "@internal") {
|
||||
return m.get(serviceName)
|
||||
}
|
||||
|
||||
return m.serviceManager.BuildHTTP(rootCtx, serviceName, responseModifier)
|
||||
}
|
||||
|
||||
func (m *InternalHandlers) get(serviceName string) (http.Handler, error) {
|
||||
switch serviceName {
|
||||
case "api@internal":
|
||||
if m.api == nil {
|
||||
return nil, errors.New("api is not enabled")
|
||||
}
|
||||
return m.api, nil
|
||||
|
||||
case "dashboard@internal":
|
||||
if m.dashboard == nil {
|
||||
return nil, errors.New("dashboard is not enabled")
|
||||
}
|
||||
return m.dashboard, nil
|
||||
|
||||
case "rest@internal":
|
||||
if m.rest == nil {
|
||||
return nil, errors.New("rest is not enabled")
|
||||
}
|
||||
return m.rest, nil
|
||||
|
||||
case "ping@internal":
|
||||
if m.ping == nil {
|
||||
return nil, errors.New("ping is not enabled")
|
||||
}
|
||||
return m.ping, nil
|
||||
|
||||
case "prometheus@internal":
|
||||
if m.prometheus == nil {
|
||||
return nil, errors.New("prometheus is not enabled")
|
||||
}
|
||||
return m.prometheus, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown internal service %s", serviceName)
|
||||
}
|
||||
}
|
61
pkg/server/service/managerfactory.go
Normal file
61
pkg/server/service/managerfactory.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/api"
|
||||
"github.com/containous/traefik/v2/pkg/config/runtime"
|
||||
"github.com/containous/traefik/v2/pkg/config/static"
|
||||
"github.com/containous/traefik/v2/pkg/metrics"
|
||||
"github.com/containous/traefik/v2/pkg/safe"
|
||||
)
|
||||
|
||||
// ManagerFactory a factory of service manager.
|
||||
type ManagerFactory struct {
|
||||
metricsRegistry metrics.Registry
|
||||
|
||||
defaultRoundTripper http.RoundTripper
|
||||
|
||||
api func(configuration *runtime.Configuration) http.Handler
|
||||
restHandler http.Handler
|
||||
dashboardHandler http.Handler
|
||||
metricsHandler http.Handler
|
||||
pingHandler http.Handler
|
||||
|
||||
routinesPool *safe.Pool
|
||||
}
|
||||
|
||||
// NewManagerFactory creates a new ManagerFactory.
|
||||
func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, metricsRegistry metrics.Registry) *ManagerFactory {
|
||||
factory := &ManagerFactory{
|
||||
metricsRegistry: metricsRegistry,
|
||||
defaultRoundTripper: setupDefaultRoundTripper(staticConfiguration.ServersTransport),
|
||||
routinesPool: routinesPool,
|
||||
}
|
||||
|
||||
if staticConfiguration.API != nil {
|
||||
factory.api = api.NewBuilder(staticConfiguration)
|
||||
|
||||
if staticConfiguration.API.Dashboard {
|
||||
factory.dashboardHandler = http.FileServer(staticConfiguration.API.DashboardAssets)
|
||||
}
|
||||
}
|
||||
|
||||
if staticConfiguration.Providers != nil && staticConfiguration.Providers.Rest != nil {
|
||||
factory.restHandler = staticConfiguration.Providers.Rest.CreateRouter()
|
||||
}
|
||||
|
||||
if staticConfiguration.Metrics != nil && staticConfiguration.Metrics.Prometheus != nil {
|
||||
factory.metricsHandler = metrics.PrometheusHandler()
|
||||
}
|
||||
|
||||
factory.pingHandler = staticConfiguration.Ping
|
||||
|
||||
return factory
|
||||
}
|
||||
|
||||
// Build creates a service manager.
|
||||
func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers {
|
||||
svcManager := NewManager(configuration.Services, f.defaultRoundTripper, f.metricsRegistry, f.routinesPool)
|
||||
return NewInternalHandlers(f.api, configuration, f.restHandler, f.metricsHandler, f.pingHandler, f.dashboardHandler, svcManager)
|
||||
}
|
110
pkg/server/service/roundtripper.go
Normal file
110
pkg/server/service/roundtripper.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/static"
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
traefiktls "github.com/containous/traefik/v2/pkg/tls"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type h2cTransportWrapper struct {
|
||||
*http2.Transport
|
||||
}
|
||||
|
||||
func (t *h2cTransportWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.URL.Scheme = "http"
|
||||
return t.Transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
// createHTTPTransport creates an http.Transport configured with the Transport configuration settings.
|
||||
// For the settings that can't be configured in Traefik it uses the default http.Transport settings.
|
||||
// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHost
|
||||
// in Traefik at this point in time. Setting this value to the default of 100 could lead to confusing
|
||||
// behavior and backwards compatibility issues.
|
||||
func createHTTPTransport(transportConfiguration *static.ServersTransport) (*http.Transport, error) {
|
||||
if transportConfiguration == nil {
|
||||
return nil, errors.New("no transport configuration given")
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}
|
||||
|
||||
if transportConfiguration.ForwardingTimeouts != nil {
|
||||
dialer.Timeout = time.Duration(transportConfiguration.ForwardingTimeouts.DialTimeout)
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: dialer.DialContext,
|
||||
MaxIdleConnsPerHost: transportConfiguration.MaxIdleConnsPerHost,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
transport.RegisterProtocol("h2c", &h2cTransportWrapper{
|
||||
Transport: &http2.Transport{
|
||||
DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||
return net.Dial(netw, addr)
|
||||
},
|
||||
AllowHTTP: true,
|
||||
},
|
||||
})
|
||||
|
||||
if transportConfiguration.ForwardingTimeouts != nil {
|
||||
transport.ResponseHeaderTimeout = time.Duration(transportConfiguration.ForwardingTimeouts.ResponseHeaderTimeout)
|
||||
transport.IdleConnTimeout = time.Duration(transportConfiguration.ForwardingTimeouts.IdleConnTimeout)
|
||||
}
|
||||
|
||||
if transportConfiguration.InsecureSkipVerify {
|
||||
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
|
||||
if len(transportConfiguration.RootCAs) > 0 {
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
RootCAs: createRootCACertPool(transportConfiguration.RootCAs),
|
||||
}
|
||||
}
|
||||
|
||||
err := http2.ConfigureTransport(transport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
func createRootCACertPool(rootCAs []traefiktls.FileOrContent) *x509.CertPool {
|
||||
roots := x509.NewCertPool()
|
||||
|
||||
for _, cert := range rootCAs {
|
||||
certContent, err := cert.Read()
|
||||
if err != nil {
|
||||
log.WithoutContext().Error("Error while read RootCAs", err)
|
||||
continue
|
||||
}
|
||||
roots.AppendCertsFromPEM(certContent)
|
||||
}
|
||||
|
||||
return roots
|
||||
}
|
||||
|
||||
func setupDefaultRoundTripper(conf *static.ServersTransport) http.RoundTripper {
|
||||
transport, err := createHTTPTransport(conf)
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Could not configure HTTP Transport, fallbacking on default transport: %v", err)
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
return transport
|
||||
}
|
|
@ -34,7 +34,7 @@ const (
|
|||
)
|
||||
|
||||
// NewManager creates a new Manager
|
||||
func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper http.RoundTripper, metricsRegistry metrics.Registry, routinePool *safe.Pool, api http.Handler, rest http.Handler) *Manager {
|
||||
func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper http.RoundTripper, metricsRegistry metrics.Registry, routinePool *safe.Pool) *Manager {
|
||||
return &Manager{
|
||||
routinePool: routinePool,
|
||||
metricsRegistry: metricsRegistry,
|
||||
|
@ -42,8 +42,6 @@ func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper htt
|
|||
defaultRoundTripper: defaultRoundTripper,
|
||||
balancers: make(map[string][]healthcheck.BalancerHandler),
|
||||
configs: configs,
|
||||
api: api,
|
||||
rest: rest,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,26 +53,10 @@ type Manager struct {
|
|||
defaultRoundTripper http.RoundTripper
|
||||
balancers map[string][]healthcheck.BalancerHandler
|
||||
configs map[string]*runtime.ServiceInfo
|
||||
api http.Handler
|
||||
rest http.Handler
|
||||
}
|
||||
|
||||
// BuildHTTP Creates a http.Handler for a service configuration.
|
||||
func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) {
|
||||
if serviceName == "api@internal" {
|
||||
if m.api == nil {
|
||||
return nil, errors.New("api is not enabled")
|
||||
}
|
||||
return m.api, nil
|
||||
}
|
||||
|
||||
if serviceName == "rest@internal" {
|
||||
if m.rest == nil {
|
||||
return nil, errors.New("rest is not enabled")
|
||||
}
|
||||
return m.rest, nil
|
||||
}
|
||||
|
||||
ctx := log.With(rootCtx, log.Str(log.ServiceName, serviceName))
|
||||
|
||||
serviceName = internal.GetQualifiedName(ctx, serviceName)
|
||||
|
|
|
@ -80,7 +80,7 @@ func TestGetLoadBalancer(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
||||
sm := NewManager(nil, http.DefaultTransport, nil, nil, nil, nil)
|
||||
sm := NewManager(nil, http.DefaultTransport, nil, nil)
|
||||
|
||||
server1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-From", "first")
|
||||
|
@ -332,7 +332,7 @@ func TestManager_Build(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
manager := NewManager(test.configs, http.DefaultTransport, nil, nil, nil, nil)
|
||||
manager := NewManager(test.configs, http.DefaultTransport, nil, nil)
|
||||
|
||||
ctx := context.Background()
|
||||
if len(test.providerName) > 0 {
|
||||
|
@ -346,14 +346,16 @@ func TestManager_Build(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMultipleTypeOnBuildHTTP(t *testing.T) {
|
||||
manager := NewManager(map[string]*runtime.ServiceInfo{
|
||||
services := map[string]*runtime.ServiceInfo{
|
||||
"test@file": {
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{},
|
||||
Weighted: &dynamic.WeightedRoundRobin{},
|
||||
},
|
||||
},
|
||||
}, http.DefaultTransport, nil, nil, nil, nil)
|
||||
}
|
||||
|
||||
manager := NewManager(services, http.DefaultTransport, nil, nil)
|
||||
|
||||
_, err := manager.BuildHTTP(context.Background(), "test@file", nil)
|
||||
assert.Error(t, err, "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue