Merge lp:~chipaca/ubuntu-push/connectivity into lp:ubuntu-push
- connectivity
- Merge into trunk
Proposed by
John Lenton
Status: | Merged |
---|---|
Approved by: | John Lenton |
Approved revision: | 19 |
Merged at revision: | 12 |
Proposed branch: | lp:~chipaca/ubuntu-push/connectivity |
Merge into: | lp:ubuntu-push |
Prerequisite: | lp:~chipaca/ubuntu-push/networkmanager |
Diff against target: |
747 lines (+704/-0) 7 files modified
connectivity/connectivity.go (+190/-0) connectivity/connectivity_test.go (+266/-0) connectivity/example/main.go (+54/-0) connectivity/example/thing.json (+7/-0) connectivity/webchecker/webchecker.go (+80/-0) connectivity/webchecker/webchecker_test.go (+106/-0) networkmanager/networkmanager.go (+1/-0) |
To merge this branch: | bzr merge lp:~chipaca/ubuntu-push/connectivity |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Samuele Pedroni | Approve | ||
Review via email:
|
Commit message
A super simple connectivity api (in the "am i connected?" sense)
Description of the change
Complete with a little example program.
To post a comment you must log in.
- 13. By John Lenton
-
Merged networkmanager into connectivity.
- 14. By John Lenton
-
Merged networkmanager into connectivity.
- 15. By John Lenton
-
Merged networkmanager into connectivity.
- 16. By John Lenton
-
sync up with the changes to the other branches
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Samuele Pedroni (pedronis) wrote : | # |
- 17. By John Lenton
-
addressed issues raised by pedronis during peer review
- 18. By John Lenton
-
renaming a couple more channel names to match /ch$/i
- 19. By John Lenton
-
aaand forgot to catch a label
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Samuele Pedroni (pedronis) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'connectivity' |
2 | === added file 'connectivity/connectivity.go' |
3 | --- connectivity/connectivity.go 1970-01-01 00:00:00 +0000 |
4 | +++ connectivity/connectivity.go 2014-01-20 17:45:15 +0000 |
5 | @@ -0,0 +1,190 @@ |
6 | +/* |
7 | + Copyright 2013-2014 Canonical Ltd. |
8 | + |
9 | + This program is free software: you can redistribute it and/or modify it |
10 | + under the terms of the GNU General Public License version 3, as published |
11 | + by the Free Software Foundation. |
12 | + |
13 | + This program is distributed in the hope that it will be useful, but |
14 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
15 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
16 | + PURPOSE. See the GNU General Public License for more details. |
17 | + |
18 | + You should have received a copy of the GNU General Public License along |
19 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | +*/ |
21 | + |
22 | +// Package connectivity a single, simple stream of booleans to answer |
23 | +// the quesiton “are we connected?”. |
24 | +// |
25 | +// It can potentially fire two falses in a row, if a disconnected |
26 | +// state is followed by a dbus watch error. Other than that, it's edge |
27 | +// triggered. |
28 | +package connectivity |
29 | + |
30 | +import ( |
31 | + "errors" |
32 | + "launchpad.net/ubuntu-push/bus" |
33 | + "launchpad.net/ubuntu-push/config" |
34 | + "launchpad.net/ubuntu-push/connectivity/webchecker" |
35 | + "launchpad.net/ubuntu-push/logger" |
36 | + "launchpad.net/ubuntu-push/networkmanager" |
37 | + "time" |
38 | +) |
39 | + |
40 | +// the configuration for ConnectedState, with the idea that you'd populate it |
41 | +// from a config file. |
42 | +type Config struct { |
43 | + // a list of timeouts, for backoff. Should be roughly doubling. |
44 | + ConnectTimeouts []config.ConfigTimeDuration |
45 | + // how long to wait after a state change to make sure it's "stable" |
46 | + // before acting on it |
47 | + StabilizingTimeout config.ConfigTimeDuration |
48 | + // How long to wait between online connectivity checks. |
49 | + RecheckTimeout config.ConfigTimeDuration |
50 | + // The URL against which to do the connectivity check. |
51 | + ConnectivityCheckURL string |
52 | + // The expected MD5 of the content at the ConnectivityCheckURL |
53 | + ConnectivityCheckMD5 string |
54 | +} |
55 | + |
56 | +type connectedState struct { |
57 | + networkStateCh <-chan networkmanager.State |
58 | + config Config |
59 | + log logger.Logger |
60 | + bus bus.Bus |
61 | + connAttempts uint32 |
62 | + webget func(ch chan<- bool) |
63 | + webgetCh chan bool |
64 | + currentState networkmanager.State |
65 | + lastSent bool |
66 | + timer *time.Timer |
67 | +} |
68 | + |
69 | +// implements the logic for connect timeouts backoff |
70 | +// |
71 | +// (walk the list of timeouts, and repeat the last one until done; cope with |
72 | +// the list being empty; keep track of connection attempts). |
73 | +func (cs *connectedState) connectTimeout() time.Duration { |
74 | + var timeout config.ConfigTimeDuration |
75 | + timeouts := cs.config.ConnectTimeouts |
76 | + if cs.connAttempts < uint32(len(timeouts)) { |
77 | + timeout = timeouts[cs.connAttempts] |
78 | + } else if len(timeouts) > 0 { |
79 | + timeout = cs.config.ConnectTimeouts[len(timeouts)-1] |
80 | + } |
81 | + cs.connAttempts++ |
82 | + return timeout.Duration |
83 | +} |
84 | + |
85 | +// start connects to the bus, gets the initial NetworkManager state, and sets |
86 | +// up the watch. |
87 | +func (cs *connectedState) start() networkmanager.State { |
88 | + var initial networkmanager.State |
89 | + for { |
90 | + time.Sleep(cs.connectTimeout()) |
91 | + cs.log.Debugf("Starting DBus connection attempt %d\n", cs.connAttempts) |
92 | + conn, err := cs.bus.Connect(networkmanager.BusAddress, cs.log) |
93 | + if err != nil { |
94 | + cs.log.Debugf("DBus connection attempt %d failed.\n", cs.connAttempts) |
95 | + continue |
96 | + } |
97 | + nm := networkmanager.New(conn, cs.log) |
98 | + |
99 | + // Get the current state. |
100 | + initial = nm.GetState() |
101 | + if initial == networkmanager.Unknown { |
102 | + cs.log.Debugf("Failed to get state at attempt.") |
103 | + conn.Close() |
104 | + continue |
105 | + } |
106 | + |
107 | + // set up the watch |
108 | + ch, err := nm.WatchState() |
109 | + if err != nil { |
110 | + cs.log.Debugf("Failed to set up the watch: %s", err) |
111 | + conn.Close() |
112 | + continue |
113 | + } |
114 | + |
115 | + cs.networkStateCh = ch |
116 | + cs.log.Debugf("worked at attempt %d. Resetting counter.\n", cs.connAttempts) |
117 | + return initial |
118 | + } |
119 | +} |
120 | + |
121 | +// connectedStateStep takes one step forwards in the “am I connected?” |
122 | +// answering state machine. |
123 | +func (cs *connectedState) connectedStateStep() (bool, error) { |
124 | + stabilizingTimeout := cs.config.StabilizingTimeout.Duration |
125 | + recheckTimeout := cs.config.RecheckTimeout.Duration |
126 | + log := cs.log |
127 | + |
128 | +Loop: |
129 | + for { |
130 | + select { |
131 | + case v, ok := <-cs.networkStateCh: |
132 | + if !ok { |
133 | + // tear it all down and start over |
134 | + return false, errors.New("Got not-OK from StateChanged watch") |
135 | + } |
136 | + cs.webgetCh = nil |
137 | + cs.currentState = v |
138 | + cs.timer.Reset(stabilizingTimeout) |
139 | + log.Debugf("State changed to %s. Assuming disconnect.", v) |
140 | + if cs.lastSent == true { |
141 | + log.Infof("Sending 'disconnected'.") |
142 | + cs.lastSent = false |
143 | + break Loop |
144 | + } |
145 | + |
146 | + case <-cs.timer.C: |
147 | + if cs.currentState == networkmanager.ConnectedGlobal { |
148 | + log.Debugf("May be connected; checking...") |
149 | + cs.webgetCh = make(chan bool) |
150 | + go cs.webget(cs.webgetCh) |
151 | + } |
152 | + |
153 | + case connected := <-cs.webgetCh: |
154 | + cs.timer.Reset(recheckTimeout) |
155 | + log.Debugf("Connection check says: %t", connected) |
156 | + cs.webgetCh = nil |
157 | + if connected && cs.lastSent == false { |
158 | + log.Infof("Sending 'connected'.") |
159 | + cs.lastSent = true |
160 | + break Loop |
161 | + } |
162 | + } |
163 | + } |
164 | + return cs.lastSent, nil |
165 | +} |
166 | + |
167 | +// ConnectedState sends the initial NetworkManager state and changes to it |
168 | +// over the "out" channel. Sends "false" as soon as it detects trouble, "true" |
169 | +// after checking actual connectivity. |
170 | +func ConnectedState(busType bus.Bus, config Config, log logger.Logger, out chan<- bool) { |
171 | + wg := webchecker.New(config.ConnectivityCheckURL, config.ConnectivityCheckMD5, log) |
172 | + cs := &connectedState{ |
173 | + config: config, |
174 | + log: log, |
175 | + bus: busType, |
176 | + webget: wg.Webcheck, |
177 | + } |
178 | + |
179 | +Start: |
180 | + log.Infof("Sending initial 'disconnected'.") |
181 | + out <- false |
182 | + cs.lastSent = false |
183 | + cs.currentState = cs.start() |
184 | + cs.timer = time.NewTimer(cs.config.StabilizingTimeout.Duration) |
185 | + |
186 | + for { |
187 | + v, err := cs.connectedStateStep() |
188 | + if err != nil { |
189 | + // tear it all down and start over |
190 | + log.Errorf("%s", err) |
191 | + goto Start |
192 | + } |
193 | + out <- v |
194 | + } |
195 | +} |
196 | |
197 | === added file 'connectivity/connectivity_test.go' |
198 | --- connectivity/connectivity_test.go 1970-01-01 00:00:00 +0000 |
199 | +++ connectivity/connectivity_test.go 2014-01-20 17:45:15 +0000 |
200 | @@ -0,0 +1,266 @@ |
201 | +/* |
202 | + Copyright 2013-2014 Canonical Ltd. |
203 | + |
204 | + This program is free software: you can redistribute it and/or modify it |
205 | + under the terms of the GNU General Public License version 3, as published |
206 | + by the Free Software Foundation. |
207 | + |
208 | + This program is distributed in the hope that it will be useful, but |
209 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
210 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
211 | + PURPOSE. See the GNU General Public License for more details. |
212 | + |
213 | + You should have received a copy of the GNU General Public License along |
214 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
215 | +*/ |
216 | + |
217 | +package connectivity |
218 | + |
219 | +import ( |
220 | + "io/ioutil" |
221 | + . "launchpad.net/gocheck" |
222 | + testingbus "launchpad.net/ubuntu-push/bus/testing" |
223 | + "launchpad.net/ubuntu-push/config" |
224 | + "launchpad.net/ubuntu-push/logger" |
225 | + "launchpad.net/ubuntu-push/networkmanager" |
226 | + "launchpad.net/ubuntu-push/testing/condition" |
227 | + "net/http" |
228 | + "net/http/httptest" |
229 | + "testing" |
230 | + "time" |
231 | +) |
232 | + |
233 | +// hook up gocheck |
234 | +func Test(t *testing.T) { TestingT(t) } |
235 | + |
236 | +type ConnSuite struct{} |
237 | + |
238 | +var _ = Suite(&ConnSuite{}) |
239 | + |
240 | +var nullog = logger.NewSimpleLogger(ioutil.Discard, "error") |
241 | + |
242 | +/* |
243 | + tests for connectedState's ConnectTimeout() method |
244 | +*/ |
245 | + |
246 | +// When given no timeouts, ConnectTimeout() returns 0 forever |
247 | +func (s *ConnSuite) TestConnectTimeoutWorksWithNoTimeouts(c *C) { |
248 | + cs := connectedState{} |
249 | + c.Check(cs.connectTimeout(), Equals, time.Duration(0)) |
250 | + c.Check(cs.connectTimeout(), Equals, time.Duration(0)) |
251 | +} |
252 | + |
253 | +// when given a few timeouts, ConnectTimeout() returns them each in |
254 | +// turn, and then repeats the last one |
255 | +func (s *ConnSuite) TestConnectTimeoutWorks(c *C) { |
256 | + ts := []config.ConfigTimeDuration{ |
257 | + config.ConfigTimeDuration{0}, |
258 | + config.ConfigTimeDuration{2 * time.Second}, |
259 | + config.ConfigTimeDuration{time.Second}, |
260 | + } |
261 | + cs := connectedState{config: Config{ConnectTimeouts: ts}} |
262 | + c.Check(cs.connectTimeout(), Equals, time.Duration(0)) |
263 | + c.Check(cs.connectTimeout(), Equals, 2*time.Second) |
264 | + c.Check(cs.connectTimeout(), Equals, time.Second) |
265 | + c.Check(cs.connectTimeout(), Equals, time.Second) |
266 | + c.Check(cs.connectTimeout(), Equals, time.Second) |
267 | + c.Check(cs.connectTimeout(), Equals, time.Second) |
268 | + // ... ad nauseam |
269 | +} |
270 | + |
271 | +/* |
272 | + tests for connectedState's Start() method |
273 | +*/ |
274 | + |
275 | +// when given a working config and bus, Start() will work |
276 | +func (s *ConnSuite) TestStartWorks(c *C) { |
277 | + cfg := Config{} |
278 | + tb := testingbus.NewTestingBus(condition.Work(true), condition.Work(true), uint32(networkmanager.Connecting)) |
279 | + cs := connectedState{config: cfg, log: nullog, bus: tb} |
280 | + |
281 | + c.Check(cs.start(), Equals, networkmanager.Connecting) |
282 | +} |
283 | + |
284 | +// if the bus fails a couple of times, we're still OK |
285 | +func (s *ConnSuite) TestStartRetriesConnect(c *C) { |
286 | + timeouts := []config.ConfigTimeDuration{config.ConfigTimeDuration{0}} |
287 | + cfg := Config{ConnectTimeouts: timeouts} |
288 | + tb := testingbus.NewTestingBus(condition.Fail2Work(2), condition.Work(true), uint32(networkmanager.Connecting)) |
289 | + cs := connectedState{config: cfg, log: nullog, bus: tb} |
290 | + |
291 | + c.Check(cs.start(), Equals, networkmanager.Connecting) |
292 | + c.Check(cs.connAttempts, Equals, uint32(3)) // 1 more than the Fail2Work |
293 | +} |
294 | + |
295 | +// when the calls to NetworkManager fail for a bit, we're still OK |
296 | +func (s *ConnSuite) TestStartRetriesCall(c *C) { |
297 | + cfg := Config{} |
298 | + tb := testingbus.NewTestingBus(condition.Work(true), condition.Fail2Work(5), uint32(networkmanager.Connecting)) |
299 | + cs := connectedState{config: cfg, log: nullog, bus: tb} |
300 | + |
301 | + c.Check(cs.start(), Equals, networkmanager.Connecting) |
302 | + |
303 | + c.Check(cs.connAttempts, Equals, uint32(6)) |
304 | +} |
305 | + |
306 | +// when ... and bear with me ... the bus works, and the first call to |
307 | +// get network manager's state works, but then you can't establish the |
308 | +// watch, we recover and try again. |
309 | +func (s *ConnSuite) TestStartRetriesWatch(c *C) { |
310 | + nmcond := condition.Chain( |
311 | + 1, condition.Work(true), // 1 call to nm works |
312 | + 1, condition.Work(false), // 1 call to nm fails |
313 | + 0, condition.Work(true)) // and everything works from there on |
314 | + cfg := Config{} |
315 | + tb := testingbus.NewTestingBus(condition.Work(true), nmcond, |
316 | + uint32(networkmanager.Connecting), |
317 | + uint32(networkmanager.ConnectedGlobal)) |
318 | + cs := connectedState{config: cfg, log: nullog, bus: tb} |
319 | + |
320 | + c.Check(cs.start(), Equals, networkmanager.Connecting) |
321 | + c.Check(cs.connAttempts, Equals, uint32(2)) |
322 | + c.Check(<-cs.networkStateCh, Equals, networkmanager.Connecting) |
323 | + c.Check(<-cs.networkStateCh, Equals, networkmanager.ConnectedGlobal) |
324 | +} |
325 | + |
326 | +/* |
327 | + tests for connectedStateStep() |
328 | +*/ |
329 | + |
330 | +func (s *ConnSuite) TestSteps(c *C) { |
331 | + webget_works := func(ch chan<- bool) { ch <- true } |
332 | + webget_fails := func(ch chan<- bool) { ch <- false } |
333 | + |
334 | + cfg := Config{ |
335 | + RecheckTimeout: config.ConfigTimeDuration{10 * time.Millisecond}, |
336 | + } |
337 | + ch := make(chan networkmanager.State, 10) |
338 | + cs := &connectedState{ |
339 | + config: cfg, |
340 | + networkStateCh: ch, |
341 | + timer: time.NewTimer(time.Second), |
342 | + log: nullog, |
343 | + webget: webget_works, |
344 | + lastSent: false, |
345 | + } |
346 | + ch <- networkmanager.ConnectedGlobal |
347 | + f, e := cs.connectedStateStep() |
348 | + c.Check(e, IsNil) |
349 | + c.Check(f, Equals, true) |
350 | + ch <- networkmanager.ConnectedGlobal // a ConnectedGlobal when connected signals trouble |
351 | + f, e = cs.connectedStateStep() |
352 | + c.Check(e, IsNil) |
353 | + c.Check(f, Equals, false) // so we assume a disconnect happened |
354 | + f, e = cs.connectedStateStep() |
355 | + c.Check(e, IsNil) |
356 | + c.Check(f, Equals, true) // and if the web check works, go back to connected |
357 | + |
358 | + // same scenario, but with failing web check |
359 | + cs.webget = webget_fails |
360 | + ch <- networkmanager.ConnectedGlobal |
361 | + f, e = cs.connectedStateStep() |
362 | + c.Check(e, IsNil) |
363 | + c.Check(f, Equals, false) // first false is from assuming a Connected signals trouble |
364 | + |
365 | + // the next call to Step will time out |
366 | + _ch := make(chan bool, 1) |
367 | + _t := time.NewTimer(10 * time.Millisecond) |
368 | + |
369 | + go func() { |
370 | + f, e := cs.connectedStateStep() |
371 | + c.Check(e, IsNil) |
372 | + _ch <- f |
373 | + }() |
374 | + |
375 | + select { |
376 | + case <-_ch: |
377 | + c.Fatal("test failed to timeout") |
378 | + case <-_t.C: |
379 | + } |
380 | + |
381 | + // put it back together again |
382 | + cs.webget = webget_works |
383 | + // now an recheckTimeout later, we'll get true |
384 | + c.Check(<-_ch, Equals, true) |
385 | + |
386 | + ch <- networkmanager.Disconnected // this should trigger a 'false' |
387 | + ch <- networkmanager.Disconnected // this should not |
388 | + ch <- networkmanager.ConnectedGlobal // this should trigger a 'true' |
389 | + |
390 | + f, e = cs.connectedStateStep() |
391 | + c.Check(e, IsNil) |
392 | + c.Check(f, Equals, false) |
393 | + f, e = cs.connectedStateStep() |
394 | + c.Check(e, IsNil) |
395 | + c.Check(f, Equals, true) |
396 | + |
397 | + close(ch) // this should make it error out |
398 | + _, e = cs.connectedStateStep() |
399 | + c.Check(e, NotNil) |
400 | +} |
401 | + |
402 | +/* |
403 | + tests for ConnectedState() |
404 | +*/ |
405 | + |
406 | +// Todo: get rid of duplication between this and webchecker_test |
407 | +const ( |
408 | + staticText = "something ipsum dolor something" |
409 | + staticHash = "6155f83b471583f47c99998a472a178f" |
410 | +) |
411 | + |
412 | +// mkHandler makes an http.HandlerFunc that returns the provided text |
413 | +// for whatever request it's given. |
414 | +func mkHandler(text string) http.HandlerFunc { |
415 | + return func(w http.ResponseWriter, r *http.Request) { |
416 | + w.(http.Flusher).Flush() |
417 | + w.Write([]byte(text)) |
418 | + w.(http.Flusher).Flush() |
419 | + } |
420 | +} |
421 | + |
422 | +// :oboT |
423 | + |
424 | +// yes, this is an integration test |
425 | +func (s *ConnSuite) TestRun(c *C) { |
426 | + ts := httptest.NewServer(mkHandler(staticText)) |
427 | + defer ts.Close() |
428 | + |
429 | + cfg := Config{ |
430 | + ConnectivityCheckURL: ts.URL, |
431 | + ConnectivityCheckMD5: staticHash, |
432 | + RecheckTimeout: config.ConfigTimeDuration{time.Second}, |
433 | + } |
434 | + |
435 | + busType := testingbus.NewTestingBus(condition.Work(true), condition.Work(true), |
436 | + uint32(networkmanager.ConnectedGlobal), |
437 | + uint32(networkmanager.ConnectedGlobal), |
438 | + uint32(networkmanager.Disconnected), |
439 | + ) |
440 | + |
441 | + out := make(chan bool) |
442 | + dt := time.Second / 10 |
443 | + timer := time.NewTimer(dt) |
444 | + go ConnectedState(busType, cfg, nullog, out) |
445 | + var v bool |
446 | + expecteds := []bool{ |
447 | + false, // first state is always false |
448 | + true, // then it should be true as per ConnectedGlobal above |
449 | + false, // then, false (upon receiving the next ConnectedGlobal) |
450 | + true, // then it should be true (webcheck passed) |
451 | + false, // then it should be false (Disconnected) |
452 | + false, // then it should be false again because it's restarted |
453 | + } |
454 | + |
455 | + for i, expected := range expecteds { |
456 | + timer.Reset(dt) |
457 | + select { |
458 | + case v = <-out: |
459 | + break |
460 | + case <-timer.C: |
461 | + c.Fatalf("Timed out before getting value (#%d)", i+1) |
462 | + } |
463 | + |
464 | + c.Check(v, Equals, expected) |
465 | + } |
466 | +} |
467 | |
468 | === added directory 'connectivity/example' |
469 | === added file 'connectivity/example/main.go' |
470 | --- connectivity/example/main.go 1970-01-01 00:00:00 +0000 |
471 | +++ connectivity/example/main.go 2014-01-20 17:45:15 +0000 |
472 | @@ -0,0 +1,54 @@ |
473 | +/* |
474 | + Copyright 2013-2014 Canonical Ltd. |
475 | + |
476 | + This program is free software: you can redistribute it and/or modify it |
477 | + under the terms of the GNU General Public License version 3, as published |
478 | + by the Free Software Foundation. |
479 | + |
480 | + This program is distributed in the hope that it will be useful, but |
481 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
482 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
483 | + PURPOSE. See the GNU General Public License for more details. |
484 | + |
485 | + You should have received a copy of the GNU General Public License along |
486 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
487 | +*/ |
488 | + |
489 | +// a silly example of the connectivity api |
490 | +package main |
491 | + |
492 | +import ( |
493 | + "fmt" |
494 | + "launchpad.net/ubuntu-push/bus" |
495 | + "launchpad.net/ubuntu-push/config" |
496 | + "launchpad.net/ubuntu-push/connectivity" |
497 | + "launchpad.net/ubuntu-push/logger" |
498 | + "os" |
499 | + "strings" |
500 | +) |
501 | + |
502 | +func main() { |
503 | + log := logger.NewSimpleLogger(os.Stderr, "error") |
504 | + |
505 | + paths := []string{"thing.json", "connectivity/example/thing.json"} |
506 | + for _, path := range paths { |
507 | + cff, err := os.Open(path) |
508 | + if err == nil { |
509 | + var cfg connectivity.Config |
510 | + err = config.ReadConfig(cff, &cfg) |
511 | + if err != nil { |
512 | + log.Fatalf("%s", err) |
513 | + } |
514 | + |
515 | + ch := make(chan bool) |
516 | + go connectivity.ConnectedState(bus.SystemBus, cfg, log, ch) |
517 | + |
518 | + for c := range ch { |
519 | + fmt.Println("Are we connected?", c) |
520 | + } |
521 | + return |
522 | + } |
523 | + } |
524 | + log.Fatalf("Unable to open the config file; tried %s.", strings.Join(paths, ", ")) |
525 | + |
526 | +} |
527 | |
528 | === added file 'connectivity/example/thing.json' |
529 | --- connectivity/example/thing.json 1970-01-01 00:00:00 +0000 |
530 | +++ connectivity/example/thing.json 2014-01-20 17:45:15 +0000 |
531 | @@ -0,0 +1,7 @@ |
532 | +{ |
533 | + "connectTimeouts": ["0s", "2s", "3s", "5s", "11s", "19s", "37s", "67s"], |
534 | + "stabilizingTimeout": "2s", |
535 | + "recheckTimeout": "10m", |
536 | + "connectivityCheckURL": "http://start.ubuntu.com/connectivity-check.html", |
537 | + "connectivityCheckMD5": "4589f42e1546aa47ca181e5d949d310b" |
538 | +} |
539 | |
540 | === added directory 'connectivity/webchecker' |
541 | === added file 'connectivity/webchecker/webchecker.go' |
542 | --- connectivity/webchecker/webchecker.go 1970-01-01 00:00:00 +0000 |
543 | +++ connectivity/webchecker/webchecker.go 2014-01-20 17:45:15 +0000 |
544 | @@ -0,0 +1,80 @@ |
545 | +/* |
546 | + Copyright 2013-2014 Canonical Ltd. |
547 | + |
548 | + This program is free software: you can redistribute it and/or modify it |
549 | + under the terms of the GNU General Public License version 3, as published |
550 | + by the Free Software Foundation. |
551 | + |
552 | + This program is distributed in the hope that it will be useful, but |
553 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
554 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
555 | + PURPOSE. See the GNU General Public License for more details. |
556 | + |
557 | + You should have received a copy of the GNU General Public License along |
558 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
559 | +*/ |
560 | + |
561 | +// Package webchecker checks whether we're actually connected by doing an http |
562 | +// GET to the Ubuntu connectivity check URL, |
563 | +// http://start.ubuntu.com/connectivity-check.html |
564 | +// |
565 | +// We could make it be https to make extra doubly sure, but it's expensive |
566 | +// overkill for the majority of cases. |
567 | +package webchecker |
568 | + |
569 | +import ( |
570 | + "crypto/md5" |
571 | + "fmt" |
572 | + "io" |
573 | + "launchpad.net/ubuntu-push/logger" |
574 | + "net/http" |
575 | +) |
576 | + |
577 | +// how much web would a webchecker check |
578 | + |
579 | +type Webchecker interface { |
580 | + // Webcheck checks whether retrieving the URL works, and if its |
581 | + // contents match the target. If so, then it sends true; if anything |
582 | + // fails, it sends false. |
583 | + Webcheck(chan<- bool) |
584 | +} |
585 | + |
586 | +type webchecker struct { |
587 | + log logger.Logger |
588 | + url string |
589 | + target string |
590 | +} |
591 | + |
592 | +// Build a webchecker for the given URL, that should match the target MD5. |
593 | +func New(url string, target string, log logger.Logger) Webchecker { |
594 | + return &webchecker{log, url, target} |
595 | +} |
596 | + |
597 | +// ensure webchecker implements Webchecker |
598 | +var _ Webchecker = &webchecker{} |
599 | + |
600 | +func (wb *webchecker) Webcheck(ch chan<- bool) { |
601 | + response, err := http.Get(wb.url) |
602 | + if err != nil { |
603 | + wb.log.Errorf("While GETting %s: %s", wb.url, err) |
604 | + ch <- false |
605 | + return |
606 | + } |
607 | + defer response.Body.Close() |
608 | + hash := md5.New() |
609 | + _, err = io.CopyN(hash, response.Body, 1024) |
610 | + if err != io.EOF { |
611 | + wb.log.Errorf("Reading %s, expecting EOF, got: %s", |
612 | + wb.url, err) |
613 | + ch <- false |
614 | + return |
615 | + } |
616 | + sum := fmt.Sprintf("%x", hash.Sum(nil)) |
617 | + if sum == wb.target { |
618 | + wb.log.Infof("Connectivity check passed.") |
619 | + ch <- true |
620 | + } else { |
621 | + wb.log.Infof("Connectivity check failed: content mismatch.") |
622 | + ch <- false |
623 | + } |
624 | +} |
625 | |
626 | === added file 'connectivity/webchecker/webchecker_test.go' |
627 | --- connectivity/webchecker/webchecker_test.go 1970-01-01 00:00:00 +0000 |
628 | +++ connectivity/webchecker/webchecker_test.go 2014-01-20 17:45:15 +0000 |
629 | @@ -0,0 +1,106 @@ |
630 | +/* |
631 | + Copyright 2013-2014 Canonical Ltd. |
632 | + |
633 | + This program is free software: you can redistribute it and/or modify it |
634 | + under the terms of the GNU General Public License version 3, as published |
635 | + by the Free Software Foundation. |
636 | + |
637 | + This program is distributed in the hope that it will be useful, but |
638 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
639 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
640 | + PURPOSE. See the GNU General Public License for more details. |
641 | + |
642 | + You should have received a copy of the GNU General Public License along |
643 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
644 | +*/ |
645 | + |
646 | +package webchecker |
647 | + |
648 | +import ( |
649 | + "io/ioutil" |
650 | + . "launchpad.net/gocheck" |
651 | + "launchpad.net/ubuntu-push/logger" |
652 | + "net/http" |
653 | + "net/http/httptest" |
654 | + "testing" |
655 | +) |
656 | + |
657 | +// hook up gocheck |
658 | +func Test(t *testing.T) { TestingT(t) } |
659 | + |
660 | +type WebcheckerSuite struct{} |
661 | + |
662 | +var _ = Suite(&WebcheckerSuite{}) |
663 | + |
664 | +var nullog = logger.NewSimpleLogger(ioutil.Discard, "error") |
665 | + |
666 | +const ( |
667 | + staticText = "something ipsum dolor something" |
668 | + staticHash = "6155f83b471583f47c99998a472a178f" |
669 | + bigText = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. |
670 | + Vivamus tincidunt vitae sapien tempus fermentum. Cras commodo augue luctu, |
671 | + tempus libero sit amet, laoreet lectus. Vestibulum ali justo et malesuada |
672 | + placerat. Pellentesque viverra luctus velit, adipiscing fermentum tortori |
673 | + vehicula nec. Integer tincidunt purus et pretium vestibulum. Donec portas |
674 | + suscipit pulvinar. Suspendisse potenti. Donec sit amet pharetra nisl, sit |
675 | + amet posuere orci. In feugiat elitist nec augue fringilla, a rutrum risus |
676 | + posuere. Aliquam erat volutpat. Morbi aliquam arcu et eleifend placeraten. |
677 | + Pellentesque egestas varius aliquam. In egestas nisi sed ipsum tristiquer |
678 | + lacinia. Sed vitae nisi non eros consectetur vestibulum vehicularum vitae. |
679 | + Curabitur cursus consectetur eros, in vestibulum turpis cursus at i lorem. |
680 | + Pellentesque ultrices arcu ut massa faucibus, e consequat sapien placerat. |
681 | + Maecenas quis ultricies mi. Phasellus turpis nisl, porttitor ac mi cursus, |
682 | + euismod imperdiet lorem. Donec facilisis est id dignissim imperdiet.` |
683 | + bigHash = "9bf86bce26e8f2d9c9d9bd4a98f9e668" |
684 | +) |
685 | + |
686 | +// mkHandler makes an http.HandlerFunc that returns the provided text |
687 | +// for whatever request it's given. |
688 | +func mkHandler(text string) http.HandlerFunc { |
689 | + return func(w http.ResponseWriter, r *http.Request) { |
690 | + w.(http.Flusher).Flush() |
691 | + w.Write([]byte(text)) |
692 | + w.(http.Flusher).Flush() |
693 | + } |
694 | +} |
695 | + |
696 | +// Webchecker sends true when everything works |
697 | +func (s *WebcheckerSuite) TestWorks(c *C) { |
698 | + ts := httptest.NewServer(mkHandler(staticText)) |
699 | + defer ts.Close() |
700 | + |
701 | + ck := New(ts.URL, staticHash, nullog) |
702 | + ch := make(chan bool, 1) |
703 | + ck.Webcheck(ch) |
704 | + c.Check(<-ch, Equals, true) |
705 | +} |
706 | + |
707 | +// Webchecker sends false if the download fails. |
708 | +func (s *WebcheckerSuite) TestActualFails(c *C) { |
709 | + ck := New("garbage://", "", nullog) |
710 | + ch := make(chan bool, 1) |
711 | + ck.Webcheck(ch) |
712 | + c.Check(<-ch, Equals, false) |
713 | +} |
714 | + |
715 | +// Webchecker sends false if the hash doesn't match |
716 | +func (s *WebcheckerSuite) TestHashFails(c *C) { |
717 | + ts := httptest.NewServer(mkHandler("")) |
718 | + defer ts.Close() |
719 | + |
720 | + ck := New(ts.URL, staticHash, nullog) |
721 | + ch := make(chan bool, 1) |
722 | + ck.Webcheck(ch) |
723 | + c.Check(<-ch, Equals, false) |
724 | +} |
725 | + |
726 | +// Webchecker sends false if the download is too big |
727 | +func (s *WebcheckerSuite) TestTooBigFails(c *C) { |
728 | + ts := httptest.NewServer(mkHandler(bigText)) |
729 | + defer ts.Close() |
730 | + |
731 | + ck := New(ts.URL, bigHash, nullog) |
732 | + ch := make(chan bool, 1) |
733 | + ck.Webcheck(ch) |
734 | + c.Check(<-ch, Equals, false) |
735 | +} |
736 | |
737 | === modified file 'networkmanager/networkmanager.go' |
738 | --- networkmanager/networkmanager.go 2014-01-20 13:44:58 +0000 |
739 | +++ networkmanager/networkmanager.go 2014-01-20 17:45:15 +0000 |
740 | @@ -53,6 +53,7 @@ |
741 | return &networkManager{endp, log} |
742 | } |
743 | |
744 | +// ensure networkManager implements NetworkManager |
745 | var _ NetworkManager = &networkManager{} |
746 | |
747 | /* |
you can avoid the done flag by:
136 + if cs.lastSent == true { Reset(stabilizi ngTimeout)
137 + log.Infof("Sending 'disconnected'.")
138 + cs.lastSent = false
139 + done = true
140 + }
141 + cs.timer.Stop()
142 + cs.webgetC = nil
143 + cs.timer.
144 + cs.currentState = v
changing the order of the if to be last, then you can use plain return directly return cs.lastSent, nil
same change would work for the 2nd done = true,
147 + cs.timer.Stop()
no need to stop the timer if it has just triggered
does +func (cs *connectedState) ConnectTimeout() need to be public?