WeightedRoundRobin load balancer
Co-authored-by: Ludovic Fernandez <ldez@users.noreply.github.com>
This commit is contained in:
parent
84de444325
commit
6fed76a687
44 changed files with 1612 additions and 833 deletions
|
@ -48,7 +48,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
|
@ -85,7 +85,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
|
@ -108,7 +108,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
|
@ -132,7 +132,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
|
@ -173,7 +173,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
|
@ -213,7 +213,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service@provider-1": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
|
@ -236,7 +236,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service@provider-2": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
|
@ -260,7 +260,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service@provider-1": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
|
@ -355,7 +355,7 @@ func TestAccessLog(t *testing.T) {
|
|||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
|
@ -383,7 +383,7 @@ func TestAccessLog(t *testing.T) {
|
|||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
|
@ -448,7 +448,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
desc: "No error",
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1:8085",
|
||||
|
@ -482,7 +482,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
desc: "One router with wrong rule",
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
|
@ -509,7 +509,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
desc: "All router with wrong rule",
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
|
@ -536,7 +536,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
desc: "Router with unknown service",
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
|
@ -579,7 +579,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
desc: "Router with middleware",
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
|
@ -619,7 +619,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
desc: "Router with unknown middleware",
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
|
@ -650,7 +650,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
desc: "Router with broken middleware",
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
|
@ -749,7 +749,7 @@ func BenchmarkRouterServe(b *testing.B) {
|
|||
}
|
||||
serviceConfig := map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
|
@ -793,7 +793,7 @@ func BenchmarkService(b *testing.B) {
|
|||
|
||||
serviceConfig := map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.LoadBalancerService{
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "tchouck",
|
||||
|
|
|
@ -199,7 +199,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
|
|||
th.WithRule(routeRule)),
|
||||
),
|
||||
th.WithLoadBalancerServices(th.WithService("bar",
|
||||
th.WithStickiness("test")),
|
||||
th.WithSticky("test")),
|
||||
),
|
||||
)
|
||||
},
|
||||
|
@ -229,7 +229,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
|
|||
th.WithRule(routeRule)),
|
||||
),
|
||||
th.WithLoadBalancerServices(th.WithService("bar",
|
||||
th.WithStickiness("test")),
|
||||
th.WithSticky("test")),
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
155
pkg/server/service/loadbalancer/wrr/wrr.go
Normal file
155
pkg/server/service/loadbalancer/wrr/wrr.go
Normal file
|
@ -0,0 +1,155 @@
|
|||
package wrr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/containous/traefik/v2/pkg/log"
|
||||
)
|
||||
|
||||
type namedHandler struct {
|
||||
http.Handler
|
||||
name string
|
||||
weight int
|
||||
}
|
||||
|
||||
type stickyCookie struct {
|
||||
name string
|
||||
secure bool
|
||||
httpOnly bool
|
||||
}
|
||||
|
||||
// New creates a new load balancer.
|
||||
func New(sticky *dynamic.Sticky) *Balancer {
|
||||
balancer := &Balancer{
|
||||
mutex: &sync.Mutex{},
|
||||
index: -1,
|
||||
}
|
||||
if sticky != nil && sticky.Cookie != nil {
|
||||
balancer.stickyCookie = &stickyCookie{
|
||||
name: sticky.Cookie.Name,
|
||||
secure: sticky.Cookie.Secure,
|
||||
httpOnly: sticky.Cookie.HTTPOnly,
|
||||
}
|
||||
}
|
||||
return balancer
|
||||
}
|
||||
|
||||
// Balancer is a WeightedRoundRobin load balancer.
|
||||
type Balancer struct {
|
||||
handlers []*namedHandler
|
||||
mutex *sync.Mutex
|
||||
// Current index (starts from -1)
|
||||
index int
|
||||
currentWeight int
|
||||
stickyCookie *stickyCookie
|
||||
}
|
||||
|
||||
func (b *Balancer) maxWeight() int {
|
||||
max := -1
|
||||
for _, s := range b.handlers {
|
||||
if s.weight > max {
|
||||
max = s.weight
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
func (b *Balancer) weightGcd() int {
|
||||
divisor := -1
|
||||
for _, s := range b.handlers {
|
||||
if divisor == -1 {
|
||||
divisor = s.weight
|
||||
} else {
|
||||
divisor = gcd(divisor, s.weight)
|
||||
}
|
||||
}
|
||||
return divisor
|
||||
}
|
||||
|
||||
func gcd(a, b int) int {
|
||||
for b != 0 {
|
||||
a, b = b, a%b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (b *Balancer) nextServer() (*namedHandler, error) {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
|
||||
if len(b.handlers) == 0 {
|
||||
return nil, fmt.Errorf("no servers in the pool")
|
||||
}
|
||||
|
||||
// The algo below may look messy, but is actually very simple
|
||||
// it calculates the GCD and subtracts it on every iteration, what interleaves servers
|
||||
// and allows us not to build an iterator every time we readjust weights
|
||||
|
||||
// GCD across all enabled servers
|
||||
gcd := b.weightGcd()
|
||||
// Maximum weight across all enabled servers
|
||||
max := b.maxWeight()
|
||||
|
||||
for {
|
||||
b.index = (b.index + 1) % len(b.handlers)
|
||||
if b.index == 0 {
|
||||
b.currentWeight -= gcd
|
||||
if b.currentWeight <= 0 {
|
||||
b.currentWeight = max
|
||||
if b.currentWeight == 0 {
|
||||
return nil, fmt.Errorf("all servers have 0 weight")
|
||||
}
|
||||
}
|
||||
}
|
||||
srv := b.handlers[b.index]
|
||||
if srv.weight >= b.currentWeight {
|
||||
log.WithoutContext().Debugf("Service Select: %s", srv.name)
|
||||
return srv, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Balancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
if b.stickyCookie != nil {
|
||||
cookie, err := req.Cookie(b.stickyCookie.name)
|
||||
|
||||
if err != nil && err != http.ErrNoCookie {
|
||||
log.WithoutContext().Warnf("Error while reading cookie: %v", err)
|
||||
}
|
||||
|
||||
if err == nil && cookie != nil {
|
||||
for _, handler := range b.handlers {
|
||||
if handler.name == cookie.Value {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server, err := b.nextServer()
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError)+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if b.stickyCookie != nil {
|
||||
cookie := &http.Cookie{Name: b.stickyCookie.name, Value: server.name, Path: "/", HttpOnly: b.stickyCookie.httpOnly, Secure: b.stickyCookie.secure}
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
server.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
// AddService adds a handler.
|
||||
// It is not thread safe with ServeHTTP.
|
||||
func (b *Balancer) AddService(name string, handler http.Handler, weight *int) {
|
||||
w := 1
|
||||
if weight != nil {
|
||||
w = *weight
|
||||
}
|
||||
b.handlers = append(b.handlers, &namedHandler{Handler: handler, name: name, weight: w})
|
||||
}
|
115
pkg/server/service/loadbalancer/wrr/wrr_test.go
Normal file
115
pkg/server/service/loadbalancer/wrr/wrr_test.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package wrr
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Int(v int) *int { return &v }
|
||||
|
||||
type responseRecorder struct {
|
||||
*httptest.ResponseRecorder
|
||||
save map[string]int
|
||||
}
|
||||
|
||||
func (r *responseRecorder) WriteHeader(statusCode int) {
|
||||
r.save[r.Header().Get("server")]++
|
||||
r.ResponseRecorder.WriteHeader(statusCode)
|
||||
|
||||
}
|
||||
|
||||
func TestBalancer(t *testing.T) {
|
||||
balancer := New(nil)
|
||||
|
||||
balancer.AddService("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "first")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(3))
|
||||
|
||||
balancer.AddService("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "second")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
|
||||
recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}}
|
||||
for i := 0; i < 4; i++ {
|
||||
balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil))
|
||||
}
|
||||
|
||||
assert.Equal(t, 3, recorder.save["first"])
|
||||
assert.Equal(t, 1, recorder.save["second"])
|
||||
}
|
||||
|
||||
func TestBalancerNoService(t *testing.T) {
|
||||
balancer := New(nil)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil))
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, recorder.Result().StatusCode)
|
||||
}
|
||||
|
||||
func TestBalancerOneServerZeroWeight(t *testing.T) {
|
||||
balancer := New(nil)
|
||||
|
||||
balancer.AddService("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "first")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
|
||||
balancer.AddService("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0))
|
||||
|
||||
recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}}
|
||||
for i := 0; i < 3; i++ {
|
||||
balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil))
|
||||
}
|
||||
|
||||
assert.Equal(t, 3, recorder.save["first"])
|
||||
}
|
||||
|
||||
func TestBalancerAllServersZeroWeight(t *testing.T) {
|
||||
balancer := New(nil)
|
||||
|
||||
balancer.AddService("test", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0))
|
||||
balancer.AddService("test2", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), Int(0))
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil))
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, recorder.Result().StatusCode)
|
||||
}
|
||||
|
||||
func TestSticky(t *testing.T) {
|
||||
balancer := New(&dynamic.Sticky{
|
||||
Cookie: &dynamic.Cookie{Name: "test"},
|
||||
})
|
||||
|
||||
balancer.AddService("first", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "first")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(1))
|
||||
|
||||
balancer.AddService("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("server", "second")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), Int(2))
|
||||
|
||||
recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
for i := 0; i < 3; i++ {
|
||||
for _, cookie := range recorder.Result().Cookies() {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
recorder.ResponseRecorder = httptest.NewRecorder()
|
||||
|
||||
balancer.ServeHTTP(recorder, req)
|
||||
}
|
||||
|
||||
assert.Equal(t, 0, recorder.save["first"])
|
||||
assert.Equal(t, 3, recorder.save["second"])
|
||||
}
|
|
@ -2,6 +2,7 @@ package service
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
@ -20,6 +21,7 @@ import (
|
|||
"github.com/containous/traefik/v2/pkg/middlewares/pipelining"
|
||||
"github.com/containous/traefik/v2/pkg/server/cookie"
|
||||
"github.com/containous/traefik/v2/pkg/server/internal"
|
||||
"github.com/containous/traefik/v2/pkg/server/service/loadbalancer/wrr"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
)
|
||||
|
||||
|
@ -60,27 +62,58 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons
|
|||
return nil, fmt.Errorf("the service %q does not exist", serviceName)
|
||||
}
|
||||
|
||||
// TODO Should handle multiple service types
|
||||
// FIXME Check if the service is declared multiple times with different types
|
||||
if conf.LoadBalancer == nil {
|
||||
sErr := fmt.Errorf("the service %q doesn't have any load balancer", serviceName)
|
||||
conf.AddError(sErr, true)
|
||||
return nil, sErr
|
||||
if conf.LoadBalancer != nil && conf.Weighted != nil {
|
||||
return nil, errors.New("cannot create service: multi-types service not supported, consider declaring two different pieces of service instead")
|
||||
}
|
||||
|
||||
lb, err := m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer, responseModifier)
|
||||
if err != nil {
|
||||
conf.AddError(err, true)
|
||||
return nil, err
|
||||
var lb http.Handler
|
||||
|
||||
switch {
|
||||
case conf.LoadBalancer != nil:
|
||||
var err error
|
||||
lb, err = m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer, responseModifier)
|
||||
if err != nil {
|
||||
conf.AddError(err, true)
|
||||
return nil, err
|
||||
}
|
||||
case conf.Weighted != nil:
|
||||
var err error
|
||||
lb, err = m.getLoadBalancerWRRServiceHandler(ctx, serviceName, conf.Weighted, responseModifier)
|
||||
if err != nil {
|
||||
conf.AddError(err, true)
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
sErr := fmt.Errorf("the service %q does not have any type defined", serviceName)
|
||||
conf.AddError(sErr, true)
|
||||
return nil, sErr
|
||||
}
|
||||
|
||||
return lb, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getLoadBalancerWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin, responseModifier func(*http.Response) error) (http.Handler, error) {
|
||||
// TODO Handle accesslog and metrics with multiple service name
|
||||
if config.Sticky != nil && config.Sticky.Cookie != nil {
|
||||
config.Sticky.Cookie.Name = cookie.GetName(config.Sticky.Cookie.Name, serviceName)
|
||||
}
|
||||
|
||||
balancer := wrr.New(config.Sticky)
|
||||
for _, service := range config.Services {
|
||||
serviceHandler, err := m.BuildHTTP(ctx, service.Name, responseModifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
balancer.AddService(service.Name, serviceHandler, service.Weight)
|
||||
}
|
||||
return balancer, nil
|
||||
}
|
||||
|
||||
func (m *Manager) getLoadBalancerServiceHandler(
|
||||
ctx context.Context,
|
||||
serviceName string,
|
||||
service *dynamic.LoadBalancerService,
|
||||
service *dynamic.ServersLoadBalancer,
|
||||
responseModifier func(*http.Response) error,
|
||||
) (http.Handler, error) {
|
||||
fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, m.defaultRoundTripper, m.bufferPool, responseModifier)
|
||||
|
@ -193,16 +226,16 @@ func buildHealthCheckOptions(ctx context.Context, lb healthcheck.BalancerHandler
|
|||
}
|
||||
}
|
||||
|
||||
func (m *Manager) getLoadBalancer(ctx context.Context, serviceName string, service *dynamic.LoadBalancerService, fwd http.Handler) (healthcheck.BalancerHandler, error) {
|
||||
func (m *Manager) getLoadBalancer(ctx context.Context, serviceName string, service *dynamic.ServersLoadBalancer, fwd http.Handler) (healthcheck.BalancerHandler, error) {
|
||||
logger := log.FromContext(ctx)
|
||||
logger.Debug("Creating load-balancer")
|
||||
|
||||
var options []roundrobin.LBOption
|
||||
|
||||
var cookieName string
|
||||
if stickiness := service.Stickiness; stickiness != nil {
|
||||
cookieName = cookie.GetName(stickiness.CookieName, serviceName)
|
||||
opts := roundrobin.CookieOptions{HTTPOnly: stickiness.HTTPOnlyCookie, Secure: stickiness.SecureCookie}
|
||||
if service.Sticky != nil && service.Sticky.Cookie != nil {
|
||||
cookieName = cookie.GetName(service.Sticky.Cookie.Name, serviceName)
|
||||
opts := roundrobin.CookieOptions{HTTPOnly: service.Sticky.Cookie.HTTPOnly, Secure: service.Sticky.Cookie.Secure}
|
||||
options = append(options, roundrobin.EnableStickySession(roundrobin.NewStickySessionWithOptions(cookieName, opts)))
|
||||
logger.Debugf("Sticky session cookie name: %v", cookieName)
|
||||
}
|
||||
|
|
|
@ -27,14 +27,14 @@ func TestGetLoadBalancer(t *testing.T) {
|
|||
testCases := []struct {
|
||||
desc string
|
||||
serviceName string
|
||||
service *dynamic.LoadBalancerService
|
||||
service *dynamic.ServersLoadBalancer
|
||||
fwd http.Handler
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
desc: "Fails when provided an invalid URL",
|
||||
serviceName: "test",
|
||||
service: &dynamic.LoadBalancerService{
|
||||
service: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: ":",
|
||||
|
@ -47,15 +47,15 @@ func TestGetLoadBalancer(t *testing.T) {
|
|||
{
|
||||
desc: "Succeeds when there are no servers",
|
||||
serviceName: "test",
|
||||
service: &dynamic.LoadBalancerService{},
|
||||
service: &dynamic.ServersLoadBalancer{},
|
||||
fwd: &MockForwarder{},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
desc: "Succeeds when stickiness is set",
|
||||
desc: "Succeeds when sticky.cookie is set",
|
||||
serviceName: "test",
|
||||
service: &dynamic.LoadBalancerService{
|
||||
Stickiness: &dynamic.Stickiness{},
|
||||
service: &dynamic.ServersLoadBalancer{
|
||||
Sticky: &dynamic.Sticky{Cookie: &dynamic.Cookie{}},
|
||||
},
|
||||
fwd: &MockForwarder{},
|
||||
expectError: false,
|
||||
|
@ -114,7 +114,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
|||
testCases := []struct {
|
||||
desc string
|
||||
serviceName string
|
||||
service *dynamic.LoadBalancerService
|
||||
service *dynamic.ServersLoadBalancer
|
||||
responseModifier func(*http.Response) error
|
||||
|
||||
expected []ExpectedResult
|
||||
|
@ -122,7 +122,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
|||
{
|
||||
desc: "Load balances between the two servers",
|
||||
serviceName: "test",
|
||||
service: &dynamic.LoadBalancerService{
|
||||
service: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server1.URL,
|
||||
|
@ -146,7 +146,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
|||
{
|
||||
desc: "StatusBadGateway when the server is not reachable",
|
||||
serviceName: "test",
|
||||
service: &dynamic.LoadBalancerService{
|
||||
service: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://foo",
|
||||
|
@ -162,7 +162,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
|||
{
|
||||
desc: "ServiceUnavailable when no servers are available",
|
||||
serviceName: "test",
|
||||
service: &dynamic.LoadBalancerService{
|
||||
service: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{},
|
||||
},
|
||||
expected: []ExpectedResult{
|
||||
|
@ -172,10 +172,10 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
desc: "Always call the same server when stickiness is true",
|
||||
desc: "Always call the same server when sticky.cookie is true",
|
||||
serviceName: "test",
|
||||
service: &dynamic.LoadBalancerService{
|
||||
Stickiness: &dynamic.Stickiness{},
|
||||
service: &dynamic.ServersLoadBalancer{
|
||||
Sticky: &dynamic.Sticky{Cookie: &dynamic.Cookie{}},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server1.URL,
|
||||
|
@ -199,8 +199,8 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
|||
{
|
||||
desc: "Sticky Cookie's options set correctly",
|
||||
serviceName: "test",
|
||||
service: &dynamic.LoadBalancerService{
|
||||
Stickiness: &dynamic.Stickiness{HTTPOnlyCookie: true, SecureCookie: true},
|
||||
service: &dynamic.ServersLoadBalancer{
|
||||
Sticky: &dynamic.Sticky{Cookie: &dynamic.Cookie{HTTPOnly: true, Secure: true}},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server1.URL,
|
||||
|
@ -219,8 +219,8 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
|||
{
|
||||
desc: "PassHost passes the host instead of the IP",
|
||||
serviceName: "test",
|
||||
service: &dynamic.LoadBalancerService{
|
||||
Stickiness: &dynamic.Stickiness{},
|
||||
service: &dynamic.ServersLoadBalancer{
|
||||
Sticky: &dynamic.Sticky{Cookie: &dynamic.Cookie{}},
|
||||
PassHostHeader: true,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
|
@ -238,8 +238,8 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
|||
{
|
||||
desc: "PassHost doesn't passe the host instead of the IP",
|
||||
serviceName: "test",
|
||||
service: &dynamic.LoadBalancerService{
|
||||
Stickiness: &dynamic.Stickiness{},
|
||||
service: &dynamic.ServersLoadBalancer{
|
||||
Sticky: &dynamic.Sticky{Cookie: &dynamic.Cookie{}},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: serverPassHostFalse.URL,
|
||||
|
@ -297,7 +297,7 @@ func TestManager_Build(t *testing.T) {
|
|||
configs: map[string]*runtime.ServiceInfo{
|
||||
"serviceName": {
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{},
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -308,7 +308,7 @@ func TestManager_Build(t *testing.T) {
|
|||
configs: map[string]*runtime.ServiceInfo{
|
||||
"serviceName@provider-1": {
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{},
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -319,7 +319,7 @@ func TestManager_Build(t *testing.T) {
|
|||
configs: map[string]*runtime.ServiceInfo{
|
||||
"serviceName@provider-1": {
|
||||
Service: &dynamic.Service{
|
||||
LoadBalancer: &dynamic.LoadBalancerService{},
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue