Skip to content

Commit ad0e685

Browse files
routing: fix first route load signaling (#3447)
Dataclients may load routes with arbitrary delays so it may happen that of two dataclients the first loads routes twice and the second zero times which would signal first route loaded condition although routing table would only contain routes from the first dataclient. This change fixes route update logic to signal first load only after routes from all configured dataclients are received at least once. Updates #1709 Updates #1710 --------- Signed-off-by: Alexander Yastrebov <alexander.yastrebov@zalando.de>
1 parent 3025d16 commit ad0e685

5 files changed

Lines changed: 52 additions & 30 deletions

File tree

routing/datasource.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,17 @@ func applyIncoming(defs routeDefs, d *incomingData) routeDefs {
140140
return defs
141141
}
142142

143+
type mergedDefs struct {
144+
routes []*eskip.Route
145+
clients map[DataClient]struct{}
146+
}
147+
143148
// merges the route definitions from multiple data clients by route id
144-
func mergeDefs(defsByClient map[DataClient]routeDefs) []*eskip.Route {
149+
func mergeDefs(defsByClient map[DataClient]routeDefs) mergedDefs {
150+
clients := make(map[DataClient]struct{}, len(defsByClient))
145151
mergeByID := make(routeDefs)
146-
for _, defs := range defsByClient {
152+
for c, defs := range defsByClient {
153+
clients[c] = struct{}{}
147154
for id, def := range defs {
148155
mergeByID[id] = def
149156
}
@@ -153,7 +160,7 @@ func mergeDefs(defsByClient map[DataClient]routeDefs) []*eskip.Route {
153160
for _, def := range mergeByID {
154161
all = append(all, def)
155162
}
156-
return all
163+
return mergedDefs{routes: all, clients: clients}
157164
}
158165

159166
// receives the initial set of the route definitiosn and their
@@ -162,9 +169,9 @@ func mergeDefs(defsByClient map[DataClient]routeDefs) []*eskip.Route {
162169
//
163170
// The active set of routes from last successful update are used until the
164171
// next successful update.
165-
func receiveRouteDefs(o Options, quit <-chan struct{}) <-chan []*eskip.Route {
172+
func receiveRouteDefs(o Options, quit <-chan struct{}) <-chan mergedDefs {
166173
in := make(chan *incomingData)
167-
out := make(chan []*eskip.Route)
174+
out := make(chan mergedDefs)
168175
defsByClient := make(map[DataClient]routeDefs)
169176

170177
for _, c := range o.DataClients {
@@ -524,6 +531,7 @@ type routeTable struct {
524531
routes []*Route // only used for closing
525532
validRoutes []*eskip.Route
526533
invalidRoutes []*eskip.Route
534+
clients map[DataClient]struct{}
527535
created time.Time
528536
}
529537

@@ -548,18 +556,19 @@ func receiveRouteMatcher(o Options, out chan<- *routeTable, quit <-chan struct{}
548556
var (
549557
rt *routeTable
550558
outRelay chan<- *routeTable
551-
updatesRelay <-chan []*eskip.Route
559+
updatesRelay <-chan mergedDefs
552560
updateId int
553561
)
554562
updatesRelay = updates
555563
for {
556564
select {
557-
case defs := <-updatesRelay:
565+
case mdefs := <-updatesRelay:
558566
updateId++
559567
start := time.Now()
560568

561569
o.Log.Infof("route settings received, id: %d", updateId)
562570

571+
defs := mdefs.routes
563572
for i := range o.PreProcessors {
564573
defs = o.PreProcessors[i].Do(defs)
565574
}
@@ -599,6 +608,7 @@ func receiveRouteMatcher(o Options, out chan<- *routeTable, quit <-chan struct{}
599608
routes: routes,
600609
validRoutes: validRoutes,
601610
invalidRoutes: invalidRoutes,
611+
clients: mdefs.clients,
602612
created: start,
603613
}
604614
updatesRelay = nil

routing/export_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package routing
22

3-
import "time"
3+
import (
4+
"time"
5+
6+
"github.com/zalando/skipper/eskip"
7+
)
48

59
var (
610
ExportProcessRouteDef = processRouteDef
@@ -14,3 +18,7 @@ var (
1418
func SetNow(r *EndpointRegistry, now func() time.Time) {
1519
r.now = now
1620
}
21+
22+
func (rl *RouteLookup) ValidRoutes() []*eskip.Route {
23+
return rl.rt.validRoutes
24+
}

routing/routing.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,6 @@ func (r *Routing) ServeHTTP(w http.ResponseWriter, req *http.Request) {
338338
}
339339

340340
func (r *Routing) startReceivingUpdates(o Options) {
341-
dc := len(o.DataClients)
342341
c := make(chan *routeTable)
343342
go receiveRouteMatcher(o, c, r.quit)
344343
go func() {
@@ -347,8 +346,7 @@ func (r *Routing) startReceivingUpdates(o Options) {
347346
case rt := <-c:
348347
r.routeTable.Store(rt)
349348
if !r.firstLoadSignaled {
350-
dc--
351-
if dc == 0 {
349+
if len(rt.clients) == len(o.DataClients) {
352350
close(r.firstLoad)
353351
r.firstLoadSignaled = true
354352
}
@@ -398,20 +396,20 @@ func (r *Routing) FirstLoad() <-chan struct{} {
398396
// against is found, the feature is experimental and its exported interface may
399397
// change.
400398
type RouteLookup struct {
401-
matcher *matcher
399+
rt *routeTable
402400
}
403401

404402
// Do executes the lookup against the captured routing table. Equivalent to
405403
// Routing.Route().
406404
func (rl *RouteLookup) Do(req *http.Request) (*Route, map[string]string) {
407-
return rl.matcher.match(req)
405+
return rl.rt.m.match(req)
408406
}
409407

410408
// Get returns a captured generation of the lookup table. This feature is
411409
// experimental. See the description of the RouteLookup type.
412410
func (r *Routing) Get() *RouteLookup {
413411
rt := r.routeTable.Load().(*routeTable)
414-
return &RouteLookup{matcher: rt.m}
412+
return &RouteLookup{rt: rt}
415413
}
416414

417415
// Close closes routing, routeTable and stops statemachine for receiving routes.

routing/routing_test.go

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -955,38 +955,36 @@ func TestSignalFirstLoad(t *testing.T) {
955955
})
956956

957957
t.Run("multiple data clients", func(t *testing.T) {
958-
dc1 := testdataclient.New([]*eskip.Route{{}})
958+
const pollTimeout = 12 * time.Millisecond
959+
960+
dc1 := testdataclient.New([]*eskip.Route{{Id: "r1", Backend: "https://foo.example.org"}})
959961
defer dc1.Close()
960962

961-
dc2 := testdataclient.New([]*eskip.Route{{}})
963+
dc2 := testdataclient.New([]*eskip.Route{{Id: "r2", Backend: "https://bar.example.org"}})
962964
defer dc2.Close()
963965

966+
// Schedule r1 update right away and delay r2 update
967+
go func() {
968+
dc1.Update([]*eskip.Route{{Id: "r1", Backend: "https://baz.example.org"}}, nil)
969+
}()
970+
dc2.WithLoadAllDelay(10 * pollTimeout)
971+
964972
l := loggingtest.New()
965973
defer l.Close()
966974

967975
rt := routing.New(routing.Options{
968976
SignalFirstLoad: true,
969977
FilterRegistry: builtin.MakeRegistry(),
970978
DataClients: []routing.DataClient{dc1, dc2},
971-
PollTimeout: 12 * time.Millisecond,
979+
PollTimeout: pollTimeout,
972980
Log: l,
973981
})
974982
defer rt.Close()
975983

976-
select {
977-
case <-rt.FirstLoad():
978-
t.Error("the first load signal was not blocking")
979-
default:
980-
}
984+
<-rt.FirstLoad()
981985

982-
if err := l.WaitForN("route settings applied", 2, 12*time.Millisecond); err != nil {
983-
t.Error("failed to receive route settings", err)
984-
}
985-
986-
select {
987-
case <-rt.FirstLoad():
988-
default:
989-
t.Error("the first load signal was blocking")
986+
if validRoutes := rt.Get().ValidRoutes(); len(validRoutes) != 2 {
987+
t.Errorf("expected 2 valid routes, got: %v", validRoutes)
990988
}
991989
})
992990
}

routing/testdataclient/dataclient.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package testdataclient
99

1010
import (
1111
"errors"
12+
"time"
1213

1314
"github.com/zalando/skipper/eskip"
1415
)
@@ -24,6 +25,7 @@ type Client struct {
2425
upsert []*eskip.Route
2526
deletedIDs []string
2627
failNext int
28+
loadAllDelay time.Duration
2729
signalUpdate chan incomingUpdate
2830
quit chan struct{}
2931
}
@@ -61,6 +63,8 @@ func (c *Client) LoadAll() ([]*eskip.Route, error) {
6163
return nil, errors.New("failed to get routes")
6264
}
6365

66+
time.Sleep(c.loadAllDelay)
67+
6468
routes := make([]*eskip.Route, 0, len(c.routes))
6569
for _, r := range c.routes {
6670
routes = append(routes, r)
@@ -127,6 +131,10 @@ func (c *Client) FailNext() {
127131
c.failNext++
128132
}
129133

134+
func (c *Client) WithLoadAllDelay(d time.Duration) {
135+
c.loadAllDelay = d
136+
}
137+
130138
func (c *Client) Close() {
131139
close(c.quit)
132140
}

0 commit comments

Comments
 (0)