Skip to content

Commit

Permalink
refactor: parse load balancers in another function
Browse files Browse the repository at this point in the history
  • Loading branch information
claby2 committed Aug 23, 2023
1 parent 896491c commit 36ee4c6
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 43 deletions.
14 changes: 7 additions & 7 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import (
)

var (
errInvalidConfig = fmt.Errorf("invalid config")
errMustHaveOneService = fmt.Errorf("must have exactly one service")
errNoServiceWithName = fmt.Errorf("no service with name")
errDuplicateHost = fmt.Errorf("duplicate host")
errInvalidConfig = fmt.Errorf("invalid config")
errMustHaveOneRouter = fmt.Errorf("must have exactly one router")
errNoServiceWithName = fmt.Errorf("no service with name")
errDuplicateHost = fmt.Errorf("duplicate host")
)

type containerInfo struct {
Expand All @@ -39,20 +39,20 @@ type routers struct {
LoadBalancer *loadBalancerInfo `yaml:"loadBalancer"`
}

func (r *routers) validate() error {
func (r *routers) ensureOneRouter() error {
v := reflect.ValueOf(*r)
count := 0
for i := 0; i < v.NumField(); i++ {
if v.Field(i).IsZero() {
continue
}
if count > 0 {
return errMustHaveOneService
return errMustHaveOneRouter
}
count++
}
if count == 0 {
return errMustHaveOneService
return errMustHaveOneRouter
}
return nil
}
Expand Down
112 changes: 112 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
@@ -1 +1,113 @@
package config

import (
"errors"
"slices"
"testing"
)

func TestEnsureOneRouter(t *testing.T) {
tests := map[string]struct {
routers routers
want error
}{
"empty": {
routers: routers{},
want: errMustHaveOneRouter,
},
"container": {
routers: routers{
Container: &containerInfo{},
},
want: nil,
},
"redirect": {
routers: routers{
Redirect: "https://example.com",
},
want: nil,
},
"load balancer": {
routers: routers{
LoadBalancer: &loadBalancerInfo{},
},
want: nil,
},
"multiple": {
routers: routers{
Container: &containerInfo{},
Redirect: "https://example.com",
},
want: errMustHaveOneRouter,
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
err := test.routers.ensureOneRouter()
if !errors.Is(err, test.want) {
t.Errorf("got %v, want %v", err, test.want)
}
})
}
}

func TestNewEmptyConfig(t *testing.T) {
_, err := New([]byte(``))
if !errors.Is(err, errInvalidConfig) {
t.Errorf("got %v, want %v", err, errInvalidConfig)
}
}

func TestConfigTLSHosts(t *testing.T) {
tests := map[string]struct {
services serviceConfig
want []string
}{
"empty": {
services: serviceConfig{},
want: nil,
},
"no host": {
services: serviceConfig{
"foo": {},
"bar": {},
},
want: nil,
},
"ignore non-TLS": {
services: serviceConfig{
"foo": {Host: "example.com"},
"bar": {},
},
want: []string{},
},
"with TLS": {
services: serviceConfig{
"foo": {TLS: true, Host: "example.com"},
"bar": {TLS: true, Host: "foo.example.com"},
"baz": {TLS: true, Host: "baz.example.com"},
},
want: []string{
"example.com",
"foo.example.com",
"baz.example.com",
},
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
config := &Config{
ServiceConfig: test.services,
}
got := config.TLSHosts()

slices.Sort(got)
slices.Sort(test.want)
if !slices.Equal(got, test.want) {
t.Errorf("got %v, want %v", got, test.want)
}
})
}
}
75 changes: 46 additions & 29 deletions config/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,16 @@ func (c *Config) Services(docker dockerapi.Docker) (map[string]*services.Service
}

nameService := make(map[string]*services.Service)
loadBalancers := make(map[string]*services.LoadBalancer)

for name, service := range c.ServiceConfig {
if err := service.validate(); err != nil {
if err := service.ensureOneRouter(); err != nil {
return nil, fmt.Errorf("%w: %w", errInvalidConfig, err)
}
var router services.Router
if service.LoadBalancer != nil {
strategy, err := services.NewStrategy(service.LoadBalancer.Strategy)
if err != nil {
return nil, fmt.Errorf("%w: %s: %w", errInvalidConfig, name, err)
}
loadBalancers[name] = services.NewLoadBalancer(
service.Host,
strategy,
service.LoadBalancer.Persistent,
)
} else if service.Container != nil {
continue
}

var router services.Router
if service.Container != nil {
router = services.NewContainer(
service.Container.Name,
service.Container.Network,
Expand All @@ -68,6 +60,7 @@ func (c *Config) Services(docker dockerapi.Docker) (map[string]*services.Service
}
router = services.NewRedirect(*remote)
}

nameService[name] = &services.Service{
TLS: service.TLS,
Middlewares: service.Middlewares.List(),
Expand All @@ -76,27 +69,51 @@ func (c *Config) Services(docker dockerapi.Docker) (map[string]*services.Service
}
}

for name, lb := range loadBalancers {
var services []*services.Service
for _, serviceName := range c.ServiceConfig[name].LoadBalancer.ServiceNames {
if err := parseLoadBalancers(c.ServiceConfig, nameService); err != nil {
return nil, fmt.Errorf("%w: %w", errInvalidConfig, err)
}

m := make(map[string]*services.Service)
for name, service := range c.ServiceConfig {
m[service.Host] = nameService[name]
}
return m, nil
}

func parseLoadBalancers(conf serviceConfig, nameService map[string]*services.Service) error {
tempNameService := make(map[string]*services.Service)
for name, service := range conf {
if service.LoadBalancer == nil {
continue
}

strategy, err := services.NewStrategy(service.LoadBalancer.Strategy)
if err != nil {
return err
}

lb := services.NewLoadBalancer(service.Host, strategy, service.LoadBalancer.Persistent)

var lbServices []*services.Service
for _, serviceName := range conf[name].LoadBalancer.ServiceNames {
if s, ok := nameService[serviceName]; ok {
services = append(services, s)
lbServices = append(lbServices, s)
} else {
return nil, fmt.Errorf("%w: %s: %w %s", errInvalidConfig, name, errNoServiceWithName, serviceName)
return fmt.Errorf("%w: %s", errNoServiceWithName, serviceName)
}
}
lb.SetServices(services)
if service, ok := nameService[name]; ok {
service.Router = lb
nameService[name] = service
} else {
return nil, fmt.Errorf("%w: %s: %w", errInvalidConfig, name, errNoServiceWithName)
lb.SetServices(lbServices)

tempNameService[name] = &services.Service{
TLS: service.TLS,
Middlewares: service.Middlewares.List(),
Health: createHealthChecker(service.Health),
Router: lb,
}
}

m := make(map[string]*services.Service)
for name, service := range c.ServiceConfig {
m[service.Host] = nameService[name]
for name, service := range tempNameService {
nameService[name] = service
}
return m, nil
return nil
}
41 changes: 34 additions & 7 deletions config/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,20 @@ func TestConfigServicesError(t *testing.T) {
},
err: errDuplicateHost,
},
"multiple services": {
"multiple routers": {
services: serviceConfig{
"foo": {
Host: "example.com",
routers: routers{
Container: &containerInfo{},
Redirect: "https://example.com",
},
},
},
err: errMustHaveOneService,
err: errMustHaveOneRouter,
},
"invalid strategy": {
services: serviceConfig{
"foo": {
Host: "example.com",
routers: routers{
LoadBalancer: &loadBalancerInfo{
Strategy: "invalid",
Expand All @@ -88,7 +86,6 @@ func TestConfigServicesError(t *testing.T) {
"invalid redirect": {
services: serviceConfig{
"foo": {
Host: "example.com",
routers: routers{
Redirect: "$%^&*",
},
Expand All @@ -99,15 +96,45 @@ func TestConfigServicesError(t *testing.T) {
"load balancer with invalid service name": {
services: serviceConfig{
"foo": {
Host: "example.com",
routers: routers{
LoadBalancer: &loadBalancerInfo{
ServiceNames: []string{"invalid"},
},
},
},
},
err: errInvalidConfig,
err: errNoServiceWithName,
},
"load balancer tries to load balance itself": {
services: serviceConfig{
"foo": {
routers: routers{
LoadBalancer: &loadBalancerInfo{
ServiceNames: []string{"foo"},
},
},
},
},
err: errNoServiceWithName,
},
"load balancer tries to load balance another load balancer": {
services: serviceConfig{
"foo": {
routers: routers{
LoadBalancer: &loadBalancerInfo{
ServiceNames: []string{"bar"},
},
},
},
"bar": {
routers: routers{
LoadBalancer: &loadBalancerInfo{
ServiceNames: []string{},
},
},
},
},
err: errNoServiceWithName,
},
}

Expand Down

0 comments on commit 36ee4c6

Please sign in to comment.