Merge lp:~chipaca/ubuntu-push/redialer into lp:ubuntu-push

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: 31
Merged at revision: 26
Proposed branch: lp:~chipaca/ubuntu-push/redialer
Merge into: lp:ubuntu-push
Prerequisite: lp:~chipaca/ubuntu-push/endpoint-not-bus
Diff against target: 1152 lines (+393/-183)
16 files modified
bus/bus.go (+3/-8)
bus/bus_test.go (+5/-20)
bus/connectivity/connectivity.go (+12/-33)
bus/connectivity/connectivity_test.go (+20/-41)
bus/connectivity/webchecker_test.go (+15/-5)
bus/endpoint.go (+34/-12)
bus/endpoint_test.go (+27/-0)
bus/networkmanager/networkmanager_test.go (+8/-8)
bus/notifications/raw_test.go (+5/-5)
bus/testing/testing_bus.go (+7/-13)
bus/testing/testing_bus_test.go (+13/-19)
bus/testing/testing_endpoint.go (+29/-8)
bus/testing/testing_endpoint_test.go (+43/-9)
bus/urldispatcher/urldispatcher_test.go (+2/-2)
util/redialer.go (+86/-0)
util/redialer_test.go (+84/-0)
To merge this branch: bzr merge lp:~chipaca/ubuntu-push/redialer
Reviewer Review Type Date Requested Status
Samuele Pedroni Approve
Review via email: mp+202870@code.launchpad.net

Commit message

reworked bus.Endpoint to have a Dial() method, added an AutoRedialer() and put the redialing logic in there (for use on sessionbus as well, later).

Description of the change

Reworked bus.Endpoint to have a Dial() method, added an AutoRedialer() and put the redialing logic in there (for use on sessionbus as well, later).

To post a comment you must log in.
lp:~chipaca/ubuntu-push/redialer updated
24. By John Lenton

be less silly for tests

Revision history for this message
Samuele Pedroni (pedronis) wrote :

I think the random jitter could be included after 20sec already, also probably it should be optional?

./bus/connectivity/ and /bus/testing run tests twice because of double invoking TestingT

Revision history for this message
John Lenton (chipaca) wrote :

Agreed about the jitter on both counts; will come back to it and refactor it a tad once it's been used so I don't miss anything.

Fixing the double-invoking, and the test slowness.

Revision history for this message
Samuele Pedroni (pedronis) wrote :

406 + endp.Close()

maybe there should be a defer endp.Close around as well

lp:~chipaca/ubuntu-push/redialer updated
25. By John Lenton

made timeouts public, and stomp on them in the tests

Revision history for this message
John Lenton (chipaca) wrote :

defer endp.Close around where? Sorry if I'm being obtuse...

lp:~chipaca/ubuntu-push/redialer updated
26. By John Lenton

merged trunk

Revision history for this message
Samuele Pedroni (pedronis) wrote :

 so here is a commented version of what I was saying: https://pastebin.canonical.com/103599/

lp:~chipaca/ubuntu-push/redialer updated
27. By John Lenton

made the jitter something provided by the dialer itself

28. By John Lenton

Merged endpoint-not-bus into redialer.

29. By John Lenton

merged trunk

30. By John Lenton

make Jitter take the base duration as argument

31. By John Lenton

made the docs a bit better

Revision history for this message
Samuele Pedroni (pedronis) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bus/bus.go'
2--- bus/bus.go 2014-01-20 19:02:17 +0000
3+++ bus/bus.go 2014-01-27 13:02:37 +0000
4@@ -31,7 +31,7 @@
5 // This is the Bus itself.
6 type Bus interface {
7 String() string
8- Connect(Address, logger.Logger) (Endpoint, error)
9+ Endpoint(Address, logger.Logger) Endpoint
10 }
11
12 type concreteBus dbus.StandardBus
13@@ -58,13 +58,8 @@
14 }
15
16 // Connect() connects to the bus, and returns the bus endpoint (and/or error).
17-func (bus concreteBus) Connect(addr Address, log logger.Logger) (Endpoint, error) {
18- conn, err := dbus.Connect(bus.dbusType())
19- if err != nil {
20- return nil, err
21- } else {
22- return newEndpoint(conn, addr, log), nil
23- }
24+func (bus concreteBus) Endpoint(addr Address, log logger.Logger) Endpoint {
25+ return newEndpoint(bus, addr, log)
26 }
27
28 /*
29
30=== modified file 'bus/bus_test.go'
31--- bus/bus_test.go 2014-01-20 12:36:21 +0000
32+++ bus/bus_test.go 2014-01-27 13:02:37 +0000
33@@ -22,12 +22,11 @@
34 "launchpad.net/go-dbus/v1"
35 . "launchpad.net/gocheck"
36 "launchpad.net/ubuntu-push/logger"
37- "os"
38 "testing"
39 )
40
41 // hook up gocheck
42-func Test(t *testing.T) { TestingT(t) }
43+func BusTest(t *testing.T) { TestingT(t) }
44
45 type BusSuite struct{}
46
47@@ -46,22 +45,8 @@
48 c.Check(SessionBus.(concreteBus).dbusType(), DeepEquals, dbus.SessionBus)
49 }
50
51-// Tests that we can connect to the *actual* system bus.
52-// XXX maybe connect to a mock/fake/etc bus?
53-func (s *BusSuite) TestConnect(c *C) {
54- b, err := SystemBus.Connect(Address{"", "", ""}, nullog)
55- c.Assert(err, IsNil)
56- defer b.Close()
57-}
58-
59-// Test that if we try to connect to the session bus when no session
60-// bus is available, we get a reasonable result (i.e., an error).
61-func (s *BusSuite) TestConnectCanFail(c *C) {
62- db := "DBUS_SESSION_BUS_ADDRESS"
63- odb := os.Getenv(db)
64- defer os.Setenv(db, odb)
65- os.Setenv(db, "")
66-
67- _, err := SessionBus.Connect(Address{"", "", ""}, nullog)
68- c.Check(err, NotNil)
69+// Tests that we can get an endpoint back
70+func (s *BusSuite) TestEndpoint(c *C) {
71+ endp := SystemBus.Endpoint(Address{"", "", ""}, nullog)
72+ c.Assert(endp, NotNil)
73 }
74
75=== modified file 'bus/connectivity/connectivity.go'
76--- bus/connectivity/connectivity.go 2014-01-27 13:02:37 +0000
77+++ bus/connectivity/connectivity.go 2014-01-27 13:02:37 +0000
78@@ -28,14 +28,13 @@
79 "launchpad.net/ubuntu-push/bus/networkmanager"
80 "launchpad.net/ubuntu-push/config"
81 "launchpad.net/ubuntu-push/logger"
82+ "launchpad.net/ubuntu-push/util"
83 "time"
84 )
85
86 // the configuration for ConnectedState, with the idea that you'd populate it
87 // from a config file.
88 type Config struct {
89- // a list of timeouts, for backoff. Should be roughly doubling.
90- ConnectTimeouts []config.ConfigTimeDuration
91 // how long to wait after a state change to make sure it's "stable"
92 // before acting on it
93 StabilizingTimeout config.ConfigTimeDuration
94@@ -51,7 +50,7 @@
95 networkStateCh <-chan networkmanager.State
96 config Config
97 log logger.Logger
98- bus bus.Bus
99+ endp bus.Endpoint
100 connAttempts uint32
101 webget func(ch chan<- bool)
102 webgetCh chan bool
103@@ -60,41 +59,19 @@
104 timer *time.Timer
105 }
106
107-// implements the logic for connect timeouts backoff
108-//
109-// (walk the list of timeouts, and repeat the last one until done; cope with
110-// the list being empty; keep track of connection attempts).
111-func (cs *connectedState) connectTimeout() time.Duration {
112- var timeout config.ConfigTimeDuration
113- timeouts := cs.config.ConnectTimeouts
114- if cs.connAttempts < uint32(len(timeouts)) {
115- timeout = timeouts[cs.connAttempts]
116- } else if len(timeouts) > 0 {
117- timeout = cs.config.ConnectTimeouts[len(timeouts)-1]
118- }
119- cs.connAttempts++
120- return timeout.Duration
121-}
122-
123 // start connects to the bus, gets the initial NetworkManager state, and sets
124 // up the watch.
125 func (cs *connectedState) start() networkmanager.State {
126 var initial networkmanager.State
127 for {
128- time.Sleep(cs.connectTimeout())
129- cs.log.Debugf("Starting DBus connection attempt %d\n", cs.connAttempts)
130- conn, err := cs.bus.Connect(networkmanager.BusAddress, cs.log)
131- if err != nil {
132- cs.log.Debugf("DBus connection attempt %d failed.\n", cs.connAttempts)
133- continue
134- }
135- nm := networkmanager.New(conn, cs.log)
136+ cs.connAttempts += util.AutoRedial(cs.endp)
137+ nm := networkmanager.New(cs.endp, cs.log)
138
139 // Get the current state.
140 initial = nm.GetState()
141 if initial == networkmanager.Unknown {
142- cs.log.Debugf("Failed to get state at attempt.")
143- conn.Close()
144+ cs.log.Debugf("Failed to get state.")
145+ cs.endp.Close()
146 continue
147 }
148
149@@ -102,12 +79,11 @@
150 ch, err := nm.WatchState()
151 if err != nil {
152 cs.log.Debugf("Failed to set up the watch: %s", err)
153- conn.Close()
154+ cs.endp.Close()
155 continue
156 }
157
158 cs.networkStateCh = ch
159- cs.log.Debugf("worked at attempt %d. Resetting counter.\n", cs.connAttempts)
160 return initial
161 }
162 }
163@@ -161,12 +137,15 @@
164 // ConnectedState sends the initial NetworkManager state and changes to it
165 // over the "out" channel. Sends "false" as soon as it detects trouble, "true"
166 // after checking actual connectivity.
167-func ConnectedState(busType bus.Bus, config Config, log logger.Logger, out chan<- bool) {
168+//
169+// The endpoint need not be dialed; connectivity will Dial() and Close()
170+// it as it sees fit.
171+func ConnectedState(endp bus.Endpoint, config Config, log logger.Logger, out chan<- bool) {
172 wg := NewWebchecker(config.ConnectivityCheckURL, config.ConnectivityCheckMD5, log)
173 cs := &connectedState{
174 config: config,
175 log: log,
176- bus: busType,
177+ endp: endp,
178 webget: wg.Webcheck,
179 }
180
181
182=== modified file 'bus/connectivity/connectivity_test.go'
183--- bus/connectivity/connectivity_test.go 2014-01-27 13:02:37 +0000
184+++ bus/connectivity/connectivity_test.go 2014-01-27 13:02:37 +0000
185@@ -24,6 +24,7 @@
186 "launchpad.net/ubuntu-push/config"
187 "launchpad.net/ubuntu-push/logger"
188 "launchpad.net/ubuntu-push/testing/condition"
189+ "launchpad.net/ubuntu-push/util"
190 "net/http/httptest"
191 "testing"
192 "time"
193@@ -32,39 +33,22 @@
194 // hook up gocheck
195 func Test(t *testing.T) { TestingT(t) }
196
197-type ConnSuite struct{}
198+type ConnSuite struct {
199+ timeouts []time.Duration
200+}
201
202 var _ = Suite(&ConnSuite{})
203
204 var nullog = logger.NewSimpleLogger(ioutil.Discard, "error")
205
206-/*
207- tests for connectedState's ConnectTimeout() method
208-*/
209-
210-// When given no timeouts, ConnectTimeout() returns 0 forever
211-func (s *ConnSuite) TestConnectTimeoutWorksWithNoTimeouts(c *C) {
212- cs := connectedState{}
213- c.Check(cs.connectTimeout(), Equals, time.Duration(0))
214- c.Check(cs.connectTimeout(), Equals, time.Duration(0))
215+func (s *ConnSuite) SetUpSuite(c *C) {
216+ s.timeouts = util.Timeouts
217+ util.Timeouts = []time.Duration{0, 0, 0, 0}
218 }
219
220-// when given a few timeouts, ConnectTimeout() returns them each in
221-// turn, and then repeats the last one
222-func (s *ConnSuite) TestConnectTimeoutWorks(c *C) {
223- ts := []config.ConfigTimeDuration{
224- config.ConfigTimeDuration{0},
225- config.ConfigTimeDuration{2 * time.Second},
226- config.ConfigTimeDuration{time.Second},
227- }
228- cs := connectedState{config: Config{ConnectTimeouts: ts}}
229- c.Check(cs.connectTimeout(), Equals, time.Duration(0))
230- c.Check(cs.connectTimeout(), Equals, 2*time.Second)
231- c.Check(cs.connectTimeout(), Equals, time.Second)
232- c.Check(cs.connectTimeout(), Equals, time.Second)
233- c.Check(cs.connectTimeout(), Equals, time.Second)
234- c.Check(cs.connectTimeout(), Equals, time.Second)
235- // ... ad nauseam
236+func (s *ConnSuite) TearDownSuite(c *C) {
237+ util.Timeouts = s.timeouts
238+ s.timeouts = nil
239 }
240
241 /*
242@@ -73,19 +57,16 @@
243
244 // when given a working config and bus, Start() will work
245 func (s *ConnSuite) TestStartWorks(c *C) {
246- cfg := Config{}
247- tb := testingbus.NewTestingBus(condition.Work(true), condition.Work(true), uint32(networkmanager.Connecting))
248- cs := connectedState{config: cfg, log: nullog, bus: tb}
249+ endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.Connecting))
250+ cs := connectedState{config: Config{}, log: nullog, endp: endp}
251
252 c.Check(cs.start(), Equals, networkmanager.Connecting)
253 }
254
255 // if the bus fails a couple of times, we're still OK
256 func (s *ConnSuite) TestStartRetriesConnect(c *C) {
257- timeouts := []config.ConfigTimeDuration{config.ConfigTimeDuration{0}}
258- cfg := Config{ConnectTimeouts: timeouts}
259- tb := testingbus.NewTestingBus(condition.Fail2Work(2), condition.Work(true), uint32(networkmanager.Connecting))
260- cs := connectedState{config: cfg, log: nullog, bus: tb}
261+ endp := testingbus.NewTestingEndpoint(condition.Fail2Work(2), condition.Work(true), uint32(networkmanager.Connecting))
262+ cs := connectedState{config: Config{}, log: nullog, endp: endp}
263
264 c.Check(cs.start(), Equals, networkmanager.Connecting)
265 c.Check(cs.connAttempts, Equals, uint32(3)) // 1 more than the Fail2Work
266@@ -93,9 +74,8 @@
267
268 // when the calls to NetworkManager fail for a bit, we're still OK
269 func (s *ConnSuite) TestStartRetriesCall(c *C) {
270- cfg := Config{}
271- tb := testingbus.NewTestingBus(condition.Work(true), condition.Fail2Work(5), uint32(networkmanager.Connecting))
272- cs := connectedState{config: cfg, log: nullog, bus: tb}
273+ endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Fail2Work(5), uint32(networkmanager.Connecting))
274+ cs := connectedState{config: Config{}, log: nullog, endp: endp}
275
276 c.Check(cs.start(), Equals, networkmanager.Connecting)
277
278@@ -110,11 +90,10 @@
279 1, condition.Work(true), // 1 call to nm works
280 1, condition.Work(false), // 1 call to nm fails
281 0, condition.Work(true)) // and everything works from there on
282- cfg := Config{}
283- tb := testingbus.NewTestingBus(condition.Work(true), nmcond,
284+ endp := testingbus.NewTestingEndpoint(condition.Work(true), nmcond,
285 uint32(networkmanager.Connecting),
286 uint32(networkmanager.ConnectedGlobal))
287- cs := connectedState{config: cfg, log: nullog, bus: tb}
288+ cs := connectedState{config: Config{}, log: nullog, endp: endp}
289
290 c.Check(cs.start(), Equals, networkmanager.Connecting)
291 c.Check(cs.connAttempts, Equals, uint32(2))
292@@ -211,7 +190,7 @@
293 RecheckTimeout: config.ConfigTimeDuration{time.Second},
294 }
295
296- busType := testingbus.NewTestingBus(condition.Work(true), condition.Work(true),
297+ endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true),
298 uint32(networkmanager.ConnectedGlobal),
299 uint32(networkmanager.ConnectedGlobal),
300 uint32(networkmanager.Disconnected),
301@@ -220,7 +199,7 @@
302 out := make(chan bool)
303 dt := time.Second / 10
304 timer := time.NewTimer(dt)
305- go ConnectedState(busType, cfg, nullog, out)
306+ go ConnectedState(endp, cfg, nullog, out)
307 var v bool
308 expecteds := []bool{
309 false, // first state is always false
310
311=== modified file 'bus/connectivity/webchecker_test.go'
312--- bus/connectivity/webchecker_test.go 2014-01-27 13:02:37 +0000
313+++ bus/connectivity/webchecker_test.go 2014-01-27 13:02:37 +0000
314@@ -18,15 +18,15 @@
315
316 import (
317 . "launchpad.net/gocheck"
318+ "launchpad.net/ubuntu-push/util"
319 "net/http"
320 "net/http/httptest"
321- "testing"
322+ "time"
323 )
324
325-// hook up gocheck
326-func TestWebcheck(t *testing.T) { TestingT(t) }
327-
328-type WebcheckerSuite struct{}
329+type WebcheckerSuite struct {
330+ timeouts []time.Duration
331+}
332
333 var _ = Suite(&WebcheckerSuite{})
334
335@@ -60,6 +60,16 @@
336 }
337 }
338
339+func (s *WebcheckerSuite) SetUpSuite(c *C) {
340+ s.timeouts = util.Timeouts
341+ util.Timeouts = []time.Duration{0}
342+}
343+
344+func (s *WebcheckerSuite) TearDownSuite(c *C) {
345+ util.Timeouts = s.timeouts
346+ s.timeouts = nil
347+}
348+
349 // Webchecker sends true when everything works
350 func (s *WebcheckerSuite) TestWorks(c *C) {
351 ts := httptest.NewServer(mkHandler(staticText))
352
353=== modified file 'bus/endpoint.go'
354--- bus/endpoint.go 2014-01-23 00:21:38 +0000
355+++ bus/endpoint.go 2014-01-27 13:02:37 +0000
356@@ -22,6 +22,7 @@
357 "fmt"
358 "launchpad.net/go-dbus/v1"
359 "launchpad.net/ubuntu-push/logger"
360+ "time"
361 )
362
363 /*****************************************************************
364@@ -33,25 +34,23 @@
365 WatchSignal(member string, f func(...interface{}), d func()) error
366 Call(member string, args ...interface{}) ([]interface{}, error)
367 GetProperty(property string) (interface{}, error)
368+ Dial() error
369 Close()
370+ String() string
371+ Jitter(time.Duration) time.Duration
372 }
373
374 type endpoint struct {
375+ busT Bus
376 bus *dbus.Connection
377 proxy *dbus.ObjectProxy
378- iface string
379+ addr Address
380 log logger.Logger
381 }
382
383 // constructor
384-func newEndpoint(bus *dbus.Connection, addr Address, log logger.Logger) *endpoint {
385- endp := new(endpoint)
386- endp.bus = bus
387- endp.proxy = bus.Object(addr.Name, dbus.ObjectPath(addr.Path))
388- endp.iface = addr.Interface
389- endp.log = log
390-
391- return endp
392+func newEndpoint(bus Bus, addr Address, log logger.Logger) *endpoint {
393+ return &endpoint{busT: bus, addr: addr, log: log}
394 }
395
396 // ensure endpoint implements Endpoint
397@@ -61,13 +60,24 @@
398 public methods
399 */
400
401+// Dial() (re)establishes the connection with dbus
402+func (endp *endpoint) Dial() error {
403+ bus, err := dbus.Connect(endp.busT.(concreteBus).dbusType())
404+ if err != nil {
405+ return err
406+ }
407+ endp.bus = bus
408+ endp.proxy = bus.Object(endp.addr.Name, dbus.ObjectPath(endp.addr.Path))
409+ return nil
410+}
411+
412 // WatchSignal() takes a member name and sets up a watch for it (on the name,
413 // path and interface provided when creating the endpoint), and then calls f()
414 // with the unpacked value. If it's unable to set up the watch it'll return an
415 // error. If the watch fails once established, d() is called. Typically f()
416 // sends the values over a channel, and d() would close the channel.
417 func (endp *endpoint) WatchSignal(member string, f func(...interface{}), d func()) error {
418- watch, err := endp.proxy.WatchSignal(endp.iface, member)
419+ watch, err := endp.proxy.WatchSignal(endp.addr.Interface, member)
420 if err != nil {
421 endp.log.Debugf("Failed to set up the watch: %s", err)
422 return err
423@@ -82,7 +92,7 @@
424 // provided when creating the endpoint). The return value is unpacked before
425 // being returned.
426 func (endp *endpoint) Call(member string, args ...interface{}) ([]interface{}, error) {
427- msg, err := endp.proxy.Call(endp.iface, member, args...)
428+ msg, err := endp.proxy.Call(endp.addr.Interface, member, args...)
429 if err != nil {
430 return nil, err
431 }
432@@ -95,7 +105,7 @@
433 // creating the endpoint. The return value is unpacked into a dbus.Variant,
434 // and its value returned.
435 func (endp *endpoint) GetProperty(property string) (interface{}, error) {
436- msg, err := endp.proxy.Call("org.freedesktop.DBus.Properties", "Get", endp.iface, property)
437+ msg, err := endp.proxy.Call("org.freedesktop.DBus.Properties", "Get", endp.addr.Interface, property)
438 if err != nil {
439 return nil, err
440 }
441@@ -118,6 +128,18 @@
442 // Close the connection to dbus.
443 func (endp *endpoint) Close() {
444 endp.bus.Close()
445+ endp.bus = nil
446+ endp.proxy = nil
447+}
448+
449+// String() performs advanced endpoint stringification
450+func (endp *endpoint) String() string {
451+ return fmt.Sprintf("<Connection to %s %#v>", endp.bus, endp.addr)
452+}
453+
454+// Jitter() returns 0: no need to jitter D-Bus connections.
455+func (endp *endpoint) Jitter(_ time.Duration) time.Duration {
456+ return 0
457 }
458
459 /*
460
461=== modified file 'bus/endpoint_test.go'
462--- bus/endpoint_test.go 2014-01-20 13:45:30 +0000
463+++ bus/endpoint_test.go 2014-01-27 13:02:37 +0000
464@@ -18,6 +18,7 @@
465
466 import (
467 . "launchpad.net/gocheck"
468+ "os"
469 "testing"
470 )
471
472@@ -30,3 +31,29 @@
473
474 // TODO: this is going to remain empty until go-dbus grows some
475 // testing amenities (already talked about it with jamesh)
476+
477+// Tests that we can connect to the *actual* system bus.
478+// XXX maybe connect to a mock/fake/etc bus?
479+func (s *BusSuite) TestDial(c *C) {
480+ endp := newEndpoint(SystemBus, Address{"", "", ""}, nullog)
481+ c.Assert(endp.bus, IsNil)
482+ err := endp.Dial()
483+ c.Assert(err, IsNil)
484+ defer endp.Close() // yes, a second close. On purpose.
485+ c.Assert(endp.bus, NotNil)
486+ endp.Close() // the first close. If you're counting right.
487+ c.Assert(endp.bus, IsNil) // Close cleans up
488+}
489+
490+// Test that if we try to connect to the session bus when no session
491+// bus is available, we get a reasonable result (i.e., an error).
492+func (s *BusSuite) TestDialCanFail(c *C) {
493+ db := "DBUS_SESSION_BUS_ADDRESS"
494+ odb := os.Getenv(db)
495+ defer os.Setenv(db, odb)
496+ os.Setenv(db, "")
497+
498+ endp := newEndpoint(SessionBus, Address{"", "", ""}, nullog)
499+ err := endp.Dial()
500+ c.Check(err, NotNil)
501+}
502
503=== modified file 'bus/networkmanager/networkmanager_test.go'
504--- bus/networkmanager/networkmanager_test.go 2014-01-23 00:21:38 +0000
505+++ bus/networkmanager/networkmanager_test.go 2014-01-27 13:02:37 +0000
506@@ -47,41 +47,41 @@
507
508 // TestNew doesn't test much at all. If this fails, all is wrong in the world.
509 func (s *NMSuite) TestNew(c *C) {
510- nm := New(testingbus.NewTestingEndpoint(condition.Work(true)), nullog)
511+ nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(true)), nullog)
512 c.Check(nm, NotNil)
513 }
514
515 // GetState returns the right state when everything works
516 func (s *NMSuite) TestGetState(c *C) {
517- nm := New(testingbus.NewTestingEndpoint(condition.Work(true), uint32(ConnectedGlobal)), nullog)
518+ nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(true), uint32(ConnectedGlobal)), nullog)
519 state := nm.GetState()
520 c.Check(state, Equals, ConnectedGlobal)
521 }
522
523 // GetState returns the right state when dbus fails
524 func (s *NMSuite) TestGetStateFail(c *C) {
525- nm := New(testingbus.NewTestingEndpoint(condition.Work(false), uint32(ConnectedGlobal)), nullog)
526+ nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false), uint32(ConnectedGlobal)), nullog)
527 state := nm.GetState()
528 c.Check(state, Equals, Unknown)
529 }
530
531 // GetState returns the right state when dbus works but delivers rubbish values
532 func (s *NMSuite) TestGetStateRubbishValues(c *C) {
533- nm := New(testingbus.NewTestingEndpoint(condition.Work(false), 42), nullog)
534+ nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false), 42), nullog)
535 state := nm.GetState()
536 c.Check(state, Equals, Unknown)
537 }
538
539 // GetState returns the right state when dbus works but delivers a rubbish structure
540 func (s *NMSuite) TestGetStateRubbishStructure(c *C) {
541- nm := New(testingbus.NewMultiValuedTestingEndpoint(condition.Work(true), []interface{}{}), nullog)
542+ nm := New(testingbus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{}), nullog)
543 state := nm.GetState()
544 c.Check(state, Equals, Unknown)
545 }
546
547 // WatchState sends a stream of States over the channel
548 func (s *NMSuite) TestWatchState(c *C) {
549- tc := testingbus.NewTestingEndpoint(condition.Work(true), uint32(Unknown), uint32(Asleep), uint32(ConnectedGlobal))
550+ tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), uint32(Unknown), uint32(Asleep), uint32(ConnectedGlobal))
551 nm := New(tc, nullog)
552 ch, err := nm.WatchState()
553 c.Check(err, IsNil)
554@@ -91,14 +91,14 @@
555
556 // WatchState returns on error if the dbus call fails
557 func (s *NMSuite) TestWatchStateFails(c *C) {
558- nm := New(testingbus.NewTestingEndpoint(condition.Work(false)), nullog)
559+ nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), nullog)
560 _, err := nm.WatchState()
561 c.Check(err, NotNil)
562 }
563
564 // WatchState calls close on its channel when the watch bails
565 func (s *NMSuite) TestWatchClosesOnWatchBail(c *C) {
566- tc := testingbus.NewTestingEndpoint(condition.Work(true))
567+ tc := testingbus.NewTestingEndpoint(nil, condition.Work(true))
568 nm := New(tc, nullog)
569 ch, err := nm.WatchState()
570 c.Check(err, IsNil)
571
572=== modified file 'bus/notifications/raw_test.go'
573--- bus/notifications/raw_test.go 2014-01-27 13:02:37 +0000
574+++ bus/notifications/raw_test.go 2014-01-27 13:02:37 +0000
575@@ -39,7 +39,7 @@
576 var nullog = logger.NewSimpleLogger(ioutil.Discard, "error")
577
578 func (s *RawSuite) TestNotifies(c *C) {
579- endp := testibus.NewTestingEndpoint(condition.Work(true), uint32(1))
580+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
581 raw := Raw(endp, nullog)
582 nid, err := raw.Notify("", 0, "", "", "", nil, nil, 0)
583 c.Check(err, IsNil)
584@@ -47,21 +47,21 @@
585 }
586
587 func (s *RawSuite) TestNotifiesFails(c *C) {
588- endp := testibus.NewTestingEndpoint(condition.Work(false))
589+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
590 raw := Raw(endp, nullog)
591 _, err := raw.Notify("", 0, "", "", "", nil, nil, 0)
592 c.Check(err, NotNil)
593 }
594
595 func (s *RawSuite) TestNotifiesFailsWeirdly(c *C) {
596- endp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), []interface{}{1, 2})
597+ endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{1, 2})
598 raw := Raw(endp, nullog)
599 _, err := raw.Notify("", 0, "", "", "", nil, nil, 0)
600 c.Check(err, NotNil)
601 }
602
603 func (s *RawSuite) TestWatchActions(c *C) {
604- endp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true),
605+ endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true),
606 []interface{}{uint32(1), "hello"})
607 raw := Raw(endp, nullog)
608 ch, err := raw.WatchActions()
609@@ -80,7 +80,7 @@
610 }
611
612 func (s *RawSuite) TestWatchActionsFails(c *C) {
613- endp := testibus.NewTestingEndpoint(condition.Work(false))
614+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
615 raw := Raw(endp, nullog)
616 _, err := raw.WatchActions()
617 c.Check(err, NotNil)
618
619=== modified file 'bus/testing/testing_bus.go'
620--- bus/testing/testing_bus.go 2014-01-21 13:21:19 +0000
621+++ bus/testing/testing_bus.go 2014-01-27 13:02:37 +0000
622@@ -21,7 +21,6 @@
623 // Here, the bus.Bus implementation.
624
625 import (
626- "errors"
627 "launchpad.net/ubuntu-push/bus"
628 "launchpad.net/ubuntu-push/logger"
629 "launchpad.net/ubuntu-push/testing/condition"
630@@ -32,22 +31,21 @@
631 */
632
633 type testingBus struct {
634- TestCond condition.Interface
635- TestEndp bus.Endpoint
636+ endp bus.Endpoint
637 }
638
639 // Build a bus.Bus that takes a condition to determine whether it should work,
640 // as well as a condition and series of return values for the testing
641 // bus.Endpoint it builds.
642-func NewTestingBus(clientTC condition.Interface, busTC condition.Interface, retvals ...interface{}) bus.Bus {
643- return &testingBus{clientTC, NewTestingEndpoint(busTC, retvals...)}
644+func NewTestingBus(dialTC condition.Interface, callTC condition.Interface, retvals ...interface{}) bus.Bus {
645+ return &testingBus{NewTestingEndpoint(dialTC, callTC, retvals...)}
646 }
647
648 // Build a bus.Bus that takes a condition to determine whether it should work,
649 // as well as a condition and a series of lists of return values for the
650 // testing bus.Endpoint it builds.
651-func NewMultiValuedTestingBus(clientTC condition.Interface, busTC condition.Interface, retvalses ...[]interface{}) bus.Bus {
652- return &testingBus{clientTC, NewMultiValuedTestingEndpoint(busTC, retvalses...)}
653+func NewMultiValuedTestingBus(dialTC condition.Interface, callTC condition.Interface, retvalses ...[]interface{}) bus.Bus {
654+ return &testingBus{NewMultiValuedTestingEndpoint(dialTC, callTC, retvalses...)}
655 }
656
657 // ensure testingBus implements bus.Interface
658@@ -57,12 +55,8 @@
659 public methods
660 */
661
662-func (tb *testingBus) Connect(info bus.Address, log logger.Logger) (bus.Endpoint, error) {
663- if tb.TestCond.OK() {
664- return tb.TestEndp, nil
665- } else {
666- return nil, errors.New(tb.TestCond.String())
667- }
668+func (tb *testingBus) Endpoint(info bus.Address, log logger.Logger) bus.Endpoint {
669+ return tb.endp
670 }
671
672 func (tb *testingBus) String() string {
673
674=== modified file 'bus/testing/testing_bus_test.go'
675--- bus/testing/testing_bus_test.go 2014-01-21 13:38:03 +0000
676+++ bus/testing/testing_bus_test.go 2014-01-27 13:02:37 +0000
677@@ -30,46 +30,40 @@
678
679 var _ = Suite(&TestingBusSuite{})
680
681-// Test Connect() on a working bus returns an endpoint that looks right
682-func (s *TestingBusSuite) TestConnectWorks(c *C) {
683+// Test Endpoint() on a working bus returns an endpoint that looks right
684+func (s *TestingBusSuite) TestEndpointWorks(c *C) {
685 addr := bus.Address{"", "", ""}
686 tb := NewTestingBus(condition.Work(true), condition.Work(false), 42, 42, 42)
687- endp, err := tb.Connect(addr, nil)
688+ endp := tb.Endpoint(addr, nil)
689+ err := endp.Dial()
690 c.Check(err, IsNil)
691- c.Check(endp, FitsTypeOf, &testingEndpoint{})
692- c.Check(endp.(*testingEndpoint).cond.OK(), Equals, false)
693+ c.Assert(endp, FitsTypeOf, &testingEndpoint{})
694+ c.Check(endp.(*testingEndpoint).callCond.OK(), Equals, false)
695 c.Check(endp.(*testingEndpoint).retvals, HasLen, 3)
696 }
697
698-// Test Connect() on a working "multi-valued" bus returns an endpoint that looks right
699-func (s *TestingBusSuite) TestConnectMultiValued(c *C) {
700+// Test Endpoint() on a working "multi-valued" bus returns an endpoint that looks right
701+func (s *TestingBusSuite) TestEndpointMultiValued(c *C) {
702 addr := bus.Address{"", "", ""}
703 tb := NewMultiValuedTestingBus(condition.Work(true), condition.Work(true),
704 []interface{}{42, 17},
705 []interface{}{42, 17, 13},
706 []interface{}{42},
707 )
708- endpp, err := tb.Connect(addr, nil)
709+ endpp := tb.Endpoint(addr, nil)
710+ err := endpp.Dial()
711 c.Check(err, IsNil)
712 endp, ok := endpp.(*testingEndpoint)
713- c.Check(ok, Equals, true)
714- c.Check(endp.cond.OK(), Equals, true)
715+ c.Assert(ok, Equals, true)
716+ c.Check(endp.callCond.OK(), Equals, true)
717 c.Assert(endp.retvals, HasLen, 3)
718 c.Check(endp.retvals[0], HasLen, 2)
719 c.Check(endp.retvals[1], HasLen, 3)
720 c.Check(endp.retvals[2], HasLen, 1)
721 }
722
723-// Test Connect() with a non-working bus fails
724-func (s *TestingBusSuite) TestConnectNoWork(c *C) {
725- addr := bus.Address{"", "", ""}
726- tb := NewTestingBus(condition.Work(false), condition.Work(true))
727- _, err := tb.Connect(addr, nil)
728- c.Check(err, NotNil)
729-}
730-
731 // Test TestingBus stringifies sanely
732 func (s *TestingBusSuite) TestStringifyBus(c *C) {
733- tb := NewTestingBus(condition.Work(false), condition.Work(true))
734+ tb := NewTestingBus(nil, nil)
735 c.Check(tb.String(), Matches, ".*TestingBus.*")
736 }
737
738=== modified file 'bus/testing/testing_endpoint.go'
739--- bus/testing/testing_endpoint.go 2014-01-27 10:26:34 +0000
740+++ bus/testing/testing_endpoint.go 2014-01-27 13:02:37 +0000
741@@ -20,14 +20,16 @@
742
743 import (
744 "errors"
745+ "fmt"
746 "launchpad.net/ubuntu-push/bus"
747 "launchpad.net/ubuntu-push/testing/condition"
748 "time"
749 )
750
751 type testingEndpoint struct {
752- cond condition.Interface
753- retvals [][]interface{}
754+ dialCond condition.Interface
755+ callCond condition.Interface
756+ retvals [][]interface{}
757 }
758
759 // Build a bus.Endpoint that calls OK() on its condition before returning
760@@ -35,22 +37,22 @@
761 //
762 // NOTE: Call() always returns the first return value; Watch() will provide
763 // each of them in turn, irrespective of whether Call has been called.
764-func NewMultiValuedTestingEndpoint(cond condition.Interface, retvalses ...[]interface{}) bus.Endpoint {
765- return &testingEndpoint{cond, retvalses}
766+func NewMultiValuedTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvalses ...[]interface{}) bus.Endpoint {
767+ return &testingEndpoint{dialCond, callCond, retvalses}
768 }
769
770-func NewTestingEndpoint(cond condition.Interface, retvals ...interface{}) bus.Endpoint {
771+func NewTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvals ...interface{}) bus.Endpoint {
772 retvalses := make([][]interface{}, len(retvals))
773 for i, x := range retvals {
774 retvalses[i] = []interface{}{x}
775 }
776- return &testingEndpoint{cond, retvalses}
777+ return &testingEndpoint{dialCond, callCond, retvalses}
778 }
779
780 // See Endpoint's WatchSignal. This WatchSignal will check its condition to
781 // decide whether to return an error, or provide each of its return values
782 func (tc *testingEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error {
783- if tc.cond.OK() {
784+ if tc.callCond.OK() {
785 go func() {
786 for _, v := range tc.retvals {
787 f(v...)
788@@ -67,7 +69,7 @@
789 // See Endpoint's Call. This Call will check its condition to decide whether
790 // to return an error, or the first of its return values
791 func (tc *testingEndpoint) Call(member string, args ...interface{}) ([]interface{}, error) {
792- if tc.cond.OK() {
793+ if tc.callCond.OK() {
794 if len(tc.retvals) == 0 {
795 panic("No return values provided!")
796 }
797@@ -90,8 +92,27 @@
798 return rvs[0], err
799 }
800
801+// See Endpoint's Dial. This one will check its dialCondition to
802+// decide whether to return an error or not.
803+func (endp *testingEndpoint) Dial() error {
804+ if endp.dialCond.OK() {
805+ return nil
806+ } else {
807+ return errors.New("dialCond said No.")
808+ }
809+}
810+
811+// Advanced stringifobabulation
812+func (endp *testingEndpoint) String() string {
813+ return fmt.Sprintf("&testingEndpoint{dialCond:(%s) callCond:(%s) retvals:(%#v)",
814+ endp.dialCond, endp.callCond, endp.retvals)
815+}
816+
817 // see Endpoint's Close. This one does nothing.
818 func (tc *testingEndpoint) Close() {}
819
820+// see Endpoint's Jitter.
821+func (tc *testingEndpoint) Jitter(_ time.Duration) time.Duration { return 0 }
822+
823 // ensure testingEndpoint implements bus.Endpoint
824 var _ bus.Endpoint = &testingEndpoint{}
825
826=== modified file 'bus/testing/testing_endpoint_test.go'
827--- bus/testing/testing_endpoint_test.go 2014-01-23 00:21:38 +0000
828+++ bus/testing/testing_endpoint_test.go 2014-01-27 13:02:37 +0000
829@@ -20,6 +20,7 @@
830 . "launchpad.net/gocheck"
831 "launchpad.net/ubuntu-push/testing/condition"
832 "testing"
833+ "time"
834 )
835
836 // hook up gocheck
837@@ -33,7 +34,7 @@
838 // provided, as advertised.
839 func (s *TestingEndpointSuite) TestCallReturnsFirstRetval(c *C) {
840 var m, n uint32 = 42, 17
841- endp := NewTestingEndpoint(condition.Work(true), m, n)
842+ endp := NewTestingEndpoint(nil, condition.Work(true), m, n)
843 vs, e := endp.Call("what")
844 c.Check(e, IsNil)
845 c.Check(vs, HasLen, 1)
846@@ -43,7 +44,7 @@
847 // Test the same Call() but with multi-valued endpoint
848 func (s *TestingEndpointSuite) TestMultiValuedCall(c *C) {
849 var m, n uint32 = 42, 17
850- endp := NewMultiValuedTestingEndpoint(condition.Work(true), []interface{}{m}, []interface{}{n})
851+ endp := NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{m}, []interface{}{n})
852 vs, e := endp.Call("what")
853 c.Check(e, IsNil)
854 c.Check(vs, HasLen, 1)
855@@ -52,7 +53,7 @@
856
857 // Test that Call() with a negative condition returns an error.
858 func (s *TestingEndpointSuite) TestCallFails(c *C) {
859- endp := NewTestingEndpoint(condition.Work(false))
860+ endp := NewTestingEndpoint(nil, condition.Work(false))
861 _, e := endp.Call("what")
862 c.Check(e, NotNil)
863 }
864@@ -60,7 +61,7 @@
865 // Test that Call() with a positive condition and no return values panics with
866 // a helpful message.
867 func (s *TestingEndpointSuite) TestCallPanicsWithNiceMessage(c *C) {
868- endp := NewTestingEndpoint(condition.Work(true))
869+ endp := NewTestingEndpoint(nil, condition.Work(true))
870 c.Check(func() { endp.Call("") }, PanicMatches, "No return values provided.*")
871 }
872
873@@ -68,7 +69,7 @@
874 // values over the channel.
875 func (s *TestingEndpointSuite) TestWatch(c *C) {
876 var m, n uint32 = 42, 17
877- endp := NewTestingEndpoint(condition.Work(true), m, n)
878+ endp := NewTestingEndpoint(nil, condition.Work(true), m, n)
879 ch := make(chan uint32)
880 e := endp.WatchSignal("what", func(us ...interface{}) { ch <- us[0].(uint32) }, func() { close(ch) })
881 c.Check(e, IsNil)
882@@ -78,7 +79,7 @@
883
884 // Test that WatchSignal() calls the destructor callback when it runs out values
885 func (s *TestingEndpointSuite) TestWatchDestructor(c *C) {
886- endp := NewTestingEndpoint(condition.Work(true))
887+ endp := NewTestingEndpoint(nil, condition.Work(true))
888 ch := make(chan uint32)
889 e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
890 c.Check(e, IsNil)
891@@ -88,14 +89,14 @@
892
893 // Test the endpoint can be closed
894 func (s *TestingEndpointSuite) TestCloser(c *C) {
895- endp := NewTestingEndpoint(condition.Work(true))
896+ endp := NewTestingEndpoint(nil, condition.Work(true))
897 endp.Close()
898 // ... yay?
899 }
900
901 // Test that WatchSignal() with a negative condition returns an error.
902 func (s *TestingEndpointSuite) TestWatchFails(c *C) {
903- endp := NewTestingEndpoint(condition.Work(false))
904+ endp := NewTestingEndpoint(nil, condition.Work(false))
905 e := endp.WatchSignal("what", func(us ...interface{}) {}, func() {})
906 c.Check(e, NotNil)
907 }
908@@ -103,8 +104,41 @@
909 // Tests that GetProperty() works
910 func (s *TestingEndpointSuite) TestGetProperty(c *C) {
911 var m uint32 = 42
912- endp := NewTestingEndpoint(condition.Work(true), m)
913+ endp := NewTestingEndpoint(nil, condition.Work(true), m)
914 v, e := endp.GetProperty("what")
915 c.Check(e, IsNil)
916 c.Check(v, Equals, m)
917 }
918+
919+// Tests that GetProperty() fails, too
920+func (s *TestingEndpointSuite) TestGetPropertyFails(c *C) {
921+ endp := NewTestingEndpoint(nil, condition.Work(false))
922+ _, e := endp.GetProperty("what")
923+ c.Check(e, NotNil)
924+}
925+
926+// Tests that GetProperty() also fails if it's fed garbage
927+func (s *TestingEndpointSuite) TestGetPropertyFailsGargling(c *C) {
928+ endp := NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{})
929+ _, e := endp.GetProperty("what")
930+ c.Check(e, NotNil)
931+}
932+
933+// Test Dial() with a non-working bus fails
934+func (s *TestingBusSuite) TestDialNoWork(c *C) {
935+ endp := NewTestingEndpoint(condition.Work(false), nil)
936+ err := endp.Dial()
937+ c.Check(err, NotNil)
938+}
939+
940+// Test testingEndpoints serialize, more or less
941+func (s *TestingBusSuite) TestEndpointString(c *C) {
942+ endp := NewTestingEndpoint(condition.Fail2Work(2), nil, "hello there")
943+ c.Check(endp.String(), Matches, ".*Still Broken.*hello there.*")
944+}
945+
946+// Test testingEndpoints have no jitters
947+func (s *TestingBusSuite) TestEndpointJitter(c *C) {
948+ endp := NewTestingEndpoint(nil, nil)
949+ c.Check(endp.Jitter(time.Duration(42)), Equals, time.Duration(0))
950+}
951
952=== modified file 'bus/urldispatcher/urldispatcher_test.go'
953--- bus/urldispatcher/urldispatcher_test.go 2014-01-27 13:02:37 +0000
954+++ bus/urldispatcher/urldispatcher_test.go 2014-01-27 13:02:37 +0000
955@@ -35,14 +35,14 @@
956 var nullog = logger.NewSimpleLogger(ioutil.Discard, "error")
957
958 func (s *UDSuite) TestWorks(c *C) {
959- endp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), []interface{}{})
960+ endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{})
961 ud := New(endp, nullog)
962 err := ud.DispatchURL("this")
963 c.Check(err, IsNil)
964 }
965
966 func (s *UDSuite) TestFailsIfCallFails(c *C) {
967- endp := testibus.NewTestingEndpoint(condition.Work(false))
968+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
969 ud := New(endp, nullog)
970 err := ud.DispatchURL("this")
971 c.Check(err, NotNil)
972
973=== added directory 'util'
974=== added file 'util/redialer.go'
975--- util/redialer.go 1970-01-01 00:00:00 +0000
976+++ util/redialer.go 2014-01-27 13:02:37 +0000
977@@ -0,0 +1,86 @@
978+/*
979+ Copyright 2013-2014 Canonical Ltd.
980+
981+ This program is free software: you can redistribute it and/or modify it
982+ under the terms of the GNU General Public License version 3, as published
983+ by the Free Software Foundation.
984+
985+ This program is distributed in the hope that it will be useful, but
986+ WITHOUT ANY WARRANTY; without even the implied warranties of
987+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
988+ PURPOSE. See the GNU General Public License for more details.
989+
990+ You should have received a copy of the GNU General Public License along
991+ with this program. If not, see <http://www.gnu.org/licenses/>.
992+*/
993+
994+package util
995+
996+import (
997+ "math/rand"
998+ "time"
999+)
1000+
1001+// A Dialer is an object that knows how to establish a connection, and
1002+// where you'd usually want some kind of back off if that connection
1003+// fails.
1004+type Dialer interface {
1005+ Dial() error
1006+ String() string
1007+ Jitter(time.Duration) time.Duration
1008+}
1009+
1010+// The timeouts used during backoff. While this is public, you'd
1011+// usually not need to meddle with it.
1012+var Timeouts []time.Duration
1013+
1014+var ( // for use in testing
1015+ quitRedialing chan bool = make(chan bool)
1016+)
1017+
1018+// Jitter returns a random time.Duration somewhere in [-spread, spread].
1019+//
1020+// This is meant as a default implementation for Dialers to use if wanted.
1021+func Jitter(spread time.Duration) time.Duration {
1022+ if spread < 0 {
1023+ panic("spread must be non-negative")
1024+ }
1025+ n := int64(spread)
1026+ return time.Duration(rand.Int63n(2*n+1) - n)
1027+}
1028+
1029+// AutoRedialer takes a Dialer and retries its Dial() method until it
1030+// stops returning an error. It does exponential (optionally
1031+// jitter'ed) backoff.
1032+func AutoRedial(dialer Dialer) uint32 {
1033+ var timeout time.Duration
1034+ var dialAttempts uint32 = 0 // unsigned so it can wrap safely ...
1035+ var numTimeouts uint32 = uint32(len(Timeouts))
1036+ for {
1037+ if dialer.Dial() == nil {
1038+ return dialAttempts + 1
1039+ }
1040+ if dialAttempts < numTimeouts {
1041+ timeout = Timeouts[dialAttempts]
1042+ } else {
1043+ timeout = Timeouts[numTimeouts-1]
1044+ }
1045+ timeout += dialer.Jitter(timeout)
1046+ dialAttempts++
1047+ select {
1048+ case <-quitRedialing:
1049+ return dialAttempts
1050+ case <-time.NewTimer(timeout).C:
1051+ }
1052+ }
1053+}
1054+
1055+func init() {
1056+ ps := []int{1, 2, 5, 11, 19, 37, 67, 113, 191} // 3 pₙ₊₁ ≥ 5 pₙ
1057+ Timeouts = make([]time.Duration, len(ps))
1058+ for i, n := range ps {
1059+ Timeouts[i] = time.Duration(n) * time.Second
1060+ }
1061+
1062+ rand.Seed(time.Now().Unix()) // good enough for us (not crypto, yadda)
1063+}
1064
1065=== added file 'util/redialer_test.go'
1066--- util/redialer_test.go 1970-01-01 00:00:00 +0000
1067+++ util/redialer_test.go 2014-01-27 13:02:37 +0000
1068@@ -0,0 +1,84 @@
1069+/*
1070+ Copyright 2013-2014 Canonical Ltd.
1071+
1072+ This program is free software: you can redistribute it and/or modify it
1073+ under the terms of the GNU General Public License version 3, as published
1074+ by the Free Software Foundation.
1075+
1076+ This program is distributed in the hope that it will be useful, but
1077+ WITHOUT ANY WARRANTY; without even the implied warranties of
1078+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1079+ PURPOSE. See the GNU General Public License for more details.
1080+
1081+ You should have received a copy of the GNU General Public License along
1082+ with this program. If not, see <http://www.gnu.org/licenses/>.
1083+*/
1084+
1085+package util
1086+
1087+import (
1088+ "io/ioutil"
1089+ . "launchpad.net/gocheck"
1090+ testibus "launchpad.net/ubuntu-push/bus/testing"
1091+ "launchpad.net/ubuntu-push/logger"
1092+ "launchpad.net/ubuntu-push/testing/condition"
1093+ "testing"
1094+ "time"
1095+)
1096+
1097+// hook up gocheck
1098+func TestRedialer(t *testing.T) { TestingT(t) }
1099+
1100+type RedialerSuite struct {
1101+ timeouts []time.Duration
1102+}
1103+
1104+var nullog = logger.NewSimpleLogger(ioutil.Discard, "error")
1105+var _ = Suite(&RedialerSuite{})
1106+
1107+func (s *RedialerSuite) SetUpSuite(c *C) {
1108+ s.timeouts = Timeouts
1109+ Timeouts = []time.Duration{0, 0}
1110+}
1111+
1112+func (s *RedialerSuite) TearDownSuite(c *C) {
1113+ Timeouts = s.timeouts
1114+ s.timeouts = nil
1115+}
1116+
1117+func (s *RedialerSuite) TestWorks(c *C) {
1118+ endp := testibus.NewTestingEndpoint(condition.Fail2Work(3), nil)
1119+ // instead of bus.Dial(), we do AutoRedial(bus)
1120+ c.Check(AutoRedial(endp), Equals, uint32(4))
1121+}
1122+
1123+func (s *RedialerSuite) TestCanBeStopped(c *C) {
1124+ endp := testibus.NewTestingEndpoint(condition.Work(false), nil)
1125+ go func() { c.Check(AutoRedial(endp), Equals, uint32(1)) }()
1126+ quitRedialing <- true
1127+}
1128+
1129+func (s *RedialerSuite) TestJitter(c *C) {
1130+ num_tries := 20 // should do the math
1131+ spread := time.Second //
1132+ has_neg := false
1133+ has_pos := false
1134+ has_zero := true
1135+ for i := 0; i < num_tries; i++ {
1136+ n := Jitter(spread)
1137+ if n > 0 {
1138+ has_pos = true
1139+ } else if n < 0 {
1140+ has_neg = true
1141+ } else {
1142+ has_zero = true
1143+ }
1144+ }
1145+ c.Check(has_neg, Equals, true)
1146+ c.Check(has_pos, Equals, true)
1147+ c.Check(has_zero, Equals, true)
1148+
1149+ // a negative spread is caught in the reasonable place
1150+ c.Check(func() { Jitter(time.Duration(-1)) }, PanicMatches,
1151+ "spread must be non-negative")
1152+}

Subscribers

People subscribed via source and target branches