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

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
Reviewer Review Type Date Requested Status
Samuele Pedroni Approve
Review via email: mp+202243@code.launchpad.net

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.
lp:~chipaca/ubuntu-push/connectivity updated
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
Samuele Pedroni (pedronis) wrote :

you can avoid the done flag by:

136 + if cs.lastSent == true {
137 + log.Infof("Sending 'disconnected'.")
138 + cs.lastSent = false
139 + done = true
140 + }
141 + cs.timer.Stop()
142 + cs.webgetC = nil
143 + cs.timer.Reset(stabilizingTimeout)
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?

lp:~chipaca/ubuntu-push/connectivity updated
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
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 /*

Subscribers

People subscribed via source and target branches