Skip to content

Commit 4465cd7

Browse files
authored
Merge pull request #17 from threefoldtech/development_load_balance_gateway
Development load balance gateway
2 parents c6e59ac + 4581187 commit 4465cd7

File tree

4 files changed

+168
-48
lines changed

4 files changed

+168
-48
lines changed

pkg/gateway/gateway.go

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -558,10 +558,9 @@ func (g *gatewayModule) SetNamedProxy(wlID string, config zos.GatewayNameProxy)
558558
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
559559
defer cancel()
560560

561-
if len(config.Backends) != 1 {
562-
return "", fmt.Errorf("only one backend is supported got '%d'", len(config.Backends))
561+
if len(config.Backends) == 0 {
562+
return "", fmt.Errorf("at least one backend is needed got '%d'", len(config.Backends))
563563
}
564-
565564
twinID, _, _, err := gridtypes.WorkloadID(wlID).Parts()
566565
if err != nil {
567566
return "", errors.Wrap(err, "invalid workload id")
@@ -600,10 +599,9 @@ func (g *gatewayModule) SetFQDNProxy(wlID string, config zos.GatewayFQDNProxy) e
600599
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
601600
defer cancel()
602601

603-
if len(config.Backends) != 1 {
604-
return fmt.Errorf("only one backend is supported got '%d'", len(config.Backends))
602+
if len(config.Backends) == 0 {
603+
return fmt.Errorf("at least one backend is needed got '%d'", len(config.Backends))
605604
}
606-
607605
cfg, err := g.ensureGateway(ctx, false)
608606
if err != nil {
609607
return err
@@ -631,14 +629,12 @@ func (g *gatewayModule) setupRouting(ctx context.Context, wlID string, fqdn stri
631629
g.domainLock.Lock()
632630
defer g.domainLock.Unlock()
633631

634-
backend := config.Backends[0]
635-
636-
if err := backend.Valid(config.TLSPassthrough); err != nil {
637-
return errors.Wrapf(err, "failed to validate backend '%s'", backend)
632+
if err := zos.ValidateBackends(config.Backends, config.TLSPassthrough); err != nil {
633+
return err
638634
}
639635

640636
if _, ok := g.getReservedDomain(fqdn); ok {
641-
return errors.New("domain already registered")
637+
return errors.Errorf("domain already registered: %s", fqdn)
642638
}
643639

644640
if config.Network == nil {
@@ -661,23 +657,28 @@ func (g *gatewayModule) setupRouting(ctx context.Context, wlID string, fqdn stri
661657
return errors.Wrap(err, "failed to get user network")
662658
}
663659
ns := net.Namespace(ctx, netID)
664-
backend, err = g.nncEnsure(wlID, ns, config.Backends[0])
665-
if err != nil {
666-
return errors.Wrap(err, "failed to ensure local gateway")
667-
}
668660

669-
if !config.TLSPassthrough {
670-
// if tls passthrough is disabled traefik expecting backend
671-
// to be in the format http://<ip>:port
672-
backend = zos.Backend(fmt.Sprintf("http://%s", backend))
661+
processedBackends := []zos.Backend{}
662+
for _, backend := range config.Backends {
663+
processedBackend, err := g.nncEnsure(wlID, ns, backend)
664+
if err != nil {
665+
return errors.Wrap(err, "failed to ensure local gateway")
666+
}
667+
668+
if !config.TLSPassthrough {
669+
// Format for non-TLS passthrough
670+
processedBackend = zos.Backend(fmt.Sprintf("http://%s", processedBackend))
671+
}
672+
673+
processedBackends = append(processedBackends, processedBackend)
674+
673675
}
674676

675-
config.Backends = []zos.Backend{backend}
677+
config.Backends = processedBackends
676678
return g.setupRoutingGeneric(wlID, fqdn, tlsConfig, config)
677679
}
678680

679681
func (g *gatewayModule) setupRoutingGeneric(wlID string, fqdn string, tlsConfig TlsConfig, config zos.GatewayBase) error {
680-
backend := config.Backends[0]
681682
var rule string
682683
if config.TLSPassthrough {
683684
rule = fmt.Sprintf("HostSNI(`%s`)", fqdn)
@@ -688,11 +689,15 @@ func (g *gatewayModule) setupRoutingGeneric(wlID string, fqdn string, tlsConfig
688689
rule = fmt.Sprintf("Host(`%s`)", fqdn)
689690
}
690691

691-
var server Server
692-
if config.TLSPassthrough {
693-
server = Server{Address: string(backend)}
694-
} else {
695-
server = Server{Url: string(backend)}
692+
servers := []Server{}
693+
for _, backend := range config.Backends {
694+
var server Server
695+
if config.TLSPassthrough {
696+
server = Server{Address: string(backend)}
697+
} else {
698+
server = Server{Url: string(backend)}
699+
}
700+
servers = append(servers, server)
696701
}
697702

698703
route := fmt.Sprintf("%s-route", wlID)
@@ -709,7 +714,7 @@ func (g *gatewayModule) setupRoutingGeneric(wlID string, fqdn string, tlsConfig
709714
Services: map[string]Service{
710715
wlID: {
711716
LoadBalancer: LoadBalancer{
712-
Servers: []Server{server},
717+
Servers: servers,
713718
},
714719
},
715720
},

pkg/gateway_light/gateway.go

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -569,10 +569,9 @@ func (g *gatewayModule) SetNamedProxy(wlID string, config zos.GatewayNameProxy)
569569
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
570570
defer cancel()
571571

572-
if len(config.Backends) != 1 {
573-
return "", fmt.Errorf("only one backend is supported got '%d'", len(config.Backends))
572+
if len(config.Backends) == 0 {
573+
return "", fmt.Errorf("at least one backend is needed got '%d'", len(config.Backends))
574574
}
575-
576575
twinID, _, _, err := gridtypes.WorkloadID(wlID).Parts()
577576
if err != nil {
578577
return "", errors.Wrap(err, "invalid workload id")
@@ -611,10 +610,9 @@ func (g *gatewayModule) SetFQDNProxy(wlID string, config zos.GatewayFQDNProxy) e
611610
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
612611
defer cancel()
613612

614-
if len(config.Backends) != 1 {
615-
return fmt.Errorf("only one backend is supported got '%d'", len(config.Backends))
613+
if len(config.Backends) == 0 {
614+
return fmt.Errorf("at least one backend is needed got '%d'", len(config.Backends))
616615
}
617-
618616
domain, err := g.ensureGateway(ctx, false)
619617
if err != nil {
620618
return err
@@ -642,10 +640,8 @@ func (g *gatewayModule) setupRouting(ctx context.Context, wlID string, fqdn stri
642640
g.domainLock.Lock()
643641
defer g.domainLock.Unlock()
644642

645-
backend := config.Backends[0]
646-
647-
if err := backend.Valid(config.TLSPassthrough); err != nil {
648-
return errors.Wrapf(err, "failed to validate backend '%s'", backend)
643+
if err := zos.ValidateBackends(config.Backends, config.TLSPassthrough); err != nil {
644+
return err
649645
}
650646

651647
if _, ok := g.getReservedDomain(fqdn); ok {
@@ -689,7 +685,6 @@ func (g *gatewayModule) setupRouting(ctx context.Context, wlID string, fqdn stri
689685
}
690686

691687
func (g *gatewayModule) setupRoutingGeneric(wlID string, fqdn string, tlsConfig TlsConfig, config zos.GatewayBase) error {
692-
backend := config.Backends[0]
693688
var rule string
694689
if config.TLSPassthrough {
695690
rule = fmt.Sprintf("HostSNI(`%s`)", fqdn)
@@ -700,11 +695,15 @@ func (g *gatewayModule) setupRoutingGeneric(wlID string, fqdn string, tlsConfig
700695
rule = fmt.Sprintf("Host(`%s`)", fqdn)
701696
}
702697

703-
var server Server
704-
if config.TLSPassthrough {
705-
server = Server{Address: string(backend)}
706-
} else {
707-
server = Server{Url: string(backend)}
698+
servers := []Server{}
699+
for _, backend := range config.Backends {
700+
var server Server
701+
if config.TLSPassthrough {
702+
server = Server{Address: string(backend)}
703+
} else {
704+
server = Server{Url: string(backend)}
705+
}
706+
servers = append(servers, server)
708707
}
709708

710709
route := fmt.Sprintf("%s-route", wlID)
@@ -721,7 +720,7 @@ func (g *gatewayModule) setupRoutingGeneric(wlID string, fqdn string, tlsConfig
721720
Services: map[string]Service{
722721
wlID: {
723722
LoadBalancer: LoadBalancer{
724-
Servers: []Server{server},
723+
Servers: servers,
725724
},
726725
},
727726
},

pkg/gridtypes/zos/gw.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/url"
99
"strconv"
1010

11+
"github.com/hashicorp/go-multierror"
1112
"github.com/pkg/errors"
1213
"github.com/threefoldtech/zosbase/pkg/gridtypes"
1314
)
@@ -44,6 +45,16 @@ func (b Backend) Valid(tlsPassthrough bool) error {
4445
return nil
4546
}
4647

48+
func ValidateBackends(backends []Backend, tlsPassthrough bool) error {
49+
var errs error
50+
for _, backend := range backends {
51+
if err := backend.Valid(tlsPassthrough); err != nil {
52+
errs = multierror.Append(errs, errors.Wrapf(err, "failed to validate backend '%s'", backend))
53+
}
54+
}
55+
return errs
56+
}
57+
4758
func asIpPort(a string) (ip net.IP, port uint16, err error) {
4859
h, p, err := net.SplitHostPort(a)
4960
if err != nil {
@@ -105,10 +116,6 @@ func (g GatewayBase) Valid(getter gridtypes.WorkloadGetter) error {
105116
return fmt.Errorf("backends list can not be empty")
106117
}
107118

108-
if len(g.Backends) != 1 {
109-
return fmt.Errorf("only one backend is supported")
110-
}
111-
112119
for _, backend := range g.Backends {
113120
if err := backend.Valid(g.TLSPassthrough); err != nil {
114121
return errors.Wrapf(err, "failed to validate backend '%s'", backend)

pkg/gridtypes/zos/gw_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package zos
33
import (
44
"testing"
55

6+
"github.com/hashicorp/go-multierror"
67
"github.com/stretchr/testify/require"
78
)
89

@@ -159,3 +160,111 @@ func TestValidBackendIP6(t *testing.T) {
159160
require.Error(err)
160161
})
161162
}
163+
164+
func TestValidateBackends(t *testing.T) {
165+
require := require.New(t)
166+
167+
t.Run("empty backends", func(t *testing.T) {
168+
backends := []Backend{
169+
"",
170+
}
171+
err := ValidateBackends(backends, true)
172+
require.Error(err)
173+
174+
err = ValidateBackends(backends, false)
175+
require.Error(err)
176+
})
177+
178+
t.Run("all valid backends with tlsPassthrough=true", func(t *testing.T) {
179+
backends := []Backend{
180+
"1.1.1.1:80",
181+
"2.2.2.2:443",
182+
"[2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF]:8080",
183+
}
184+
err := ValidateBackends(backends, true)
185+
require.NoError(err)
186+
})
187+
188+
t.Run("all valid backends with tlsPassthrough=false", func(t *testing.T) {
189+
backends := []Backend{
190+
"http://1.1.1.1",
191+
"http://2.2.2.2:443",
192+
"http://[2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF]",
193+
}
194+
err := ValidateBackends(backends, false)
195+
require.NoError(err)
196+
})
197+
198+
t.Run("mixed valid and invalid backends with tlsPassthrough=true", func(t *testing.T) {
199+
backends := []Backend{
200+
"1.1.1.1:80",
201+
"http://2.2.2.2:443", // invalid (should be IP:port without http://)
202+
"2.2.2.2", // invalid (missing port)
203+
"3.3.3.3:port", // invalid (non-numeric port)
204+
"127.0.0.1:8080",
205+
"[::1]:8080",
206+
"[2001:db8::1]:8080",
207+
"2001:db8::1:8080", // invalid (wrong IPv6 format)
208+
}
209+
err := ValidateBackends(backends, true)
210+
require.Error(err)
211+
merr, ok := err.(*multierror.Error)
212+
require.True(ok)
213+
require.Equal(4, len(merr.Errors))
214+
})
215+
216+
t.Run("mixed valid and invalid backends with tlsPassthrough=false", func(t *testing.T) {
217+
backends := []Backend{
218+
"http://1.1.1.1",
219+
"1.1.1.1:80", // invalid (needs http://)
220+
"http://2.2.2.2:443",
221+
"https://3.3.3.3", // invalid (wrong scheme)
222+
"http://localhost", // invalid (loopback)
223+
"http://127.0.0.1", // invalid (loopback)
224+
"http://[::1]", // invalid (loopback)
225+
"http://[2001:db8::1]:8080",
226+
}
227+
err := ValidateBackends(backends, false)
228+
require.Error(err)
229+
merr, ok := err.(*multierror.Error)
230+
require.True(ok)
231+
require.Equal(5, len(merr.Errors))
232+
})
233+
234+
t.Run("scheme mismatch using https", func(t *testing.T) {
235+
backends := []Backend{
236+
"https://1.1.1.1",
237+
}
238+
err := ValidateBackends(backends, false)
239+
require.Error(err)
240+
})
241+
242+
t.Run("scheme mismatch using http with tlsPassthrough=true", func(t *testing.T) {
243+
backends := []Backend{
244+
"http://1.1.1.1:80",
245+
}
246+
err := ValidateBackends(backends, true)
247+
require.Error(err)
248+
})
249+
250+
t.Run("invalid backends", func(t *testing.T) {
251+
backends := []Backend{
252+
"invalid",
253+
"1.1.1.1:port",
254+
"http://invalid",
255+
"ftp://1.1.1.1",
256+
}
257+
258+
err := ValidateBackends(backends, true)
259+
require.Error(err)
260+
merr, ok := err.(*multierror.Error)
261+
require.True(ok)
262+
require.Equal(4, len(merr.Errors))
263+
264+
err = ValidateBackends(backends, false)
265+
require.Error(err)
266+
merr, ok = err.(*multierror.Error)
267+
require.True(ok)
268+
require.Equal(4, len(merr.Errors))
269+
})
270+
}

0 commit comments

Comments
 (0)