From 51227241b746deadfca766622d4856057960a9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20J=C3=B8rgensen?= Date: Wed, 6 Jun 2018 17:56:03 +0200 Subject: [PATCH] Fix backend reuse --- server/server.go | 20 ++++++++++----- server/server_test.go | 59 +++++++++++++++++++++++++++++++++++++++++++ testhelpers/config.go | 7 +++++ types/types.go | 16 ++++++++++-- 4 files changed, 94 insertions(+), 8 deletions(-) diff --git a/server/server.go b/server/server.go index 0dc255556..33d736791 100644 --- a/server/server.go +++ b/server/server.go @@ -950,7 +950,15 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura redirectHandlers[entryPointName] = handlerToUse } } - if backends[entryPointName+providerName+frontend.Backend] == nil { + + frontendHash, err := frontend.Hash() + if err != nil { + log.Errorf("Error calculating hash value for frontend %s: %v", frontendName, err) + log.Errorf("Skipping frontend %s...", frontendName) + continue frontend + } + backendCacheKey := entryPointName + providerName + frontendHash + if backends[backendCacheKey] == nil { log.Debugf("Creating backend %s", frontend.Backend) roundTripper, err := s.getRoundTripper(entryPointName, globalConfiguration, frontend.PassTLSCert, entryPoint.TLS) @@ -1045,7 +1053,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura if hcOpts != nil { log.Debugf("Setting up backend health check %s", *hcOpts) hcOpts.Transport = s.defaultForwardingRoundTripper - backendsHealthCheck[entryPointName+frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts, frontend.Backend) + backendsHealthCheck[backendCacheKey] = healthcheck.NewBackendHealthCheck(*hcOpts, frontend.Backend) } lb = middlewares.NewEmptyBackendHandler(rebalancer, lb) case types.Wrr: @@ -1067,7 +1075,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura if hcOpts != nil { log.Debugf("Setting up backend health check %s", *hcOpts) hcOpts.Transport = s.defaultForwardingRoundTripper - backendsHealthCheck[entryPointName+frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts, frontend.Backend) + backendsHealthCheck[backendCacheKey] = healthcheck.NewBackendHealthCheck(*hcOpts, frontend.Backend) } lb = middlewares.NewEmptyBackendHandler(rr, lb) } @@ -1208,16 +1216,16 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura } else { n.UseHandler(lb) } - backends[entryPointName+providerName+frontend.Backend] = n + backends[backendCacheKey] = n } else { log.Debugf("Reusing backend %s", frontend.Backend) } if frontend.Priority > 0 { newServerRoute.Route.Priority(frontend.Priority) } - s.wireFrontendBackend(newServerRoute, backends[entryPointName+providerName+frontend.Backend]) + s.wireFrontendBackend(newServerRoute, backends[backendCacheKey]) - err := newServerRoute.Route.GetError() + err = newServerRoute.Route.GetError() if err != nil { log.Errorf("Error building route: %s", err) } diff --git a/server/server_test.go b/server/server_test.go index 78b145bd3..4353687ad 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -962,6 +962,65 @@ func TestServerResponseEmptyBackend(t *testing.T) { } } +func TestReuseBackend(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + defer testServer.Close() + + globalConfig := configuration.GlobalConfiguration{ + DefaultEntryPoints: []string{"http"}, + } + + entryPoints := map[string]EntryPoint{ + "http": {Configuration: &configuration.EntryPoint{ + ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}, + }}, + } + + dynamicConfigs := types.Configurations{ + "config": th.BuildConfiguration( + th.WithFrontends( + th.WithFrontend("backend", + th.WithFrontendName("frontend0"), + th.WithEntryPoints("http"), + th.WithRoutes(th.WithRoute("/ok", "Path: /ok"))), + th.WithFrontend("backend", + th.WithFrontendName("frontend1"), + th.WithEntryPoints("http"), + th.WithRoutes(th.WithRoute("/unauthorized", "Path: /unauthorized")), + th.WithBasicAuth("foo", "bar")), + ), + th.WithBackends(th.WithBackendNew("backend", + th.WithLBMethod("wrr"), + th.WithServersNew(th.WithServerNew(testServer.URL))), + ), + ), + } + + srv := NewServer(globalConfig, nil, entryPoints) + + serverEntryPoints, err := srv.loadConfig(dynamicConfigs, globalConfig) + if err != nil { + t.Fatalf("error loading config: %s", err) + } + + // Test that the /ok path returns a status 200. + responseRecorderOk := &httptest.ResponseRecorder{} + requestOk := httptest.NewRequest(http.MethodGet, testServer.URL+"/ok", nil) + serverEntryPoints["http"].httpRouter.ServeHTTP(responseRecorderOk, requestOk) + + assert.Equal(t, http.StatusOK, responseRecorderOk.Result().StatusCode, "status code") + + // Test that the /unauthorized path returns a 401 because of + // the basic authentication defined on the frontend. + responseRecorderUnauthorized := &httptest.ResponseRecorder{} + requestUnauthorized := httptest.NewRequest(http.MethodGet, testServer.URL+"/unauthorized", nil) + serverEntryPoints["http"].httpRouter.ServeHTTP(responseRecorderUnauthorized, requestUnauthorized) + + assert.Equal(t, http.StatusUnauthorized, responseRecorderUnauthorized.Result().StatusCode, "status code") +} + func TestBuildRedirectHandler(t *testing.T) { srv := Server{ globalConfiguration: configuration.GlobalConfiguration{}, diff --git a/testhelpers/config.go b/testhelpers/config.go index 1abe0cf77..71969cc7e 100644 --- a/testhelpers/config.go +++ b/testhelpers/config.go @@ -137,6 +137,13 @@ func WithRoute(name string, rule string) func(*types.Route) string { } } +// WithBasicAuth is a helper to create a configuration +func WithBasicAuth(username string, password string) func(*types.Frontend) { + return func(fe *types.Frontend) { + fe.BasicAuth = []string{username + ":" + password} + } +} + // WithLBSticky is a helper to create a configuration func WithLBSticky(cookieName string) func(*types.Backend) { return func(b *types.Backend) { diff --git a/types/types.go b/types/types.go index 38031f1a1..4ff401619 100644 --- a/types/types.go +++ b/types/types.go @@ -16,6 +16,7 @@ import ( "github.com/containous/mux" "github.com/containous/traefik/log" traefiktls "github.com/containous/traefik/tls" + "github.com/mitchellh/hashstructure" "github.com/ryanuber/go-glob" ) @@ -177,9 +178,9 @@ func (h *Headers) HasSecureHeadersDefined() bool { // Frontend holds frontend configuration. type Frontend struct { - EntryPoints []string `json:"entryPoints,omitempty"` + EntryPoints []string `json:"entryPoints,omitempty" hash:"ignore"` Backend string `json:"backend,omitempty"` - Routes map[string]Route `json:"routes,omitempty"` + Routes map[string]Route `json:"routes,omitempty" hash:"ignore"` PassHostHeader bool `json:"passHostHeader,omitempty"` PassTLSCert bool `json:"passTLSCert,omitempty"` Priority int `json:"priority"` @@ -192,6 +193,17 @@ type Frontend struct { Redirect *Redirect `json:"redirect,omitempty"` } +// Hash returns the hash value of a Frontend struct. +func (f *Frontend) Hash() (string, error) { + hash, err := hashstructure.Hash(f, nil) + + if err != nil { + return "", err + } + + return strconv.FormatUint(hash, 10), nil +} + // Redirect configures a redirection of an entry point to another, or to an URL type Redirect struct { EntryPoint string `json:"entryPoint,omitempty"`