1
0
Fork 0

Add internal provider

Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
This commit is contained in:
Ludovic Fernandez 2019-11-14 16:40:05 +01:00 committed by Traefiker Bot
parent 2ee2e29262
commit 424e2a9439
71 changed files with 2523 additions and 1469 deletions

View 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)
}
}

View 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)
}

View 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
}

View file

@ -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)

View file

@ -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")