Skip to content

Commit 1d84ea6

Browse files
routing: ignore duplicate dataclients (#3527)
Ignore duplicate dataclients since this is likely a misconfiguration. Note that datasource still allows multiple pointer dataclients of the same type or non-equal value-type dataclients. Routing constructor does not return error and we generally do not use panic therefore ignoring and logging an error is the only simple option. This is also needed because datasource manages routes keyed by DataClient and to correctly signal first route load. Follow up on #3447 Signed-off-by: Alexander Yastrebov <alexander.yastrebov@zalando.de>
1 parent 52da8f0 commit 1d84ea6

3 files changed

Lines changed: 64 additions & 1 deletion

File tree

routing/export_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@ func SetNow(r *EndpointRegistry, now func() time.Time) {
2222
func (rl *RouteLookup) ValidRoutes() []*eskip.Route {
2323
return rl.rt.validRoutes
2424
}
25+
26+
func (rl *RouteLookup) DataClients() map[DataClient]struct{} {
27+
return rl.rt.clients
28+
}

routing/routing.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package routing
33
import (
44
"encoding/json"
55
"fmt"
6+
"maps"
67
"net/http"
8+
"slices"
79
"strconv"
810
"strings"
911
"sync/atomic"
@@ -259,6 +261,19 @@ func New(o Options) *Routing {
259261
o.Log = &logging.DefaultLog{}
260262
}
261263

264+
uniqueClients := make(map[DataClient]struct{}, len(o.DataClients))
265+
for i, c := range o.DataClients {
266+
if _, ok := uniqueClients[c]; ok {
267+
o.Log.Errorf("Duplicate data clients are not allowed, ignoring client #%d: %T", i, c)
268+
continue
269+
}
270+
uniqueClients[c] = struct{}{}
271+
}
272+
if len(uniqueClients) != len(o.DataClients) {
273+
o.Log.Errorf("Ignored %d duplicate data clients", len(o.DataClients)-len(uniqueClients))
274+
o.DataClients = slices.Collect(maps.Keys(uniqueClients))
275+
}
276+
262277
r := &Routing{log: o.Log, firstLoad: make(chan struct{}), quit: make(chan struct{})}
263278
r.metrics = o.Metrics
264279
if !o.SignalFirstLoad {

routing/routing_test.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,13 @@ func TestUpdateFailsRecovers(t *testing.T) {
861861
check("baz", "/baz", "https://baz-new.example.org", true)
862862
}
863863

864+
type valueDataClient struct{}
865+
866+
var _ routing.DataClient = valueDataClient{}
867+
868+
func (v valueDataClient) LoadAll() ([]*eskip.Route, error) { return nil, nil }
869+
func (v valueDataClient) LoadUpdate() ([]*eskip.Route, []string, error) { return nil, nil, nil }
870+
864871
func TestSignalFirstLoad(t *testing.T) {
865872
t.Run("disabled", func(t *testing.T) {
866873
dc := testdataclient.New([]*eskip.Route{{}})
@@ -963,6 +970,10 @@ func TestSignalFirstLoad(t *testing.T) {
963970
dc2 := testdataclient.New([]*eskip.Route{{Id: "r2", Backend: "https://bar.example.org"}})
964971
defer dc2.Close()
965972

973+
// duplicate data clients, will be removed
974+
dc3 := dc1
975+
dc4 := dc2
976+
966977
// Schedule r1 update right away and delay r2 update
967978
go func() {
968979
dc1.Update([]*eskip.Route{{Id: "r1", Backend: "https://baz.example.org"}}, nil)
@@ -975,7 +986,7 @@ func TestSignalFirstLoad(t *testing.T) {
975986
rt := routing.New(routing.Options{
976987
SignalFirstLoad: true,
977988
FilterRegistry: builtin.MakeRegistry(),
978-
DataClients: []routing.DataClient{dc1, dc2},
989+
DataClients: []routing.DataClient{dc1, dc2, dc3, dc4},
979990
PollTimeout: pollTimeout,
980991
Log: l,
981992
})
@@ -986,5 +997,38 @@ func TestSignalFirstLoad(t *testing.T) {
986997
if validRoutes := rt.Get().ValidRoutes(); len(validRoutes) != 2 {
987998
t.Errorf("expected 2 valid routes, got: %v", validRoutes)
988999
}
1000+
1001+
if clients := rt.Get().DataClients(); len(clients) != 2 {
1002+
t.Errorf("expected 2 dataclients (excluding duplicates), got: %v", clients)
1003+
}
9891004
})
9901005
}
1006+
1007+
func TestDuplicateDataClients(t *testing.T) {
1008+
l := loggingtest.New()
1009+
defer l.Close()
1010+
1011+
dc1 := testdataclient.New([]*eskip.Route{{Id: "r1", Backend: "https://foo.example.org"}})
1012+
defer dc1.Close()
1013+
1014+
dc2 := dc1 // duplicate, will be removed
1015+
dc3 := valueDataClient{}
1016+
dc4 := valueDataClient{} // duplicate, will be removed
1017+
1018+
rt := routing.New(routing.Options{
1019+
SignalFirstLoad: true,
1020+
FilterRegistry: builtin.MakeRegistry(),
1021+
DataClients: []routing.DataClient{dc1, dc2, dc3, dc4},
1022+
PollTimeout: pollTimeout,
1023+
Log: l,
1024+
})
1025+
defer rt.Close()
1026+
1027+
<-rt.FirstLoad()
1028+
1029+
l.WaitFor("Removed 2 duplicate data clients", 10*pollTimeout)
1030+
1031+
if clients := rt.Get().DataClients(); len(clients) != 2 {
1032+
t.Errorf("expected 2 dataclients, got: %v", clients)
1033+
}
1034+
}

0 commit comments

Comments
 (0)