Merge lp:~pedronis/ubuntu-push/automatic-land-to-vivid into lp:ubuntu-push

Proposed by Samuele Pedroni
Status: Merged
Approved by: Roberto Alsina
Approved revision: 145
Merged at revision: 144
Proposed branch: lp:~pedronis/ubuntu-push/automatic-land-to-vivid
Merge into: lp:ubuntu-push
Diff against target: 3221 lines (+1310/-514)
15 files modified
bus/networkmanager/networkmanager.go (+65/-12)
bus/networkmanager/networkmanager_test.go (+109/-1)
bus/urfkill/urfkill.go (+94/-0)
bus/urfkill/urfkill.go_test.go (+121/-0)
client/client.go (+23/-61)
client/client_test.go (+57/-140)
client/session/session.go (+208/-82)
client/session/session_test.go (+358/-173)
debian/changelog (+23/-0)
debian/config.json (+2/-1)
launch_helper/kindpool_test.go (+1/-1)
poller/poller.go (+164/-32)
poller/poller_test.go (+71/-11)
scripts/click-hook (+2/-0)
server/tlsconfig.go (+12/-0)
To merge this branch: bzr merge lp:~pedronis/ubuntu-push/automatic-land-to-vivid
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Review via email: mp+255060@code.launchpad.net

Commit message

[Roberto Alsina]
* click-hook: report failure if hooks_path doesn't exist. [client]

[Bret Barker]
* add a hacky busy sleep loop to workaround go's sleep not
  accounting for suspended time, more logging (lp:1435109). [client]

[John R. Lenton]
* Refactor code maintaining session (better fix for lp:1390663) [client]

[Samuele Pedroni]
* just delegate whether there's a update-worthy image to the
  system-settings helper and system-image. [client]
* stop waking up for polling if in flight-mode and wireless not
  enabled (lp:1437135). [client]
* don't hold a lock for a long time on handleErrConn, trigger
  autoRedial on Error more actively (lp:1435109). [client]
* disallow RC4 and SSLv3. [server]

Description of the change

[Roberto Alsina]
* click-hook: report failure if hooks_path doesn't exist. [client]

[Bret Barker]
* add a hacky busy sleep loop to workaround go's sleep not
  accounting for suspended time, more logging (lp:1435109). [client]

[John R. Lenton]
* Refactor code maintaining session (better fix for lp:1390663) [client]

[Samuele Pedroni]
* just delegate whether there's a update-worthy image to the
  system-settings helper and system-image. [client]
* stop waking up for polling if in flight-mode and wireless not
  enabled (lp:1437135). [client]
* don't hold a lock for a long time on handleErrConn, trigger
  autoRedial on Error more actively (lp:1435109). [client]
* disallow RC4 and SSLv3. [server]

To post a comment you must log in.
Revision history for this message
Roberto Alsina (ralsina) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bus/networkmanager/networkmanager.go'
2--- bus/networkmanager/networkmanager.go 2015-02-26 19:36:57 +0000
3+++ bus/networkmanager/networkmanager.go 2015-04-02 09:52:21 +0000
4@@ -1,5 +1,5 @@
5 /*
6- Copyright 2013-2014 Canonical Ltd.
7+ Copyright 2013-2015 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@@ -14,9 +14,10 @@
12 with this program. If not, see <http://www.gnu.org/licenses/>.
13 */
14
15-// Package networkmanager wraps a couple of NetworkManager's DBus API points:
16-// the org.freedesktop.NetworkManager.state call, and listening for the
17-// StateChange signal.
18+// Package networkmanager wraps a couple of NetworkManager's DBus API
19+// points: the org.freedesktop.NetworkManager.state call, and
20+// listening for the StateChange signal, similarly for the primary
21+// connection and wireless enabled state.
22 package networkmanager
23
24 import (
25@@ -47,8 +48,14 @@
26 // primary connection.
27 GetPrimaryConnection() string
28 // WatchPrimaryConnection listens for changes of NetworkManager's
29- // Primary Connection, and sends it out over the channel returned.
30+ // Primary Connection, and sends them out over the channel returned.
31 WatchPrimaryConnection() (<-chan string, bus.Cancellable, error)
32+ // GetWirelessEnabled fetches and returns NetworkManager's
33+ // wireless state.
34+ GetWirelessEnabled() bool
35+ // WatchWirelessEnabled listens for changes of NetworkManager's
36+ // wireless state, and sends them out over the channel returned.
37+ WatchWirelessEnabled() (<-chan bool, bus.Cancellable, error)
38 }
39
40 type networkManager struct {
41@@ -71,7 +78,7 @@
42 func (nm *networkManager) GetState() State {
43 s, err := nm.bus.GetProperty("state")
44 if err != nil {
45- nm.log.Errorf("failed gettting current state: %s", err)
46+ nm.log.Errorf("failed getting current state: %s", err)
47 nm.log.Debugf("defaulting state to Unknown")
48 return Unknown
49 }
50@@ -108,16 +115,16 @@
51 }
52
53 func (nm *networkManager) GetPrimaryConnection() string {
54- s, err := nm.bus.GetProperty("PrimaryConnection")
55+ got, err := nm.bus.GetProperty("PrimaryConnection")
56 if err != nil {
57- nm.log.Errorf("failed gettting current primary connection: %s", err)
58- nm.log.Debugf("defaulting primary connection to empty")
59+ nm.log.Errorf("failed getting current PrimaryConnection: %s", err)
60+ nm.log.Debugf("defaulting PrimaryConnection to empty")
61 return ""
62 }
63
64- v, ok := s.(dbus.ObjectPath)
65+ v, ok := got.(dbus.ObjectPath)
66 if !ok {
67- nm.log.Errorf("got weird PrimaryConnection: %#v", s)
68+ nm.log.Errorf("got weird PrimaryConnection: %#v", got)
69 return ""
70 }
71
72@@ -142,7 +149,7 @@
73 nm.log.Errorf("got weird PrimaryConnection via PropertiesChanged: %#v", v)
74 return
75 }
76- nm.log.Debugf("got primary connection: %s", con)
77+ nm.log.Debugf("got PrimaryConnection change: %s", con)
78 ch <- string(con)
79 }, func() { close(ch) })
80 if err != nil {
81@@ -152,3 +159,49 @@
82
83 return ch, w, nil
84 }
85+
86+func (nm *networkManager) GetWirelessEnabled() bool {
87+ got, err := nm.bus.GetProperty("WirelessEnabled")
88+ if err != nil {
89+ nm.log.Errorf("failed getting WirelessEnabled: %s", err)
90+ nm.log.Debugf("defaulting WirelessEnabled to true")
91+ return true
92+ }
93+
94+ v, ok := got.(bool)
95+ if !ok {
96+ nm.log.Errorf("got weird WirelessEnabled: %#v", got)
97+ return true
98+ }
99+
100+ return v
101+}
102+
103+func (nm *networkManager) WatchWirelessEnabled() (<-chan bool, bus.Cancellable, error) {
104+ ch := make(chan bool)
105+ w, err := nm.bus.WatchSignal("PropertiesChanged",
106+ func(ppsi ...interface{}) {
107+ pps, ok := ppsi[0].(map[string]dbus.Variant)
108+ if !ok {
109+ nm.log.Errorf("got weird PropertiesChanged: %#v", ppsi[0])
110+ return
111+ }
112+ v, ok := pps["WirelessEnabled"]
113+ if !ok {
114+ return
115+ }
116+ en, ok := v.Value.(bool)
117+ if !ok {
118+ nm.log.Errorf("got weird WirelessEnabled via PropertiesChanged: %#v", v)
119+ return
120+ }
121+ nm.log.Debugf("got WirelessEnabled change: %v", en)
122+ ch <- en
123+ }, func() { close(ch) })
124+ if err != nil {
125+ nm.log.Debugf("failed to set up the watch: %s", err)
126+ return nil, nil, err
127+ }
128+
129+ return ch, w, nil
130+}
131
132=== modified file 'bus/networkmanager/networkmanager_test.go'
133--- bus/networkmanager/networkmanager_test.go 2015-02-26 19:36:57 +0000
134+++ bus/networkmanager/networkmanager_test.go 2015-04-02 09:52:21 +0000
135@@ -1,5 +1,5 @@
136 /*
137- Copyright 2013-2014 Canonical Ltd.
138+ Copyright 2013-2015 Canonical Ltd.
139
140 This program is free software: you can redistribute it and/or modify it
141 under the terms of the GNU General Public License version 3, as published
142@@ -232,3 +232,111 @@
143 c.Check(ok, Equals, true)
144 c.Check(v, Equals, "42")
145 }
146+
147+// GetWirelessEnabled returns the right state when everything works
148+func (s *NMSuite) TestGetWirelessEnabled(c *C) {
149+ nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(true), false), s.log)
150+ en := nm.GetWirelessEnabled()
151+ c.Check(en, Equals, false)
152+}
153+
154+// GetWirelessEnabled returns the right state when dbus fails
155+func (s *NMSuite) TestGetWirelessEnabledFail(c *C) {
156+ nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log)
157+ en := nm.GetWirelessEnabled()
158+ c.Check(en, Equals, true)
159+}
160+
161+// GetWirelessEnabled returns the right state when dbus works but delivers rubbish values
162+func (s *NMSuite) TestGetWirelessEnabledRubbishValues(c *C) {
163+ nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(true), "broken"), s.log)
164+ en := nm.GetWirelessEnabled()
165+ c.Check(en, Equals, true)
166+}
167+
168+// GetWirelessEnabled returns the right state when dbus works but delivers a rubbish structure
169+func (s *NMSuite) TestGetWirelessEnabledRubbishStructure(c *C) {
170+ nm := New(testingbus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{}), s.log)
171+ en := nm.GetWirelessEnabled()
172+ c.Check(en, Equals, true)
173+}
174+
175+func mkWirelessEnMap(en bool) map[string]dbus.Variant {
176+ m := make(map[string]dbus.Variant)
177+ m["WirelessEnabled"] = dbus.Variant{en}
178+ return m
179+}
180+
181+// WatchWirelessEnabled sends a stream of wireless enabled states over the channel
182+func (s *NMSuite) TestWatchWirelessEnabled(c *C) {
183+ tc := testingbus.NewTestingEndpoint(nil, condition.Work(true),
184+ mkWirelessEnMap(true),
185+ mkWirelessEnMap(false),
186+ mkWirelessEnMap(true),
187+ )
188+ nm := New(tc, s.log)
189+ ch, w, err := nm.WatchWirelessEnabled()
190+ c.Assert(err, IsNil)
191+ defer w.Cancel()
192+ l := []bool{<-ch, <-ch, <-ch}
193+ c.Check(l, DeepEquals, []bool{true, false, true})
194+}
195+
196+// WatchWirelessEnabled returns on error if the dbus call fails
197+func (s *NMSuite) TestWatchWirelessEnabledFails(c *C) {
198+ nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log)
199+ _, _, err := nm.WatchWirelessEnabled()
200+ c.Check(err, NotNil)
201+}
202+
203+// WatchWirelessEnabled calls close on its channel when the watch bails
204+func (s *NMSuite) TestWatchWirelessEnabledClosesOnWatchBail(c *C) {
205+ tc := testingbus.NewTestingEndpoint(nil, condition.Work(true))
206+ nm := New(tc, s.log)
207+ ch, w, err := nm.WatchWirelessEnabled()
208+ c.Assert(err, IsNil)
209+ defer w.Cancel()
210+ _, ok := <-ch
211+ c.Check(ok, Equals, false)
212+}
213+
214+// WatchWirelessEnabled survives rubbish values
215+func (s *NMSuite) TestWatchWirelessEnabledSurvivesRubbishValues(c *C) {
216+ tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "gorp")
217+ nm := New(tc, s.log)
218+ ch, w, err := nm.WatchWirelessEnabled()
219+ c.Assert(err, IsNil)
220+ defer w.Cancel()
221+ _, ok := <-ch
222+ c.Check(ok, Equals, false)
223+}
224+
225+// WatchWirelessEnabled ignores non-WirelessEnabled PropertyChanged
226+func (s *NMSuite) TestWatchWirelessEnabledIgnoresIrrelephant(c *C) {
227+ tc := testingbus.NewTestingEndpoint(nil, condition.Work(true),
228+ map[string]dbus.Variant{"foo": dbus.Variant{}},
229+ map[string]dbus.Variant{"WirelessEnabled": dbus.Variant{true}},
230+ )
231+ nm := New(tc, s.log)
232+ ch, w, err := nm.WatchWirelessEnabled()
233+ c.Assert(err, IsNil)
234+ defer w.Cancel()
235+ v, ok := <-ch
236+ c.Check(ok, Equals, true)
237+ c.Check(v, Equals, true)
238+}
239+
240+// WatchWirelessEnabled ignores rubbish WirelessEnabled
241+func (s *NMSuite) TestWatchWirelessEnabledIgnoresRubbishValues(c *C) {
242+ tc := testingbus.NewTestingEndpoint(nil, condition.Work(true),
243+ map[string]dbus.Variant{"WirelessEnabled": dbus.Variant{-12}},
244+ map[string]dbus.Variant{"WirelessEnabled": dbus.Variant{false}},
245+ )
246+ nm := New(tc, s.log)
247+ ch, w, err := nm.WatchWirelessEnabled()
248+ c.Assert(err, IsNil)
249+ defer w.Cancel()
250+ v, ok := <-ch
251+ c.Check(ok, Equals, true)
252+ c.Check(v, Equals, false)
253+}
254
255=== added directory 'bus/urfkill'
256=== added file 'bus/urfkill/urfkill.go'
257--- bus/urfkill/urfkill.go 1970-01-01 00:00:00 +0000
258+++ bus/urfkill/urfkill.go 2015-04-02 09:52:21 +0000
259@@ -0,0 +1,94 @@
260+/*
261+ Copyright 2015 Canonical Ltd.
262+
263+ This program is free software: you can redistribute it and/or modify it
264+ under the terms of the GNU General Public License version 3, as published
265+ by the Free Software Foundation.
266+
267+ This program is distributed in the hope that it will be useful, but
268+ WITHOUT ANY WARRANTY; without even the implied warranties of
269+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
270+ PURPOSE. See the GNU General Public License for more details.
271+
272+ You should have received a copy of the GNU General Public License along
273+ with this program. If not, see <http://www.gnu.org/licenses/>.
274+*/
275+
276+// Package urfkill wraps a couple of URfkill's DBus API points to
277+// watch for flight mode state changes.
278+package urfkill
279+
280+import (
281+ //"launchpad.net/go-dbus/v1"
282+
283+ "launchpad.net/ubuntu-push/bus"
284+ "launchpad.net/ubuntu-push/logger"
285+)
286+
287+// URfkill lives on a well-knwon bus.Address
288+var BusAddress bus.Address = bus.Address{
289+ Interface: "org.freedesktop.URfkill",
290+ Path: "/org/freedesktop/URfkill",
291+ Name: "org.freedesktop.URfkill",
292+}
293+
294+/*****************************************************************
295+ * URfkill (and its implementation)
296+ */
297+
298+type URfkill interface {
299+ // IsFlightMode returns flight mode state.
300+ IsFlightMode() bool
301+ // WatchFlightMode listens for changes to URfkill's flight
302+ // mode state, and sends them out over the channel returned.
303+ WatchFlightMode() (<-chan bool, bus.Cancellable, error)
304+}
305+
306+type uRfkill struct {
307+ bus bus.Endpoint
308+ log logger.Logger
309+}
310+
311+// New returns a new URfkill that'll use the provided bus.Endpoint
312+func New(endp bus.Endpoint, log logger.Logger) URfkill {
313+ return &uRfkill{endp, log}
314+}
315+
316+// ensure uRfkill implements URfkill
317+var _ URfkill = &uRfkill{}
318+
319+/*
320+ public methods
321+*/
322+
323+func (ur *uRfkill) IsFlightMode() bool {
324+ var res bool
325+ err := ur.bus.Call("IsFlightMode", bus.Args(), &res)
326+ if err != nil {
327+ ur.log.Errorf("failed getting flight-mode state: %s", err)
328+ ur.log.Debugf("defaulting flight-mode state to false")
329+ return false
330+ }
331+ return res
332+}
333+
334+func (ur *uRfkill) WatchFlightMode() (<-chan bool, bus.Cancellable, error) {
335+ ch := make(chan bool)
336+ w, err := ur.bus.WatchSignal("FlightModeChanged",
337+ func(ns ...interface{}) {
338+ stbool, ok := ns[0].(bool)
339+ if !ok {
340+ ur.log.Errorf("got weird flight-mode state: %#v", ns[0])
341+ return
342+ }
343+ ur.log.Debugf("got flight-mode change: %v", stbool)
344+ ch <- stbool
345+ },
346+ func() { close(ch) })
347+ if err != nil {
348+ ur.log.Debugf("Failed to set up the watch: %s", err)
349+ return nil, nil, err
350+ }
351+
352+ return ch, w, nil
353+}
354
355=== added file 'bus/urfkill/urfkill.go_test.go'
356--- bus/urfkill/urfkill.go_test.go 1970-01-01 00:00:00 +0000
357+++ bus/urfkill/urfkill.go_test.go 2015-04-02 09:52:21 +0000
358@@ -0,0 +1,121 @@
359+/*
360+ Copyright 2015 Canonical Ltd.
361+
362+ This program is free software: you can redistribute it and/or modify it
363+ under the terms of the GNU General Public License version 3, as published
364+ by the Free Software Foundation.
365+
366+ This program is distributed in the hope that it will be useful, but
367+ WITHOUT ANY WARRANTY; without even the implied warranties of
368+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
369+ PURPOSE. See the GNU General Public License for more details.
370+
371+ You should have received a copy of the GNU General Public License along
372+ with this program. If not, see <http://www.gnu.org/licenses/>.
373+*/
374+
375+package urfkill
376+
377+import (
378+ "testing"
379+
380+ //"launchpad.net/go-dbus/v1"
381+ . "launchpad.net/gocheck"
382+
383+ testingbus "launchpad.net/ubuntu-push/bus/testing"
384+ "launchpad.net/ubuntu-push/logger"
385+ helpers "launchpad.net/ubuntu-push/testing"
386+ "launchpad.net/ubuntu-push/testing/condition"
387+)
388+
389+// hook up gocheck
390+func Test(t *testing.T) { TestingT(t) }
391+
392+type URSuite struct {
393+ log logger.Logger
394+}
395+
396+var _ = Suite(&URSuite{})
397+
398+func (s *URSuite) SetUpTest(c *C) {
399+ s.log = helpers.NewTestLogger(c, "debug")
400+}
401+
402+func (s *URSuite) TestNew(c *C) {
403+ ur := New(testingbus.NewTestingEndpoint(nil, condition.Work(true)), s.log)
404+ c.Check(ur, NotNil)
405+}
406+
407+// IsFlightMode returns the right state when everything works
408+func (s *URSuite) TestIsFlightMode(c *C) {
409+ endp := testingbus.NewTestingEndpoint(nil, condition.Work(true), true)
410+ ur := New(endp, s.log)
411+ state := ur.IsFlightMode()
412+ c.Check(state, Equals, true)
413+ callArgs := testingbus.GetCallArgs(endp)
414+ c.Assert(callArgs, HasLen, 1)
415+ c.Assert(callArgs[0].Member, Equals, "IsFlightMode")
416+ c.Assert(callArgs[0].Args, HasLen, 0)
417+}
418+
419+// IsFlightMode returns the right state when dbus fails
420+func (s *URSuite) TestIsFlightModeFail(c *C) {
421+ ur := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log)
422+ state := ur.IsFlightMode()
423+ c.Check(state, Equals, false)
424+}
425+
426+// IsFlightMode returns the right state when dbus works but delivers
427+// rubbish values
428+func (s *URSuite) TestIsFlightModeRubbishValues(c *C) {
429+ ur := New(testingbus.NewTestingEndpoint(nil, condition.Work(true), "broken"), s.log)
430+ state := ur.IsFlightMode()
431+ c.Check(state, Equals, false)
432+}
433+
434+// IsFlightMode returns the right state when dbus works but delivers a rubbish structure
435+func (s *URSuite) TestIsFlightModeRubbishStructure(c *C) {
436+ ur := New(testingbus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{}), s.log)
437+ state := ur.IsFlightMode()
438+ c.Check(state, Equals, false)
439+}
440+
441+// WatchFightMode sends a stream of states over the channel
442+func (s *URSuite) TestWatchFlightMode(c *C) {
443+ tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), false, true, false)
444+ ur := New(tc, s.log)
445+ ch, w, err := ur.WatchFlightMode()
446+ c.Assert(err, IsNil)
447+ defer w.Cancel()
448+ l := []bool{<-ch, <-ch, <-ch}
449+ c.Check(l, DeepEquals, []bool{false, true, false})
450+}
451+
452+// WatchFlightMode returns on error if the dbus call fails
453+func (s *URSuite) TestWatchFlightModeFails(c *C) {
454+ ur := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log)
455+ _, _, err := ur.WatchFlightMode()
456+ c.Check(err, NotNil)
457+}
458+
459+// WatchFlightMode calls close on its channel when the watch bails
460+func (s *URSuite) TestWatchFlightModeClosesOnWatchBail(c *C) {
461+ tc := testingbus.NewTestingEndpoint(nil, condition.Work(true))
462+ ur := New(tc, s.log)
463+ ch, w, err := ur.WatchFlightMode()
464+ c.Assert(err, IsNil)
465+ defer w.Cancel()
466+ _, ok := <-ch
467+ c.Check(ok, Equals, false)
468+}
469+
470+// WatchFlightMode survives rubbish values
471+func (s *URSuite) TestWatchFlightModeSurvivesRubbishValues(c *C) {
472+ tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "gorp")
473+ ur := New(tc, s.log)
474+ ch, w, err := ur.WatchFlightMode()
475+ c.Assert(err, IsNil)
476+ defer w.Cancel()
477+ _, ok := <-ch
478+ c.Check(ok, Equals, false)
479+}
480
481=== modified file 'client/client.go'
482--- client/client.go 2015-02-27 10:47:17 +0000
483+++ client/client.go 2015-04-02 09:52:21 +0000
484@@ -79,6 +79,7 @@
485 PollNetworkWait config.ConfigTimeDuration `json:"poll_net_wait"`
486 PollPolldWait config.ConfigTimeDuration `json:"poll_polld_wait"`
487 PollDoneWait config.ConfigTimeDuration `json:"poll_done_wait"`
488+ PollBusyWait config.ConfigTimeDuration `json:"poll_busy_wait"`
489 }
490
491 // PushService is the interface we use of service.PushService.
492@@ -115,8 +116,7 @@
493 systemImageEndp bus.Endpoint
494 systemImageInfo *systemimage.InfoResult
495 connCh chan bool
496- hasConnectivity bool
497- session *session.ClientSession
498+ session session.ClientSession
499 sessionConnectedCh chan uint32
500 pushService PushService
501 postalService PostalService
502@@ -125,16 +125,20 @@
503 installedChecker click.InstalledChecker
504 poller poller.Poller
505 accountsCh <-chan accounts.Changed
506+ // session-side channels
507+ broadcastCh chan *session.BroadcastNotification
508+ notificationsCh chan session.AddressedNotification
509 }
510
511 // Creates a new Ubuntu Push Notifications client-side daemon that will use
512 // the given configuration file.
513 func NewPushClient(configPath string, leveldbPath string) *PushClient {
514- client := new(PushClient)
515- client.configPath = configPath
516- client.leveldbPath = leveldbPath
517-
518- return client
519+ return &PushClient{
520+ configPath: configPath,
521+ leveldbPath: leveldbPath,
522+ broadcastCh: make(chan *session.BroadcastNotification),
523+ notificationsCh: make(chan session.AddressedNotification),
524+ }
525 }
526
527 var newIdentifier = identifier.New
528@@ -206,6 +210,8 @@
529 AuthGetter: client.getAuthorization,
530 AuthURL: client.config.SessionURL,
531 AddresseeChecker: client,
532+ BroadcastCh: client.broadcastCh,
533+ NotificationsCh: client.notificationsCh,
534 }
535 }
536
537@@ -241,6 +247,7 @@
538 NetworkWait: client.config.PollNetworkWait.TimeDuration(),
539 PolldWait: client.config.PollPolldWait.TimeDuration(),
540 DoneWait: client.config.PollDoneWait.TimeDuration(),
541+ BusyWait: client.config.PollBusyWait.TimeDuration(),
542 },
543 Log: client.log,
544 SessionStateGetter: client.session,
545@@ -280,7 +287,6 @@
546
547 // takeTheBus starts the connection(s) to D-Bus and sets up associated event channels
548 func (client *PushClient) takeTheBus() error {
549- fmt.Println("FOO")
550 cs := connectivity.New(client.connectivityEndp,
551 client.config.ConnectivityConfig, client.log)
552 go cs.Track(client.connCh)
553@@ -308,6 +314,7 @@
554 return err
555 }
556 client.session = sess
557+ sess.KeepConnection()
558 client.poller = poller.New(client.derivePollerSetup())
559 return nil
560 }
561@@ -376,29 +383,6 @@
562 }
563 }
564
565-// handleConnState deals with connectivity events
566-func (client *PushClient) handleConnState(hasConnectivity bool) {
567- client.log.Debugf("handleConnState: %v", hasConnectivity)
568- if client.hasConnectivity == hasConnectivity {
569- // nothing to do!
570- return
571- }
572- client.hasConnectivity = hasConnectivity
573- client.session.Close()
574- if hasConnectivity {
575- client.session.AutoRedial(client.sessionConnectedCh)
576- }
577-}
578-
579-// handleErr deals with the session erroring out of its loop
580-func (client *PushClient) handleErr(err error) {
581- // if we're not connected, we don't really care
582- client.log.Errorf("session exited: %s", err)
583- if client.hasConnectivity {
584- client.session.AutoRedial(client.sessionConnectedCh)
585- }
586-}
587-
588 // filterBroadcastNotification finds out if the notification is about an actual
589 // upgrade for the device. It expects msg.Decoded entries to look
590 // like:
591@@ -426,20 +410,9 @@
592 if len(pair) < 1 {
593 return false
594 }
595- buildNumber, ok := pair[0].(float64)
596- if !ok {
597- return false
598- }
599- curBuildNumber := float64(client.systemImageInfo.BuildNumber)
600- if buildNumber > curBuildNumber {
601- return true
602- }
603- // xxx we should really compare channel_target and alias here
604- // going backward by a margin, assume switch of target
605- if buildNumber < curBuildNumber && (curBuildNumber-buildNumber) > 10 {
606- return true
607- }
608- return false
609+ _, ok = pair[0].(float64)
610+ // ok means it sanity checks, let the helper check for build number etc
611+ return ok
612 }
613
614 // handleBroadcastNotification deals with receiving a broadcast notification
615@@ -469,28 +442,18 @@
616 return nil
617 }
618
619-// handleAccountsChange deals with the user adding or removing (or
620-// changing) the u1 account used to auth
621-func (client *PushClient) handleAccountsChange() {
622- client.log.Infof("U1 account changed; restarting session")
623- client.session.ClearCookie()
624- client.session.Close()
625-}
626-
627 // doLoop connects events with their handlers
628-func (client *PushClient) doLoop(connhandler func(bool), bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(session.AddressedNotification) error, errhandler func(error), unregisterhandler func(*click.AppId), accountshandler func()) {
629+func (client *PushClient) doLoop(connhandler func(bool), bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(session.AddressedNotification) error, unregisterhandler func(*click.AppId), accountshandler func()) {
630 for {
631 select {
632 case <-client.accountsCh:
633 accountshandler()
634 case state := <-client.connCh:
635 connhandler(state)
636- case bcast := <-client.session.BroadcastCh:
637+ case bcast := <-client.broadcastCh:
638 bcasthandler(bcast)
639- case aucast := <-client.session.NotificationsCh:
640+ case aucast := <-client.notificationsCh:
641 ucasthandler(aucast)
642- case err := <-client.session.ErrCh:
643- errhandler(err)
644 case count := <-client.sessionConnectedCh:
645 client.log.Debugf("session connected after %d attempts", count)
646 case app := <-client.unregisterCh:
647@@ -512,12 +475,11 @@
648
649 // Loop calls doLoop with the "real" handlers
650 func (client *PushClient) Loop() {
651- client.doLoop(client.handleConnState,
652+ client.doLoop(client.session.HasConnectivity,
653 client.handleBroadcastNotification,
654 client.handleUnicastNotification,
655- client.handleErr,
656 client.handleUnregister,
657- client.handleAccountsChange,
658+ client.session.ResetCookie,
659 )
660 }
661
662
663=== modified file 'client/client_test.go'
664--- client/client_test.go 2015-02-27 10:47:17 +0000
665+++ client/client_test.go 2015-04-02 09:52:21 +0000
666@@ -43,7 +43,6 @@
667 clickhelp "launchpad.net/ubuntu-push/click/testing"
668 "launchpad.net/ubuntu-push/client/service"
669 "launchpad.net/ubuntu-push/client/session"
670- "launchpad.net/ubuntu-push/client/session/seenstate"
671 "launchpad.net/ubuntu-push/config"
672 "launchpad.net/ubuntu-push/identifier"
673 idtesting "launchpad.net/ubuntu-push/identifier/testing"
674@@ -186,6 +185,7 @@
675 "poll_net_wait": "1m",
676 "poll_polld_wait": "3m",
677 "poll_done_wait": "5s",
678+ "poll_busy_wait": "0s",
679 }
680 for k, v := range overrides {
681 cfgMap[k] = v
682@@ -427,6 +427,8 @@
683 AuthGetter: func(string) string { return "" },
684 AuthURL: "xyzzy://",
685 AddresseeChecker: cli,
686+ BroadcastCh: make(chan *session.BroadcastNotification),
687+ NotificationsCh: make(chan session.AddressedNotification),
688 }
689 // sanity check that we are looking at all fields
690 vExpected := reflect.ValueOf(expected)
691@@ -440,6 +442,11 @@
692 conf := cli.deriveSessionConfig(info)
693 // compare authGetter by string
694 c.Check(fmt.Sprintf("%#v", conf.AuthGetter), Equals, fmt.Sprintf("%#v", cli.getAuthorization))
695+ // channels are ok as long as non-nil
696+ conf.BroadcastCh = nil
697+ conf.NotificationsCh = nil
698+ expected.BroadcastCh = nil
699+ expected.NotificationsCh = nil
700 // and set it to nil
701 conf.AuthGetter = nil
702 expected.AuthGetter = nil
703@@ -521,10 +528,18 @@
704 /*****************************************************************
705 derivePollerSetup tests
706 ******************************************************************/
707+type derivePollerSession struct{}
708+
709+func (s *derivePollerSession) ResetCookie() {}
710+func (s *derivePollerSession) State() session.ClientSessionState { return session.Unknown }
711+func (s *derivePollerSession) HasConnectivity(bool) {}
712+func (s *derivePollerSession) KeepConnection() error { return nil }
713+func (s *derivePollerSession) StopKeepConnection() {}
714+
715 func (cs *clientSuite) TestDerivePollerSetup(c *C) {
716 cs.writeTestConfig(map[string]interface{}{})
717 cli := NewPushClient(cs.configPath, cs.leveldbPath)
718- cli.session = new(session.ClientSession)
719+ cli.session = new(derivePollerSession)
720 err := cli.configure()
721 c.Assert(err, IsNil)
722 expected := &poller.PollerSetup{
723@@ -705,22 +720,6 @@
724 }
725
726 /*****************************************************************
727- handleErr tests
728-******************************************************************/
729-
730-func (cs *clientSuite) TestHandleErr(c *C) {
731- cli := NewPushClient(cs.configPath, cs.leveldbPath)
732- cli.log = cs.log
733- cli.systemImageInfo = siInfoRes
734- c.Assert(cli.initSessionAndPoller(), IsNil)
735- cs.log.ResetCapture()
736- cli.hasConnectivity = true
737- defer cli.session.Close()
738- cli.handleErr(errors.New("bananas"))
739- c.Check(cs.log.Captured(), Matches, ".*session exited.*bananas\n")
740-}
741-
742-/*****************************************************************
743 seenStateFactory tests
744 ******************************************************************/
745
746@@ -741,60 +740,6 @@
747 }
748
749 /*****************************************************************
750- handleConnState tests
751-******************************************************************/
752-
753-func (cs *clientSuite) TestHandleConnStateD2C(c *C) {
754- cli := NewPushClient(cs.configPath, cs.leveldbPath)
755- cli.log = cs.log
756- cli.systemImageInfo = siInfoRes
757- c.Assert(cli.initSessionAndPoller(), IsNil)
758-
759- c.Assert(cli.hasConnectivity, Equals, false)
760- defer cli.session.Close()
761- cli.handleConnState(true)
762- c.Check(cli.hasConnectivity, Equals, true)
763- c.Assert(cli.session, NotNil)
764-}
765-
766-func (cs *clientSuite) TestHandleConnStateSame(c *C) {
767- cli := NewPushClient(cs.configPath, cs.leveldbPath)
768- cli.log = cs.log
769- // here we want to check that we don't do anything
770- c.Assert(cli.session, IsNil)
771- c.Assert(cli.hasConnectivity, Equals, false)
772- cli.handleConnState(false)
773- c.Check(cli.session, IsNil)
774-
775- cli.hasConnectivity = true
776- cli.handleConnState(true)
777- c.Check(cli.session, IsNil)
778-}
779-
780-func (cs *clientSuite) TestHandleConnStateC2D(c *C) {
781- cli := NewPushClient(cs.configPath, cs.leveldbPath)
782- cli.log = cs.log
783- cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, seenstate.NewSeenState, cs.log)
784- cli.session.Dial()
785- cli.hasConnectivity = true
786-
787- // cli.session.State() will be "Error" here, for now at least
788- c.Check(cli.session.State(), Not(Equals), session.Disconnected)
789- cli.handleConnState(false)
790- c.Check(cli.session.State(), Equals, session.Disconnected)
791-}
792-
793-func (cs *clientSuite) TestHandleConnStateC2DPending(c *C) {
794- cli := NewPushClient(cs.configPath, cs.leveldbPath)
795- cli.log = cs.log
796- cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, seenstate.NewSeenState, cs.log)
797- cli.hasConnectivity = true
798-
799- cli.handleConnState(false)
800- c.Check(cli.session.State(), Equals, session.Disconnected)
801-}
802-
803-/*****************************************************************
804 filterBroadcastNotification tests
805 ******************************************************************/
806
807@@ -811,35 +756,11 @@
808 // empty
809 msg := &session.BroadcastNotification{}
810 c.Check(cli.filterBroadcastNotification(msg), Equals, false)
811- // same build number
812- msg = &session.BroadcastNotification{
813- Decoded: []map[string]interface{}{
814- map[string]interface{}{
815- "daily/mako": []interface{}{float64(102), "tubular"},
816- },
817- },
818- }
819- c.Check(cli.filterBroadcastNotification(msg), Equals, false)
820- // higher build number and pick last
821- msg = &session.BroadcastNotification{
822- Decoded: []map[string]interface{}{
823- map[string]interface{}{
824- "daily/mako": []interface{}{float64(102), "tubular"},
825- },
826- map[string]interface{}{
827- "daily/mako": []interface{}{float64(103), "tubular"},
828- },
829- },
830- }
831- c.Check(cli.filterBroadcastNotification(msg), Equals, true)
832- // going backward by a margin, assume switch of alias
833- msg = &session.BroadcastNotification{
834- Decoded: []map[string]interface{}{
835- map[string]interface{}{
836- "daily/mako": []interface{}{float64(102), "tubular"},
837- },
838- map[string]interface{}{
839- "daily/mako": []interface{}{float64(2), "urban"},
840+ // same build number, we let the helper deal
841+ msg = &session.BroadcastNotification{
842+ Decoded: []map[string]interface{}{
843+ map[string]interface{}{
844+ "daily/mako": []interface{}{float64(102), "tubular"},
845 },
846 },
847 }
848@@ -888,9 +809,7 @@
849 }
850 negativeBroadcastNotification = &session.BroadcastNotification{
851 Decoded: []map[string]interface{}{
852- map[string]interface{}{
853- "daily/mako": []interface{}{float64(102), "tubular"},
854- },
855+ map[string]interface{}{},
856 },
857 }
858 )
859@@ -1012,7 +931,6 @@
860 var nopConn = func(bool) {}
861 var nopBcast = func(*session.BroadcastNotification) error { return nil }
862 var nopUcast = func(session.AddressedNotification) error { return nil }
863-var nopError = func(error) {}
864 var nopUnregister = func(*click.AppId) {}
865 var nopAcct = func() {}
866
867@@ -1025,7 +943,7 @@
868 c.Assert(cli.initSessionAndPoller(), IsNil)
869
870 ch := make(chan bool, 1)
871- go cli.doLoop(func(bool) { ch <- true }, nopBcast, nopUcast, nopError, nopUnregister, nopAcct)
872+ go cli.doLoop(func(bool) { ch <- true }, nopBcast, nopUcast, nopUnregister, nopAcct)
873 c.Check(takeNextBool(ch), Equals, true)
874 }
875
876@@ -1034,11 +952,11 @@
877 cli.log = cs.log
878 cli.systemImageInfo = siInfoRes
879 c.Assert(cli.initSessionAndPoller(), IsNil)
880- cli.session.BroadcastCh = make(chan *session.BroadcastNotification, 1)
881- cli.session.BroadcastCh <- &session.BroadcastNotification{}
882+ cli.broadcastCh = make(chan *session.BroadcastNotification, 1)
883+ cli.broadcastCh <- &session.BroadcastNotification{}
884
885 ch := make(chan bool, 1)
886- go cli.doLoop(nopConn, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopError, nopUnregister, nopAcct)
887+ go cli.doLoop(nopConn, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopUnregister, nopAcct)
888 c.Check(takeNextBool(ch), Equals, true)
889 }
890
891@@ -1047,24 +965,11 @@
892 cli.log = cs.log
893 cli.systemImageInfo = siInfoRes
894 c.Assert(cli.initSessionAndPoller(), IsNil)
895- cli.session.NotificationsCh = make(chan session.AddressedNotification, 1)
896- cli.session.NotificationsCh <- session.AddressedNotification{}
897-
898- ch := make(chan bool, 1)
899- go cli.doLoop(nopConn, nopBcast, func(session.AddressedNotification) error { ch <- true; return nil }, nopError, nopUnregister, nopAcct)
900- c.Check(takeNextBool(ch), Equals, true)
901-}
902-
903-func (cs *clientSuite) TestDoLoopErr(c *C) {
904- cli := NewPushClient(cs.configPath, cs.leveldbPath)
905- cli.log = cs.log
906- cli.systemImageInfo = siInfoRes
907- c.Assert(cli.initSessionAndPoller(), IsNil)
908- cli.session.ErrCh = make(chan error, 1)
909- cli.session.ErrCh <- nil
910-
911- ch := make(chan bool, 1)
912- go cli.doLoop(nopConn, nopBcast, nopUcast, func(error) { ch <- true }, nopUnregister, nopAcct)
913+ cli.notificationsCh = make(chan session.AddressedNotification, 1)
914+ cli.notificationsCh <- session.AddressedNotification{}
915+
916+ ch := make(chan bool, 1)
917+ go cli.doLoop(nopConn, nopBcast, func(session.AddressedNotification) error { ch <- true; return nil }, nopUnregister, nopAcct)
918 c.Check(takeNextBool(ch), Equals, true)
919 }
920
921@@ -1077,7 +982,7 @@
922 cli.unregisterCh <- app1
923
924 ch := make(chan bool, 1)
925- go cli.doLoop(nopConn, nopBcast, nopUcast, nopError, func(app *click.AppId) { c.Check(app.Original(), Equals, appId1); ch <- true }, nopAcct)
926+ go cli.doLoop(nopConn, nopBcast, nopUcast, func(app *click.AppId) { c.Check(app.Original(), Equals, appId1); ch <- true }, nopAcct)
927 c.Check(takeNextBool(ch), Equals, true)
928 }
929
930@@ -1091,7 +996,7 @@
931 cli.accountsCh = acctCh
932
933 ch := make(chan bool, 1)
934- go cli.doLoop(nopConn, nopBcast, nopUcast, nopError, nopUnregister, func() { ch <- true })
935+ go cli.doLoop(nopConn, nopBcast, nopUcast, nopUnregister, func() { ch <- true })
936 c.Check(takeNextBool(ch), Equals, true)
937 }
938
939@@ -1126,6 +1031,20 @@
940 Loop() tests
941 ******************************************************************/
942
943+type loopSession struct{ hasConn bool }
944+
945+func (s *loopSession) ResetCookie() {}
946+func (s *loopSession) State() session.ClientSessionState {
947+ if s.hasConn {
948+ return session.Connected
949+ } else {
950+ return session.Disconnected
951+ }
952+}
953+func (s *loopSession) HasConnectivity(hasConn bool) { s.hasConn = hasConn }
954+func (s *loopSession) KeepConnection() error { return nil }
955+func (s *loopSession) StopKeepConnection() {}
956+
957 func (cs *clientSuite) TestLoop(c *C) {
958 cli := NewPushClient(cs.configPath, cs.leveldbPath)
959 cli.connCh = make(chan bool)
960@@ -1140,8 +1059,7 @@
961
962 c.Assert(cli.initSessionAndPoller(), IsNil)
963
964- cli.session.BroadcastCh = make(chan *session.BroadcastNotification)
965- cli.session.ErrCh = make(chan error)
966+ cli.broadcastCh = make(chan *session.BroadcastNotification)
967
968 // we use tick() to make sure things have been through the
969 // event loop at least once before looking at things;
970@@ -1149,6 +1067,10 @@
971 // at and the loop itself.
972 tick := func() { cli.sessionConnectedCh <- 42 }
973
974+ c.Assert(cli.session, NotNil)
975+ cli.session.StopKeepConnection()
976+ cli.session = &loopSession{}
977+
978 go cli.Loop()
979
980 // sessionConnectedCh to nothing in particular, but it'll help sync this test
981@@ -1158,24 +1080,19 @@
982
983 // loop() should have connected:
984 // * connCh to the connectivity checker
985- c.Check(cli.hasConnectivity, Equals, false)
986+ c.Check(cli.session.State(), Equals, session.Disconnected)
987 cli.connCh <- true
988 tick()
989- c.Check(cli.hasConnectivity, Equals, true)
990+ c.Check(cli.session.State(), Equals, session.Connected)
991 cli.connCh <- false
992 tick()
993- c.Check(cli.hasConnectivity, Equals, false)
994+ c.Check(cli.session.State(), Equals, session.Disconnected)
995
996 // * session.BroadcastCh to the notifications handler
997 c.Check(d.bcastCount, Equals, 0)
998- cli.session.BroadcastCh <- positiveBroadcastNotification
999+ cli.broadcastCh <- positiveBroadcastNotification
1000 tick()
1001 c.Check(d.bcastCount, Equals, 1)
1002-
1003- // * session.ErrCh to the error handler
1004- cli.session.ErrCh <- nil
1005- tick()
1006- c.Check(cs.log.Captured(), Matches, "(?ms).*session exited.*")
1007 }
1008
1009 /*****************************************************************
1010
1011=== modified file 'client/session/session.go'
1012--- client/session/session.go 2015-01-22 11:05:37 +0000
1013+++ client/session/session.go 2015-04-02 09:52:21 +0000
1014@@ -39,6 +39,14 @@
1015 "launchpad.net/ubuntu-push/util"
1016 )
1017
1018+type sessCmd uint8
1019+
1020+const (
1021+ cmdDisconnect sessCmd = iota
1022+ cmdConnect
1023+ cmdResetCookie
1024+)
1025+
1026 var (
1027 wireVersionBytes = []byte{protocol.ProtocolWireVersion}
1028 )
1029@@ -70,10 +78,12 @@
1030
1031 const (
1032 Error ClientSessionState = iota
1033+ Pristine
1034 Disconnected
1035 Connected
1036 Started
1037 Running
1038+ Shutdown
1039 Unknown
1040 )
1041
1042@@ -83,10 +93,12 @@
1043 }
1044 return [Unknown]string{
1045 "Error",
1046+ "Pristine",
1047 "Disconnected",
1048 "Connected",
1049 "Started",
1050 "Running",
1051+ "Shutdown",
1052 }[s]
1053 }
1054
1055@@ -118,10 +130,20 @@
1056 AuthGetter func(string) string
1057 AuthURL string
1058 AddresseeChecker AddresseeChecking
1059+ BroadcastCh chan *BroadcastNotification
1060+ NotificationsCh chan AddressedNotification
1061 }
1062
1063 // ClientSession holds a client<->server session and its configuration.
1064-type ClientSession struct {
1065+type ClientSession interface {
1066+ ResetCookie()
1067+ State() ClientSessionState
1068+ HasConnectivity(bool)
1069+ KeepConnection() error
1070+ StopKeepConnection()
1071+}
1072+
1073+type clientSession struct {
1074 // configuration
1075 DeviceId string
1076 ClientSessionConfig
1077@@ -145,25 +167,36 @@
1078 proto protocol.Protocol
1079 pingInterval time.Duration
1080 retrier util.AutoRedialer
1081- retrierLock sync.Mutex
1082 cookie string
1083 // status
1084- stateP *uint32
1085- ErrCh chan error
1086- BroadcastCh chan *BroadcastNotification
1087- NotificationsCh chan AddressedNotification
1088+ stateLock sync.RWMutex
1089+ state ClientSessionState
1090 // authorization
1091 auth string
1092 // autoredial knobs
1093 shouldDelayP *uint32
1094 lastAutoRedial time.Time
1095- redialDelay func(*ClientSession) time.Duration
1096+ redialDelay func(*clientSession) time.Duration
1097 redialJitter func(time.Duration) time.Duration
1098 redialDelays []time.Duration
1099 redialDelaysIdx int
1100+ // connection events, and cookie reset requests, come in over here
1101+ cmdCh chan sessCmd
1102+ // last seen connection event is here
1103+ lastConn bool
1104+ // connection events are handled by this
1105+ connHandler func(bool)
1106+ // autoredial goes over here (xxx spurious goroutine involved)
1107+ doneCh chan uint32
1108+ // main loop errors out through here (possibly another spurious goroutine)
1109+ errCh chan error
1110+ // main loop errors are handled by this
1111+ errHandler func(error)
1112+ // look, a stopper!
1113+ stopCh chan struct{}
1114 }
1115
1116-func redialDelay(sess *ClientSession) time.Duration {
1117+func redialDelay(sess *clientSession) time.Duration {
1118 if sess.ShouldDelay() {
1119 t := sess.redialDelays[sess.redialDelaysIdx]
1120 if len(sess.redialDelays) > sess.redialDelaysIdx+1 {
1121@@ -178,8 +211,7 @@
1122
1123 func NewSession(serverAddrSpec string, conf ClientSessionConfig,
1124 deviceId string, seenStateFactory func() (seenstate.SeenState, error),
1125- log logger.Logger) (*ClientSession, error) {
1126- state := uint32(Disconnected)
1127+ log logger.Logger) (*clientSession, error) {
1128 seenState, err := seenStateFactory()
1129 if err != nil {
1130 return nil, err
1131@@ -191,7 +223,7 @@
1132 getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout)
1133 }
1134 var shouldDelay uint32 = 0
1135- sess := &ClientSession{
1136+ sess := &clientSession{
1137 ClientSessionConfig: conf,
1138 getHost: getHost,
1139 fallbackHosts: fallbackHosts,
1140@@ -200,10 +232,10 @@
1141 Protocolator: protocol.NewProtocol0,
1142 SeenState: seenState,
1143 TLS: &tls.Config{},
1144- stateP: &state,
1145+ state: Pristine,
1146 timeSince: time.Since,
1147 shouldDelayP: &shouldDelay,
1148- redialDelay: redialDelay,
1149+ redialDelay: redialDelay, // NOTE there are tests that use calling sess.redialDelay as an indication of calling autoRedial!
1150 redialDelays: util.Timeouts(),
1151 }
1152 sess.redialJitter = sess.Jitter
1153@@ -215,62 +247,78 @@
1154 }
1155 sess.TLS.RootCAs = cp
1156 }
1157+ sess.doneCh = make(chan uint32, 1)
1158+ sess.stopCh = make(chan struct{})
1159+ sess.cmdCh = make(chan sessCmd)
1160+ sess.errCh = make(chan error, 1)
1161+
1162+ // to be overridden by tests
1163+ sess.connHandler = sess.handleConn
1164+ sess.errHandler = sess.handleErr
1165+
1166 return sess, nil
1167 }
1168
1169-func (sess *ClientSession) ShouldDelay() bool {
1170+func (sess *clientSession) ShouldDelay() bool {
1171 return atomic.LoadUint32(sess.shouldDelayP) != 0
1172 }
1173
1174-func (sess *ClientSession) setShouldDelay() {
1175+func (sess *clientSession) setShouldDelay() {
1176 atomic.StoreUint32(sess.shouldDelayP, uint32(1))
1177 }
1178
1179-func (sess *ClientSession) clearShouldDelay() {
1180+func (sess *clientSession) clearShouldDelay() {
1181 atomic.StoreUint32(sess.shouldDelayP, uint32(0))
1182 }
1183
1184-func (sess *ClientSession) State() ClientSessionState {
1185- return ClientSessionState(atomic.LoadUint32(sess.stateP))
1186-}
1187-
1188-func (sess *ClientSession) setState(state ClientSessionState) {
1189- sess.Log.Debugf("session.setState: %s -> %s", ClientSessionState(atomic.LoadUint32(sess.stateP)), state)
1190- atomic.StoreUint32(sess.stateP, uint32(state))
1191-}
1192-
1193-func (sess *ClientSession) setConnection(conn net.Conn) {
1194+func (sess *clientSession) State() ClientSessionState {
1195+ sess.stateLock.RLock()
1196+ defer sess.stateLock.RUnlock()
1197+ return sess.state
1198+}
1199+
1200+func (sess *clientSession) setState(state ClientSessionState) {
1201+ sess.stateLock.Lock()
1202+ defer sess.stateLock.Unlock()
1203+ sess.Log.Debugf("session.setState: %s -> %s", sess.state, state)
1204+ sess.state = state
1205+}
1206+
1207+func (sess *clientSession) setConnection(conn net.Conn) {
1208 sess.connLock.Lock()
1209 defer sess.connLock.Unlock()
1210 sess.Connection = conn
1211 }
1212
1213-func (sess *ClientSession) getConnection() net.Conn {
1214+func (sess *clientSession) getConnection() net.Conn {
1215 sess.connLock.RLock()
1216 defer sess.connLock.RUnlock()
1217 return sess.Connection
1218 }
1219
1220-func (sess *ClientSession) setCookie(cookie string) {
1221+func (sess *clientSession) setCookie(cookie string) {
1222 sess.connLock.Lock()
1223 defer sess.connLock.Unlock()
1224 sess.cookie = cookie
1225 }
1226
1227-func (sess *ClientSession) getCookie() string {
1228+func (sess *clientSession) getCookie() string {
1229 sess.connLock.RLock()
1230 defer sess.connLock.RUnlock()
1231 return sess.cookie
1232 }
1233
1234-func (sess *ClientSession) ClearCookie() {
1235- sess.connLock.Lock()
1236- defer sess.connLock.Unlock()
1237- sess.cookie = ""
1238+func (sess *clientSession) ResetCookie() {
1239+ sess.cmdCh <- cmdResetCookie
1240+}
1241+
1242+func (sess *clientSession) resetCookie() {
1243+ sess.stopRedial()
1244+ sess.doClose(true)
1245 }
1246
1247 // getHosts sets deliveryHosts possibly querying a remote endpoint
1248-func (sess *ClientSession) getHosts() error {
1249+func (sess *clientSession) getHosts() error {
1250 if sess.getHost != nil {
1251 if sess.deliveryHosts != nil && sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime {
1252 return nil
1253@@ -294,7 +342,7 @@
1254
1255 // addAuthorization gets the authorization blob to send to the server
1256 // and adds it to the session.
1257-func (sess *ClientSession) addAuthorization() error {
1258+func (sess *clientSession) addAuthorization() error {
1259 if sess.AuthGetter != nil {
1260 sess.Log.Debugf("adding authorization")
1261 sess.auth = sess.AuthGetter(sess.AuthURL)
1262@@ -302,13 +350,13 @@
1263 return nil
1264 }
1265
1266-func (sess *ClientSession) resetHosts() {
1267+func (sess *clientSession) resetHosts() {
1268 sess.deliveryHosts = nil
1269 }
1270
1271 // startConnectionAttempt/nextHostToTry help connect iterating over candidate hosts
1272
1273-func (sess *ClientSession) startConnectionAttempt() {
1274+func (sess *clientSession) startConnectionAttempt() {
1275 if sess.timeSince(sess.lastAttemptTimestamp) > sess.ExpectAllRepairedTime {
1276 sess.tryHost = 0
1277 }
1278@@ -319,7 +367,7 @@
1279 sess.lastAttemptTimestamp = time.Now()
1280 }
1281
1282-func (sess *ClientSession) nextHostToTry() string {
1283+func (sess *clientSession) nextHostToTry() string {
1284 if sess.leftToTry == 0 {
1285 return ""
1286 }
1287@@ -331,7 +379,7 @@
1288
1289 // we reached the Started state, we can retry with the same host if we
1290 // have to retry again
1291-func (sess *ClientSession) started() {
1292+func (sess *clientSession) started() {
1293 sess.tryHost--
1294 if sess.tryHost == -1 {
1295 sess.tryHost = len(sess.deliveryHosts) - 1
1296@@ -341,7 +389,7 @@
1297
1298 // connect to a server using the configuration in the ClientSession
1299 // and set up the connection.
1300-func (sess *ClientSession) connect() error {
1301+func (sess *clientSession) connect() error {
1302 sess.setShouldDelay()
1303 sess.startConnectionAttempt()
1304 var err error
1305@@ -363,49 +411,47 @@
1306 return nil
1307 }
1308
1309-func (sess *ClientSession) stopRedial() {
1310- sess.retrierLock.Lock()
1311- defer sess.retrierLock.Unlock()
1312+func (sess *clientSession) stopRedial() {
1313 if sess.retrier != nil {
1314 sess.retrier.Stop()
1315 sess.retrier = nil
1316 }
1317 }
1318
1319-func (sess *ClientSession) AutoRedial(doneCh chan uint32) {
1320+func (sess *clientSession) autoRedial() {
1321 sess.stopRedial()
1322 if time.Since(sess.lastAutoRedial) < 2*time.Second {
1323 sess.setShouldDelay()
1324 }
1325- time.Sleep(sess.redialDelay(sess))
1326- sess.retrierLock.Lock()
1327- defer sess.retrierLock.Unlock()
1328+ // xxx should we really wait on the caller goroutine?
1329+ delay := sess.redialDelay(sess)
1330+ sess.Log.Debugf("session redial delay: %v, wait", delay)
1331+ time.Sleep(delay)
1332+ sess.Log.Debugf("session redial delay: %v, cont", delay)
1333 if sess.retrier != nil {
1334 panic("session AutoRedial: unexpected non-nil retrier.")
1335 }
1336 sess.retrier = util.NewAutoRedialer(sess)
1337 sess.lastAutoRedial = time.Now()
1338- go func() {
1339- sess.retrierLock.Lock()
1340- retrier := sess.retrier
1341- sess.retrierLock.Unlock()
1342- if retrier == nil {
1343- sess.Log.Debugf("session autoredialer skipping retry: retrier has been set to nil.")
1344- return
1345- }
1346+ go func(retrier util.AutoRedialer) {
1347 sess.Log.Debugf("session autoredialier launching Redial goroutine")
1348- doneCh <- retrier.Redial()
1349- }()
1350-}
1351-
1352-func (sess *ClientSession) Close() {
1353- sess.stopRedial()
1354- sess.doClose()
1355-}
1356-
1357-func (sess *ClientSession) doClose() {
1358+ // if the redialer has been stopped before calling Redial(), it'll return 0.
1359+ sess.doneCh <- retrier.Redial()
1360+ }(sess.retrier)
1361+}
1362+
1363+func (sess *clientSession) doClose(resetCookie bool) {
1364 sess.connLock.Lock()
1365 defer sess.connLock.Unlock()
1366+ if resetCookie {
1367+ sess.cookie = ""
1368+ }
1369+ sess.closeConnection()
1370+ sess.setState(Disconnected)
1371+}
1372+
1373+func (sess *clientSession) closeConnection() {
1374+ // *must be called with connLock held*
1375 if sess.Connection != nil {
1376 sess.Connection.Close()
1377 // we ignore Close errors, on purpose (the thinking being that
1378@@ -413,11 +459,10 @@
1379 // you could do to recover at this stage).
1380 sess.Connection = nil
1381 }
1382- sess.setState(Disconnected)
1383 }
1384
1385 // handle "ping" messages
1386-func (sess *ClientSession) handlePing() error {
1387+func (sess *clientSession) handlePing() error {
1388 err := sess.proto.WriteMessage(protocol.PingPongMsg{Type: "pong"})
1389 if err == nil {
1390 sess.Log.Debugf("ping.")
1391@@ -429,7 +474,7 @@
1392 return err
1393 }
1394
1395-func (sess *ClientSession) decodeBroadcast(bcast *serverMsg) *BroadcastNotification {
1396+func (sess *clientSession) decodeBroadcast(bcast *serverMsg) *BroadcastNotification {
1397 decoded := make([]map[string]interface{}, 0)
1398 for _, p := range bcast.Payloads {
1399 var v map[string]interface{}
1400@@ -447,7 +492,7 @@
1401 }
1402
1403 // handle "broadcast" messages
1404-func (sess *ClientSession) handleBroadcast(bcast *serverMsg) error {
1405+func (sess *clientSession) handleBroadcast(bcast *serverMsg) error {
1406 err := sess.SeenState.SetLevel(bcast.ChanId, bcast.TopLevel)
1407 if err != nil {
1408 sess.setState(Error)
1409@@ -478,7 +523,7 @@
1410 }
1411
1412 // handle "notifications" messages
1413-func (sess *ClientSession) handleNotifications(ucast *serverMsg) error {
1414+func (sess *clientSession) handleNotifications(ucast *serverMsg) error {
1415 notifs, err := sess.SeenState.FilterBySeen(ucast.Notifications)
1416 if err != nil {
1417 sess.setState(Error)
1418@@ -512,7 +557,7 @@
1419 }
1420
1421 // handle "connbroken" messages
1422-func (sess *ClientSession) handleConnBroken(connBroken *serverMsg) error {
1423+func (sess *clientSession) handleConnBroken(connBroken *serverMsg) error {
1424 sess.setState(Error)
1425 reason := connBroken.Reason
1426 err := fmt.Errorf("server broke connection: %s", reason)
1427@@ -525,7 +570,7 @@
1428 }
1429
1430 // handle "setparams" messages
1431-func (sess *ClientSession) handleSetParams(setParams *serverMsg) error {
1432+func (sess *clientSession) handleSetParams(setParams *serverMsg) error {
1433 if setParams.SetCookie != "" {
1434 sess.setCookie(setParams.SetCookie)
1435 }
1436@@ -533,7 +578,7 @@
1437 }
1438
1439 // loop runs the session with the server, emits a stream of events.
1440-func (sess *ClientSession) loop() error {
1441+func (sess *clientSession) loop() error {
1442 var err error
1443 var recv serverMsg
1444 sess.setState(Running)
1445@@ -571,7 +616,7 @@
1446 }
1447
1448 // Call this when you've connected and want to start looping.
1449-func (sess *ClientSession) start() error {
1450+func (sess *clientSession) start() error {
1451 conn := sess.getConnection()
1452 err := conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout))
1453 if err != nil {
1454@@ -634,8 +679,8 @@
1455
1456 // run calls connect, and if it works it calls start, and if it works
1457 // it runs loop in a goroutine, and ships its return value over ErrCh.
1458-func (sess *ClientSession) run(closer func(), authChecker, hostGetter, connecter, starter, looper func() error) error {
1459- closer()
1460+func (sess *clientSession) run(closer func(bool), authChecker, hostGetter, connecter, starter, looper func() error) error {
1461+ closer(false)
1462 if err := authChecker(); err != nil {
1463 return err
1464 }
1465@@ -646,17 +691,14 @@
1466 if err == nil {
1467 err = starter()
1468 if err == nil {
1469- sess.ErrCh = make(chan error, 1)
1470- sess.BroadcastCh = make(chan *BroadcastNotification)
1471- sess.NotificationsCh = make(chan AddressedNotification)
1472- go func() { sess.ErrCh <- looper() }()
1473+ go func() { sess.errCh <- looper() }()
1474 }
1475 }
1476 return err
1477 }
1478
1479 // This Jitter returns a random time.Duration somewhere in [-spread, spread].
1480-func (sess *ClientSession) Jitter(spread time.Duration) time.Duration {
1481+func (sess *clientSession) Jitter(spread time.Duration) time.Duration {
1482 if spread < 0 {
1483 panic("spread must be non-negative")
1484 }
1485@@ -666,7 +708,7 @@
1486
1487 // Dial takes the session from newly created (or newly disconnected)
1488 // to running the main loop.
1489-func (sess *ClientSession) Dial() error {
1490+func (sess *clientSession) Dial() error {
1491 if sess.Protocolator == nil {
1492 // a missing protocolator means you've willfully overridden
1493 // it; returning an error here would prompt AutoRedial to just
1494@@ -676,6 +718,90 @@
1495 return sess.run(sess.doClose, sess.addAuthorization, sess.getHosts, sess.connect, sess.start, sess.loop)
1496 }
1497
1498+func (sess *clientSession) shutdown() {
1499+ sess.Log.Infof("session shutting down.")
1500+ sess.connLock.Lock()
1501+ defer sess.connLock.Unlock()
1502+ sess.stopRedial()
1503+ sess.closeConnection()
1504+}
1505+
1506+func (sess *clientSession) doKeepConnection() {
1507+ for {
1508+ select {
1509+ case cmd := <-sess.cmdCh:
1510+ switch cmd {
1511+ case cmdConnect:
1512+ sess.connHandler(true)
1513+ case cmdDisconnect:
1514+ sess.connHandler(false)
1515+ case cmdResetCookie:
1516+ sess.resetCookie()
1517+ }
1518+ case <-sess.stopCh:
1519+ sess.shutdown()
1520+ return
1521+ case n := <-sess.doneCh:
1522+ // if n == 0, the redialer aborted. If you do
1523+ // anything other than log it, keep that in mind.
1524+ sess.Log.Debugf("connected after %d attempts.", n)
1525+ case err := <-sess.errCh:
1526+ sess.errHandler(err)
1527+ }
1528+ }
1529+}
1530+
1531+func (sess *clientSession) handleConn(hasConn bool) {
1532+ sess.lastConn = hasConn
1533+
1534+ // Note this does not depend on the current state! That's because Dial
1535+ // starts with doClose, which gets you to Disconnected even if you're
1536+ // connected, and you can call Close when Disconnected without it
1537+ // losing its stuff.
1538+ if hasConn {
1539+ sess.autoRedial()
1540+ } else {
1541+ sess.stopRedial()
1542+ sess.doClose(false)
1543+ }
1544+}
1545+
1546+func (sess *clientSession) handleErr(err error) {
1547+ sess.Log.Errorf("session error'ed out with %v", err)
1548+ // State() == Error mostly defends interrupting an ongoing
1549+ // autoRedial if we went quickly already through hasConn =
1550+ // false => hasConn = true
1551+ if sess.State() == Error && sess.lastConn {
1552+ sess.autoRedial()
1553+ }
1554+}
1555+
1556+func (sess *clientSession) KeepConnection() error {
1557+ sess.stateLock.Lock()
1558+ defer sess.stateLock.Unlock()
1559+ if sess.state != Pristine {
1560+ return errors.New("don't call KeepConnection() on a non-pristine session.")
1561+ }
1562+ sess.state = Disconnected
1563+
1564+ go sess.doKeepConnection()
1565+
1566+ return nil
1567+}
1568+
1569+func (sess *clientSession) StopKeepConnection() {
1570+ sess.setState(Shutdown)
1571+ close(sess.stopCh)
1572+}
1573+
1574+func (sess *clientSession) HasConnectivity(hasConn bool) {
1575+ if hasConn {
1576+ sess.cmdCh <- cmdConnect
1577+ } else {
1578+ sess.cmdCh <- cmdDisconnect
1579+ }
1580+}
1581+
1582 func init() {
1583 rand.Seed(time.Now().Unix()) // good enough for us (we're not using it for crypto)
1584 }
1585
1586=== modified file 'client/session/session_test.go'
1587--- client/session/session_test.go 2015-02-02 16:49:46 +0000
1588+++ client/session/session_test.go 2015-04-02 09:52:21 +0000
1589@@ -190,6 +190,24 @@
1590 cs.lvls = func() (seenstate.SeenState, error) { return seenstate.NewSqliteSeenState(":memory:") }
1591 }
1592
1593+func (cs *clientSessionSuite) TestStateString(c *C) {
1594+ for _, i := range []struct {
1595+ v ClientSessionState
1596+ s string
1597+ }{
1598+ {Error, "Error"},
1599+ {Pristine, "Pristine"},
1600+ {Disconnected, "Disconnected"},
1601+ {Connected, "Connected"},
1602+ {Started, "Started"},
1603+ {Running, "Running"},
1604+ {Shutdown, "Shutdown"},
1605+ {Unknown, fmt.Sprintf("??? (%d)", Unknown)},
1606+ } {
1607+ c.Check(i.v.String(), Equals, i.s)
1608+ }
1609+}
1610+
1611 /****************************************************************
1612 parseServerAddrSpec() tests
1613 ****************************************************************/
1614@@ -212,10 +230,15 @@
1615 NewSession() tests
1616 ****************************************************************/
1617
1618-var dummyConf = ClientSessionConfig{}
1619+func dummyConf() ClientSessionConfig {
1620+ return ClientSessionConfig{
1621+ BroadcastCh: make(chan *BroadcastNotification, 5),
1622+ NotificationsCh: make(chan AddressedNotification, 5),
1623+ }
1624+}
1625
1626 func (cs *clientSessionSuite) TestNewSessionPlainWorks(c *C) {
1627- sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
1628+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
1629 c.Check(sess, NotNil)
1630 c.Check(err, IsNil)
1631 c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"})
1632@@ -225,11 +248,13 @@
1633 c.Check(sess.redialDelays, DeepEquals, util.Timeouts())
1634 // but no root CAs set
1635 c.Check(sess.TLS.RootCAs, IsNil)
1636- c.Check(sess.State(), Equals, Disconnected)
1637+ c.Check(sess.State(), Equals, Pristine)
1638+ c.Check(sess.stopCh, NotNil)
1639+ c.Check(sess.cmdCh, NotNil)
1640 }
1641
1642 func (cs *clientSessionSuite) TestNewSessionHostEndpointWorks(c *C) {
1643- sess, err := NewSession("http://foo/hosts", dummyConf, "wah", cs.lvls, cs.log)
1644+ sess, err := NewSession("http://foo/hosts", dummyConf(), "wah", cs.lvls, cs.log)
1645 c.Assert(err, IsNil)
1646 c.Check(sess.getHost, NotNil)
1647 }
1648@@ -255,7 +280,7 @@
1649
1650 func (cs *clientSessionSuite) TestNewSessionBadSeenStateFails(c *C) {
1651 ferr := func() (seenstate.SeenState, error) { return nil, errors.New("Busted.") }
1652- sess, err := NewSession("", dummyConf, "wah", ferr, cs.log)
1653+ sess, err := NewSession("", dummyConf(), "wah", ferr, cs.log)
1654 c.Check(sess, IsNil)
1655 c.Assert(err, NotNil)
1656 }
1657@@ -266,7 +291,7 @@
1658
1659 func (cs *clientSessionSuite) TestGetHostsFallback(c *C) {
1660 fallback := []string{"foo:443", "bar:443"}
1661- sess := &ClientSession{fallbackHosts: fallback}
1662+ sess := &clientSession{fallbackHosts: fallback}
1663 err := sess.getHosts()
1664 c.Assert(err, IsNil)
1665 c.Check(sess.deliveryHosts, DeepEquals, fallback)
1666@@ -284,14 +309,14 @@
1667
1668 func (cs *clientSessionSuite) TestGetHostsRemote(c *C) {
1669 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
1670- sess := &ClientSession{getHost: hostGetter, timeSince: time.Since}
1671+ sess := &clientSession{getHost: hostGetter, timeSince: time.Since}
1672 err := sess.getHosts()
1673 c.Assert(err, IsNil)
1674 c.Check(sess.deliveryHosts, DeepEquals, []string{"foo:443", "bar:443"})
1675 }
1676
1677 func (cs *clientSessionSuite) TestGetHostsRemoteError(c *C) {
1678- sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log)
1679+ sess, err := NewSession("", dummyConf(), "", cs.lvls, cs.log)
1680 c.Assert(err, IsNil)
1681 hostsErr := errors.New("failed")
1682 hostGetter := &testHostGetter{"", nil, hostsErr}
1683@@ -304,7 +329,7 @@
1684
1685 func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) {
1686 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
1687- sess := &ClientSession{
1688+ sess := &clientSession{
1689 getHost: hostGetter,
1690 ClientSessionConfig: ClientSessionConfig{
1691 HostsCachingExpiryTime: 2 * time.Hour,
1692@@ -329,7 +354,7 @@
1693
1694 func (cs *clientSessionSuite) TestGetHostsRemoteCachingReset(c *C) {
1695 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
1696- sess := &ClientSession{
1697+ sess := &clientSession{
1698 getHost: hostGetter,
1699 ClientSessionConfig: ClientSessionConfig{
1700 HostsCachingExpiryTime: 2 * time.Hour,
1701@@ -356,7 +381,7 @@
1702
1703 func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) {
1704 url := "xyzzy://"
1705- sess := &ClientSession{Log: cs.log}
1706+ sess := &clientSession{Log: cs.log}
1707 sess.AuthGetter = func(url string) string { return url + " auth'ed" }
1708 sess.AuthURL = url
1709 c.Assert(sess.auth, Equals, "")
1710@@ -366,7 +391,7 @@
1711 }
1712
1713 func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnset(c *C) {
1714- sess := &ClientSession{Log: cs.log}
1715+ sess := &clientSession{Log: cs.log}
1716 sess.AuthGetter = nil
1717 c.Assert(sess.auth, Equals, "")
1718 err := sess.addAuthorization()
1719@@ -380,7 +405,7 @@
1720
1721 func (cs *clientSessionSuite) TestStartConnectionAttempt(c *C) {
1722 since := time.Since(time.Time{})
1723- sess := &ClientSession{
1724+ sess := &clientSession{
1725 ClientSessionConfig: ClientSessionConfig{
1726 ExpectAllRepairedTime: 10 * time.Second,
1727 },
1728@@ -404,7 +429,7 @@
1729
1730 func (cs *clientSessionSuite) TestStartConnectionAttemptNoHostsPanic(c *C) {
1731 since := time.Since(time.Time{})
1732- sess := &ClientSession{
1733+ sess := &clientSession{
1734 ClientSessionConfig: ClientSessionConfig{
1735 ExpectAllRepairedTime: 10 * time.Second,
1736 },
1737@@ -416,7 +441,7 @@
1738 }
1739
1740 func (cs *clientSessionSuite) TestNextHostToTry(c *C) {
1741- sess := &ClientSession{
1742+ sess := &clientSession{
1743 deliveryHosts: []string{"foo:443", "bar:443", "baz:443"},
1744 tryHost: 0,
1745 leftToTry: 3,
1746@@ -439,7 +464,7 @@
1747 }
1748
1749 func (cs *clientSessionSuite) TestStarted(c *C) {
1750- sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log)
1751+ sess, err := NewSession("", dummyConf(), "", cs.lvls, cs.log)
1752 c.Assert(err, IsNil)
1753
1754 sess.deliveryHosts = []string{"foo:443", "bar:443", "baz:443"}
1755@@ -458,7 +483,7 @@
1756 ****************************************************************/
1757
1758 func (cs *clientSessionSuite) TestConnectFailsWithNoAddress(c *C) {
1759- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
1760+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1761 c.Assert(err, IsNil)
1762 sess.deliveryHosts = []string{"nowhere"}
1763 sess.clearShouldDelay()
1764@@ -472,7 +497,7 @@
1765 srv, err := net.Listen("tcp", "localhost:0")
1766 c.Assert(err, IsNil)
1767 defer srv.Close()
1768- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
1769+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1770 c.Assert(err, IsNil)
1771 sess.deliveryHosts = []string{srv.Addr().String()}
1772 sess.clearShouldDelay()
1773@@ -487,7 +512,7 @@
1774 srv, err := net.Listen("tcp", "localhost:0")
1775 c.Assert(err, IsNil)
1776 defer srv.Close()
1777- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
1778+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1779 c.Assert(err, IsNil)
1780 sess.deliveryHosts = []string{"nowhere", srv.Addr().String()}
1781 sess.clearShouldDelay()
1782@@ -502,7 +527,7 @@
1783 func (cs *clientSessionSuite) TestConnectConnectFail(c *C) {
1784 srv, err := net.Listen("tcp", "localhost:0")
1785 c.Assert(err, IsNil)
1786- sess, err := NewSession(srv.Addr().String(), dummyConf, "wah", cs.lvls, cs.log)
1787+ sess, err := NewSession(srv.Addr().String(), dummyConf(), "wah", cs.lvls, cs.log)
1788 srv.Close()
1789 c.Assert(err, IsNil)
1790 sess.deliveryHosts = []string{srv.Addr().String()}
1791@@ -513,101 +538,58 @@
1792 c.Check(sess.State(), Equals, Error)
1793 }
1794
1795-/****************************************************************
1796- Close() tests
1797-****************************************************************/
1798-
1799-func (cs *clientSessionSuite) TestClose(c *C) {
1800- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
1801- c.Assert(err, IsNil)
1802- sess.Connection = &testConn{Name: "TestClose"}
1803- sess.Close()
1804- c.Check(sess.Connection, IsNil)
1805- c.Check(sess.State(), Equals, Disconnected)
1806-}
1807-
1808-func (cs *clientSessionSuite) TestCloseTwice(c *C) {
1809- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
1810- c.Assert(err, IsNil)
1811- sess.Connection = &testConn{Name: "TestCloseTwice"}
1812- sess.Close()
1813- c.Check(sess.Connection, IsNil)
1814- sess.Close()
1815- c.Check(sess.Connection, IsNil)
1816- c.Check(sess.State(), Equals, Disconnected)
1817-}
1818-
1819-func (cs *clientSessionSuite) TestCloseFails(c *C) {
1820- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
1821- c.Assert(err, IsNil)
1822- sess.Connection = &testConn{Name: "TestCloseFails", CloseCondition: condition.Work(false)}
1823- sess.Close()
1824- c.Check(sess.Connection, IsNil) // nothing you can do to clean up anyway
1825- c.Check(sess.State(), Equals, Disconnected)
1826-}
1827-
1828-type derp struct{ stopped bool }
1829-
1830-func (*derp) Redial() uint32 { return 0 }
1831-func (d *derp) Stop() { d.stopped = true }
1832-
1833-func (cs *clientSessionSuite) TestCloseStopsRetrier(c *C) {
1834- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
1835- c.Assert(err, IsNil)
1836- ar := new(derp)
1837- sess.retrier = ar
1838- c.Check(ar.stopped, Equals, false)
1839- sess.Close()
1840- c.Check(ar.stopped, Equals, true)
1841- sess.Close() // double close check
1842- c.Check(ar.stopped, Equals, true)
1843-}
1844-
1845-/****************************************************************
1846- AutoRedial() tests
1847-****************************************************************/
1848+type dumbRetrier struct{ stopped bool }
1849+
1850+func (*dumbRetrier) Redial() uint32 { return 0 }
1851+func (d *dumbRetrier) Stop() { d.stopped = true }
1852+
1853+// /****************************************************************
1854+// AutoRedial() tests
1855+// ****************************************************************/
1856
1857 func (cs *clientSessionSuite) TestAutoRedialWorks(c *C) {
1858 // checks that AutoRedial sets up a retrier and tries redialing it
1859- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
1860+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1861 c.Assert(err, IsNil)
1862- ar := new(derp)
1863+ ar := new(dumbRetrier)
1864 sess.retrier = ar
1865 c.Check(ar.stopped, Equals, false)
1866- sess.AutoRedial(nil)
1867+ sess.autoRedial()
1868+ defer sess.stopRedial()
1869 c.Check(ar.stopped, Equals, true)
1870 }
1871
1872 func (cs *clientSessionSuite) TestAutoRedialStopsRetrier(c *C) {
1873 // checks that AutoRedial stops the previous retrier
1874- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
1875+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1876 c.Assert(err, IsNil)
1877- ch := make(chan uint32)
1878+ sess.doneCh = make(chan uint32)
1879 c.Check(sess.retrier, IsNil)
1880- sess.AutoRedial(ch)
1881+ sess.autoRedial()
1882 c.Assert(sess.retrier, NotNil)
1883 sess.retrier.Stop()
1884- c.Check(<-ch, Not(Equals), 0)
1885+ c.Check(<-sess.doneCh, Not(Equals), 0)
1886 }
1887
1888 func (cs *clientSessionSuite) TestAutoRedialCallsRedialDelay(c *C) {
1889- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
1890+ // NOTE there are tests that use calling redialDelay as an indication of calling autoRedial!
1891+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1892 c.Assert(err, IsNil)
1893 flag := false
1894- sess.redialDelay = func(sess *ClientSession) time.Duration { flag = true; return 0 }
1895- sess.AutoRedial(nil)
1896+ sess.redialDelay = func(sess *clientSession) time.Duration { flag = true; return 0 }
1897+ sess.autoRedial()
1898 c.Check(flag, Equals, true)
1899 }
1900
1901 func (cs *clientSessionSuite) TestAutoRedialSetsRedialDelayIfTooQuick(c *C) {
1902- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
1903+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1904 c.Assert(err, IsNil)
1905- sess.redialDelay = func(sess *ClientSession) time.Duration { return 0 }
1906- sess.AutoRedial(nil)
1907+ sess.redialDelay = func(sess *clientSession) time.Duration { return 0 }
1908+ sess.autoRedial()
1909 c.Check(sess.ShouldDelay(), Equals, false)
1910 sess.stopRedial()
1911 sess.clearShouldDelay()
1912- sess.AutoRedial(nil)
1913+ sess.autoRedial()
1914 c.Check(sess.ShouldDelay(), Equals, true)
1915 }
1916
1917@@ -616,29 +598,23 @@
1918 ****************************************************************/
1919
1920 type msgSuite struct {
1921- sess *ClientSession
1922+ sess *clientSession
1923 upCh chan interface{}
1924 downCh chan interface{}
1925- errCh chan error
1926 }
1927
1928 var _ = Suite(&msgSuite{})
1929
1930 func (s *msgSuite) SetUpTest(c *C) {
1931 var err error
1932- conf := ClientSessionConfig{
1933- ExchangeTimeout: time.Millisecond,
1934- }
1935+ conf := dummyConf()
1936+ conf.ExchangeTimeout = time.Millisecond
1937 s.sess, err = NewSession("", conf, "wah", seenstate.NewSeenState, helpers.NewTestLogger(c, "debug"))
1938 c.Assert(err, IsNil)
1939 s.sess.Connection = &testConn{Name: "TestHandle*"}
1940- s.errCh = make(chan error, 1)
1941 s.upCh = make(chan interface{}, 5)
1942 s.downCh = make(chan interface{}, 5)
1943 s.sess.proto = &testProtocol{up: s.upCh, down: s.downCh}
1944- // make the message channel buffered
1945- s.sess.BroadcastCh = make(chan *BroadcastNotification, 5)
1946- s.sess.NotificationsCh = make(chan AddressedNotification, 5)
1947 }
1948
1949 func (s *msgSuite) TestHandlePingWorks(c *C) {
1950@@ -694,10 +670,10 @@
1951 json.RawMessage(`{"img1/m1":[102,"tubular"]}`),
1952 },
1953 }
1954- go func() { s.errCh <- s.sess.handleBroadcast(msg) }()
1955+ go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
1956 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
1957 s.upCh <- nil // ack ok
1958- c.Check(<-s.errCh, Equals, nil)
1959+ c.Check(<-s.sess.errCh, Equals, nil)
1960 c.Assert(len(s.sess.BroadcastCh), Equals, 1)
1961 c.Check(<-s.sess.BroadcastCh, DeepEquals, &BroadcastNotification{
1962 TopLevel: 2,
1963@@ -726,11 +702,11 @@
1964 TopLevel: 2,
1965 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},
1966 }
1967- go func() { s.errCh <- s.sess.handleBroadcast(msg) }()
1968+ go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
1969 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
1970 failure := errors.New("ACK ACK ACK")
1971 s.upCh <- failure
1972- c.Assert(<-s.errCh, Equals, failure)
1973+ c.Assert(<-s.sess.errCh, Equals, failure)
1974 c.Check(s.sess.State(), Equals, Error)
1975 }
1976
1977@@ -744,10 +720,10 @@
1978 TopLevel: 2,
1979 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},
1980 }
1981- go func() { s.errCh <- s.sess.handleBroadcast(msg) }()
1982+ go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
1983 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
1984 s.upCh <- nil // ack ok
1985- c.Check(<-s.errCh, IsNil)
1986+ c.Check(<-s.sess.errCh, IsNil)
1987 c.Check(len(s.sess.BroadcastCh), Equals, 0)
1988 }
1989
1990@@ -762,10 +738,10 @@
1991 TopLevel: 2,
1992 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},
1993 }
1994- go func() { s.errCh <- s.sess.handleBroadcast(msg) }()
1995+ go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
1996 s.upCh <- nil // ack ok
1997 // start returns with error
1998- c.Check(<-s.errCh, Not(Equals), nil)
1999+ c.Check(<-s.sess.errCh, Not(Equals), nil)
2000 c.Check(s.sess.State(), Equals, Error)
2001 // no message sent out
2002 c.Check(len(s.sess.BroadcastCh), Equals, 0)
2003@@ -778,10 +754,10 @@
2004 s.sess.setShouldDelay()
2005
2006 msg := &serverMsg{Type: "broadcast"}
2007- go func() { s.errCh <- s.sess.handleBroadcast(msg) }()
2008+ go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
2009 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
2010 s.upCh <- nil // ack ok
2011- c.Check(<-s.errCh, IsNil)
2012+ c.Check(<-s.sess.errCh, IsNil)
2013
2014 c.Check(s.sess.ShouldDelay(), Equals, false)
2015 }
2016@@ -790,10 +766,10 @@
2017 s.sess.setShouldDelay()
2018
2019 msg := &serverMsg{Type: "broadcast"}
2020- go func() { s.errCh <- s.sess.handleBroadcast(msg) }()
2021+ go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
2022 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
2023 s.upCh <- errors.New("bcast")
2024- c.Check(<-s.errCh, NotNil)
2025+ c.Check(<-s.sess.errCh, NotNil)
2026
2027 c.Check(s.sess.ShouldDelay(), Equals, true)
2028 }
2029@@ -843,10 +819,10 @@
2030 msg.NotificationsMsg = protocol.NotificationsMsg{
2031 Notifications: []protocol.Notification{n1, n2},
2032 }
2033- go func() { s.errCh <- s.sess.handleNotifications(msg) }()
2034+ go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
2035 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
2036 s.upCh <- nil // ack ok
2037- c.Check(<-s.errCh, Equals, nil)
2038+ c.Check(<-s.sess.errCh, Equals, nil)
2039 c.Check(s.sess.ShouldDelay(), Equals, false)
2040 c.Assert(s.sess.NotificationsCh, HasLen, 2)
2041 app1, err := click.ParseAppId("com.example.app1_app1")
2042@@ -889,10 +865,10 @@
2043 msg.NotificationsMsg = protocol.NotificationsMsg{
2044 Notifications: []protocol.Notification{n1, n2},
2045 }
2046- go func() { s.errCh <- s.sess.handleNotifications(msg) }()
2047+ go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
2048 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
2049 s.upCh <- nil // ack ok
2050- c.Check(<-s.errCh, Equals, nil)
2051+ c.Check(<-s.sess.errCh, Equals, nil)
2052 c.Check(s.sess.ShouldDelay(), Equals, false)
2053 c.Assert(s.sess.NotificationsCh, HasLen, 1)
2054 app2, err := click.ParseAppId("com.example.app2_app2")
2055@@ -924,10 +900,10 @@
2056 msg.NotificationsMsg = protocol.NotificationsMsg{
2057 Notifications: []protocol.Notification{n1, n2},
2058 }
2059- go func() { s.errCh <- s.sess.handleNotifications(msg) }()
2060+ go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
2061 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
2062 s.upCh <- nil // ack ok
2063- c.Check(<-s.errCh, Equals, nil)
2064+ c.Check(<-s.sess.errCh, Equals, nil)
2065 c.Assert(s.sess.NotificationsCh, HasLen, 2)
2066 app1, err := click.ParseAppId("com.example.app1_app1")
2067 c.Assert(err, IsNil)
2068@@ -944,10 +920,10 @@
2069 c.Check(ac.ops, HasLen, 3)
2070
2071 // second time they get ignored
2072- go func() { s.errCh <- s.sess.handleNotifications(msg) }()
2073+ go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
2074 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
2075 s.upCh <- nil // ack ok
2076- c.Check(<-s.errCh, Equals, nil)
2077+ c.Check(<-s.sess.errCh, Equals, nil)
2078 c.Assert(s.sess.NotificationsCh, HasLen, 0)
2079 c.Check(ac.ops, HasLen, 4)
2080 }
2081@@ -964,11 +940,11 @@
2082 msg.NotificationsMsg = protocol.NotificationsMsg{
2083 Notifications: []protocol.Notification{n1},
2084 }
2085- go func() { s.errCh <- s.sess.handleNotifications(msg) }()
2086+ go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
2087 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
2088 failure := errors.New("ACK ACK ACK")
2089 s.upCh <- failure
2090- c.Assert(<-s.errCh, Equals, failure)
2091+ c.Assert(<-s.sess.errCh, Equals, failure)
2092 c.Check(s.sess.State(), Equals, Error)
2093 // didn't get to clear
2094 c.Check(s.sess.ShouldDelay(), Equals, true)
2095@@ -987,10 +963,10 @@
2096 msg.NotificationsMsg = protocol.NotificationsMsg{
2097 Notifications: []protocol.Notification{n1},
2098 }
2099- go func() { s.errCh <- s.sess.handleNotifications(msg) }()
2100+ go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
2101 s.upCh <- nil // ack ok
2102 // start returns with error
2103- c.Check(<-s.errCh, Not(Equals), nil)
2104+ c.Check(<-s.sess.errCh, Not(Equals), nil)
2105 c.Check(s.sess.State(), Equals, Error)
2106 // no message sent out
2107 c.Check(len(s.sess.NotificationsCh), Equals, 0)
2108@@ -1011,8 +987,8 @@
2109 msg.ConnBrokenMsg = protocol.ConnBrokenMsg{
2110 Reason: "REASON",
2111 }
2112- go func() { s.errCh <- s.sess.handleConnBroken(msg) }()
2113- c.Check(<-s.errCh, ErrorMatches, "server broke connection: REASON")
2114+ go func() { s.sess.errCh <- s.sess.handleConnBroken(msg) }()
2115+ c.Check(<-s.sess.errCh, ErrorMatches, "server broke connection: REASON")
2116 c.Check(s.sess.State(), Equals, Error)
2117 }
2118
2119@@ -1023,8 +999,8 @@
2120 Reason: protocol.BrokenHostMismatch,
2121 }
2122 s.sess.deliveryHosts = []string{"foo:443", "bar:443"}
2123- go func() { s.errCh <- s.sess.handleConnBroken(msg) }()
2124- c.Check(<-s.errCh, ErrorMatches, "server broke connection: host-mismatch")
2125+ go func() { s.sess.errCh <- s.sess.handleConnBroken(msg) }()
2126+ c.Check(<-s.sess.errCh, ErrorMatches, "server broke connection: host-mismatch")
2127 c.Check(s.sess.State(), Equals, Error)
2128 // hosts were reset
2129 c.Check(s.sess.deliveryHosts, IsNil)
2130@@ -1042,14 +1018,14 @@
2131 (*msgSuite)(s).SetUpTest(c)
2132 s.sess.Connection.(*testConn).Name = "TestLoop*"
2133 go func() {
2134- s.errCh <- s.sess.loop()
2135+ s.sess.errCh <- s.sess.loop()
2136 }()
2137 }
2138
2139 func (s *loopSuite) TestLoopReadError(c *C) {
2140 c.Check(s.sess.State(), Equals, Running)
2141 s.upCh <- errors.New("Read")
2142- err := <-s.errCh
2143+ err := <-s.sess.errCh
2144 c.Check(err, ErrorMatches, "Read")
2145 c.Check(s.sess.State(), Equals, Error)
2146 }
2147@@ -1061,7 +1037,7 @@
2148 c.Check(takeNext(s.downCh), Equals, protocol.PingPongMsg{Type: "pong"})
2149 failure := errors.New("pong")
2150 s.upCh <- failure
2151- c.Check(<-s.errCh, Equals, failure)
2152+ c.Check(<-s.sess.errCh, Equals, failure)
2153 }
2154
2155 func (s *loopSuite) TestLoopLoopsDaLoop(c *C) {
2156@@ -1074,7 +1050,7 @@
2157 }
2158 failure := errors.New("pong")
2159 s.upCh <- failure
2160- c.Check(<-s.errCh, Equals, failure)
2161+ c.Check(<-s.sess.errCh, Equals, failure)
2162 }
2163
2164 func (s *loopSuite) TestLoopBroadcast(c *C) {
2165@@ -1091,7 +1067,7 @@
2166 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
2167 failure := errors.New("ack")
2168 s.upCh <- failure
2169- c.Check(<-s.errCh, Equals, failure)
2170+ c.Check(<-s.sess.errCh, Equals, failure)
2171 }
2172
2173 func (s *loopSuite) TestLoopNotifications(c *C) {
2174@@ -1111,7 +1087,7 @@
2175 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
2176 failure := errors.New("ack")
2177 s.upCh <- failure
2178- c.Check(<-s.errCh, Equals, failure)
2179+ c.Check(<-s.sess.errCh, Equals, failure)
2180 }
2181
2182 func (s *loopSuite) TestLoopSetParams(c *C) {
2183@@ -1124,7 +1100,7 @@
2184 s.upCh <- setParams
2185 failure := errors.New("fail")
2186 s.upCh <- failure
2187- c.Assert(<-s.errCh, Equals, failure)
2188+ c.Assert(<-s.sess.errCh, Equals, failure)
2189 c.Check(s.sess.getCookie(), Equals, "COOKIE")
2190 }
2191
2192@@ -1136,7 +1112,7 @@
2193 }
2194 c.Check(takeNext(s.downCh), Equals, "deadline 1ms")
2195 s.upCh <- broken
2196- c.Check(<-s.errCh, NotNil)
2197+ c.Check(<-s.sess.errCh, NotNil)
2198 }
2199
2200 func (s *loopSuite) TestLoopConnWarn(c *C) {
2201@@ -1157,7 +1133,7 @@
2202 s.upCh <- warn
2203 s.upCh <- connwarn
2204 s.upCh <- failure
2205- c.Check(<-s.errCh, Equals, failure)
2206+ c.Check(<-s.sess.errCh, Equals, failure)
2207 c.Check(log.Captured(),
2208 Matches, `(?ms).* warning: XXX$.*`)
2209 c.Check(log.Captured(),
2210@@ -1168,7 +1144,7 @@
2211 start() tests
2212 ****************************************************************/
2213 func (cs *clientSessionSuite) TestStartFailsIfSetDeadlineFails(c *C) {
2214- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2215+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2216 c.Assert(err, IsNil)
2217 sess.Connection = &testConn{Name: "TestStartFailsIfSetDeadlineFails",
2218 DeadlineCondition: condition.Work(false)} // setdeadline will fail
2219@@ -1178,7 +1154,7 @@
2220 }
2221
2222 func (cs *clientSessionSuite) TestStartFailsIfWriteFails(c *C) {
2223- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2224+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2225 c.Assert(err, IsNil)
2226 sess.Connection = &testConn{Name: "TestStartFailsIfWriteFails",
2227 WriteCondition: condition.Work(false)} // write will fail
2228@@ -1188,7 +1164,7 @@
2229 }
2230
2231 func (cs *clientSessionSuite) TestStartFailsIfGetLevelsFails(c *C) {
2232- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2233+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2234 c.Assert(err, IsNil)
2235 sess.SeenState = &brokenSeenState{}
2236 sess.Connection = &testConn{Name: "TestStartConnectMessageFails"}
2237@@ -1208,7 +1184,7 @@
2238 }
2239
2240 func (cs *clientSessionSuite) TestStartConnectMessageFails(c *C) {
2241- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2242+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2243 c.Assert(err, IsNil)
2244 sess.Connection = &testConn{Name: "TestStartConnectMessageFails"}
2245 errCh := make(chan error, 1)
2246@@ -1235,7 +1211,7 @@
2247 }
2248
2249 func (cs *clientSessionSuite) TestStartConnackReadError(c *C) {
2250- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2251+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2252 c.Assert(err, IsNil)
2253 sess.Connection = &testConn{Name: "TestStartConnackReadError"}
2254 errCh := make(chan error, 1)
2255@@ -1259,7 +1235,7 @@
2256 }
2257
2258 func (cs *clientSessionSuite) TestStartBadConnack(c *C) {
2259- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2260+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2261 c.Assert(err, IsNil)
2262 sess.Connection = &testConn{Name: "TestStartBadConnack"}
2263 errCh := make(chan error, 1)
2264@@ -1283,7 +1259,7 @@
2265 }
2266
2267 func (cs *clientSessionSuite) TestStartNotConnack(c *C) {
2268- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2269+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2270 c.Assert(err, IsNil)
2271 sess.Connection = &testConn{Name: "TestStartBadConnack"}
2272 errCh := make(chan error, 1)
2273@@ -1350,13 +1326,31 @@
2274 run() tests
2275 ****************************************************************/
2276
2277+func (cs *clientSessionSuite) TestRunCallsCloserWithFalse(c *C) {
2278+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2279+ c.Assert(err, IsNil)
2280+ failure := errors.New("bail")
2281+ has_closed := false
2282+ with_false := false
2283+ err = sess.run(
2284+ func(b bool) { has_closed = true; with_false = !b },
2285+ func() error { return failure },
2286+ nil,
2287+ nil,
2288+ nil,
2289+ nil)
2290+ c.Check(err, Equals, failure)
2291+ c.Check(has_closed, Equals, true)
2292+ c.Check(with_false, Equals, true)
2293+}
2294+
2295 func (cs *clientSessionSuite) TestRunBailsIfAuthCheckFails(c *C) {
2296- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2297+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2298 c.Assert(err, IsNil)
2299 failure := errors.New("TestRunBailsIfAuthCheckFails")
2300 has_closed := false
2301 err = sess.run(
2302- func() { has_closed = true },
2303+ func(bool) { has_closed = true },
2304 func() error { return failure },
2305 nil,
2306 nil,
2307@@ -1367,12 +1361,12 @@
2308 }
2309
2310 func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) {
2311- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2312+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2313 c.Assert(err, IsNil)
2314 failure := errors.New("TestRunBailsIfHostGetterFails")
2315 has_closed := false
2316 err = sess.run(
2317- func() { has_closed = true },
2318+ func(bool) { has_closed = true },
2319 func() error { return nil },
2320 func() error { return failure },
2321 nil,
2322@@ -1383,11 +1377,11 @@
2323 }
2324
2325 func (cs *clientSessionSuite) TestRunBailsIfConnectFails(c *C) {
2326- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2327+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2328 c.Assert(err, IsNil)
2329 failure := errors.New("TestRunBailsIfConnectFails")
2330 err = sess.run(
2331- func() {},
2332+ func(bool) {},
2333 func() error { return nil },
2334 func() error { return nil },
2335 func() error { return failure },
2336@@ -1397,11 +1391,11 @@
2337 }
2338
2339 func (cs *clientSessionSuite) TestRunBailsIfStartFails(c *C) {
2340- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2341+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2342 c.Assert(err, IsNil)
2343 failure := errors.New("TestRunBailsIfStartFails")
2344 err = sess.run(
2345- func() {},
2346+ func(bool) {},
2347 func() error { return nil },
2348 func() error { return nil },
2349 func() error { return nil },
2350@@ -1411,16 +1405,12 @@
2351 }
2352
2353 func (cs *clientSessionSuite) TestRunRunsEvenIfLoopFails(c *C) {
2354- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2355+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2356 c.Assert(err, IsNil)
2357- // just to make a point: until here we haven't set ErrCh & BroadcastCh (no
2358- // biggie if this stops being true)
2359- c.Check(sess.ErrCh, IsNil)
2360- c.Check(sess.BroadcastCh, IsNil)
2361 failureCh := make(chan error) // must be unbuffered
2362 notf := &BroadcastNotification{}
2363 err = sess.run(
2364- func() {},
2365+ func(bool) {},
2366 func() error { return nil },
2367 func() error { return nil },
2368 func() error { return nil },
2369@@ -1428,12 +1418,12 @@
2370 func() error { sess.BroadcastCh <- notf; return <-failureCh })
2371 c.Check(err, Equals, nil)
2372 // if run doesn't error it sets up the channels
2373- c.Assert(sess.ErrCh, NotNil)
2374+ c.Assert(sess.errCh, NotNil)
2375 c.Assert(sess.BroadcastCh, NotNil)
2376 c.Check(<-sess.BroadcastCh, Equals, notf)
2377 failure := errors.New("TestRunRunsEvenIfLoopFails")
2378 failureCh <- failure
2379- c.Check(<-sess.ErrCh, Equals, failure)
2380+ c.Check(<-sess.errCh, Equals, failure)
2381 // so now you know it was running in a goroutine :)
2382 }
2383
2384@@ -1442,7 +1432,7 @@
2385 ****************************************************************/
2386
2387 func (cs *clientSessionSuite) TestJitter(c *C) {
2388- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2389+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2390 c.Assert(err, IsNil)
2391 num_tries := 20 // should do the math
2392 spread := time.Second //
2393@@ -1474,7 +1464,7 @@
2394
2395 func (cs *clientSessionSuite) TestDialPanics(c *C) {
2396 // one last unhappy test
2397- sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
2398+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2399 c.Assert(err, IsNil)
2400 sess.Protocolator = nil
2401 c.Check(sess.Dial, PanicMatches, ".*protocol constructor.")
2402@@ -1482,12 +1472,15 @@
2403
2404 var (
2405 dialTestTimeout = 300 * time.Millisecond
2406- dialTestConf = ClientSessionConfig{
2407- ExchangeTimeout: dialTestTimeout,
2408- PEM: helpers.TestCertPEMBlock,
2409- }
2410 )
2411
2412+func dialTestConf() ClientSessionConfig {
2413+ conf := dummyConf()
2414+ conf.ExchangeTimeout = dialTestTimeout
2415+ conf.PEM = helpers.TestCertPEMBlock
2416+ return conf
2417+}
2418+
2419 func (cs *clientSessionSuite) TestDialBadServerName(c *C) {
2420 // a borked server name
2421 lst, err := tls.Listen("tcp", "localhost:0", helpers.TestTLSServerConfig)
2422@@ -1506,7 +1499,7 @@
2423 }))
2424 defer ts.Close()
2425
2426- sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log)
2427+ sess, err := NewSession(ts.URL, dialTestConf(), "wah", cs.lvls, cs.log)
2428 c.Assert(err, IsNil)
2429 tconn := &testConn{}
2430 sess.Connection = tconn
2431@@ -1551,7 +1544,7 @@
2432 }))
2433 defer ts.Close()
2434
2435- sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log)
2436+ sess, err := NewSession(ts.URL, dialTestConf(), "wah", cs.lvls, cs.log)
2437 c.Assert(err, IsNil)
2438 tconn := &testConn{CloseCondition: condition.Fail2Work(10)}
2439 sess.Connection = tconn
2440@@ -1631,16 +1624,16 @@
2441 c.Check(takeNext(downCh), Equals, protocol.PingPongMsg{Type: "pong"})
2442 failure := errors.New("pongs")
2443 upCh <- failure
2444- c.Check(<-sess.ErrCh, Equals, failure)
2445+ c.Check(<-sess.errCh, Equals, failure)
2446 }
2447
2448 func (cs *clientSessionSuite) TestDialWorksDirect(c *C) {
2449 // happy path thoughts
2450 lst, err := tls.Listen("tcp", "localhost:0", helpers.TestTLSServerConfig)
2451 c.Assert(err, IsNil)
2452- sess, err := NewSession(lst.Addr().String(), dialTestConf, "wah", cs.lvls, cs.log)
2453+ sess, err := NewSession(lst.Addr().String(), dialTestConf(), "wah", cs.lvls, cs.log)
2454 c.Assert(err, IsNil)
2455- defer sess.Close()
2456+ defer sess.StopKeepConnection()
2457
2458 upCh := make(chan interface{}, 5)
2459 downCh := make(chan interface{}, 5)
2460@@ -1659,7 +1652,7 @@
2461 ****************************************************************/
2462
2463 func (cs *clientSessionSuite) TestShouldDelay(c *C) {
2464- sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
2465+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
2466 c.Assert(err, IsNil)
2467 c.Check(sess.ShouldDelay(), Equals, false)
2468 sess.setShouldDelay()
2469@@ -1669,7 +1662,7 @@
2470 }
2471
2472 func (cs *clientSessionSuite) TestRedialDelay(c *C) {
2473- sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
2474+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
2475 c.Assert(err, IsNil)
2476 sess.redialDelays = []time.Duration{17, 42}
2477 n := 0
2478@@ -1690,15 +1683,207 @@
2479 }
2480
2481 /****************************************************************
2482- ClearCookie() tests
2483+ ResetCookie() tests
2484 ****************************************************************/
2485
2486-func (cs *clientSessionSuite) TestClearCookie(c *C) {
2487- sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
2488+func (cs *clientSessionSuite) TestResetCookie(c *C) {
2489+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
2490 c.Assert(err, IsNil)
2491+ c.Assert(sess.KeepConnection(), IsNil)
2492+ defer sess.StopKeepConnection()
2493 c.Check(sess.getCookie(), Equals, "")
2494 sess.setCookie("COOKIE")
2495 c.Check(sess.getCookie(), Equals, "COOKIE")
2496- sess.ClearCookie()
2497+ sess.ResetCookie()
2498 c.Check(sess.getCookie(), Equals, "")
2499 }
2500+
2501+/****************************************************************
2502+ KeepConnection() (and related) tests
2503+****************************************************************/
2504+
2505+func (cs *clientSessionSuite) TestKeepConnectionDoesNothingIfNotConnected(c *C) {
2506+ // how do you test "does nothing?"
2507+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
2508+ c.Assert(err, IsNil)
2509+ c.Assert(sess, NotNil)
2510+ c.Assert(sess.State(), Equals, Pristine)
2511+ c.Assert(sess.KeepConnection(), IsNil)
2512+ defer sess.StopKeepConnection()
2513+ // stopCh is meant to be used just for closing it, but abusing
2514+ // it for testing seems the right thing to do: this ensures
2515+ // the thing is ticking along before we check the state of
2516+ // stuff.
2517+ sess.stopCh <- struct{}{}
2518+ c.Check(sess.State(), Equals, Disconnected)
2519+}
2520+
2521+func (cs *clientSessionSuite) TestYouCantCallKeepConnectionTwice(c *C) {
2522+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
2523+ c.Assert(err, IsNil)
2524+ c.Assert(sess, NotNil)
2525+ c.Assert(sess.State(), Equals, Pristine)
2526+ c.Assert(sess.KeepConnection(), IsNil)
2527+ defer sess.StopKeepConnection()
2528+ c.Check(sess.KeepConnection(), NotNil)
2529+}
2530+
2531+func (cs *clientSessionSuite) TestStopKeepConnectionShutsdown(c *C) {
2532+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
2533+ c.Assert(err, IsNil)
2534+ c.Assert(sess, NotNil)
2535+ sess.StopKeepConnection()
2536+ c.Check(sess.State(), Equals, Shutdown)
2537+}
2538+
2539+func (cs *clientSessionSuite) TestHasConnectivityTriggersConnectivityHandler(c *C) {
2540+ sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
2541+ c.Assert(err, IsNil)
2542+ c.Assert(sess, NotNil)
2543+ testCh := make(chan bool)
2544+ sess.connHandler = func(p bool) { testCh <- p }
2545+ go sess.doKeepConnection()
2546+ defer sess.StopKeepConnection()
2547+ sess.HasConnectivity(true)
2548+ c.Check(<-testCh, Equals, true)
2549+ sess.HasConnectivity(false)
2550+ c.Check(<-testCh, Equals, false)
2551+}
2552+
2553+func (cs *clientSessionSuite) TestDoneChIsEmptiedAndLogged(c *C) {
2554+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2555+ c.Assert(err, IsNil)
2556+ sess.doneCh = make(chan uint32) // unbuffered
2557+
2558+ sess.KeepConnection()
2559+ defer sess.StopKeepConnection()
2560+
2561+ sess.doneCh <- 23
2562+ sess.doneCh <- 24 // makes sure the first one has been processed before checking
2563+
2564+ c.Check(cs.log.Captured(),
2565+ Matches, `(?ms).* connected after 23 attempts\.`)
2566+}
2567+
2568+func (cs *clientSessionSuite) TestErrChIsEmptiedAndLoggedAndAutoRedial(c *C) {
2569+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2570+ c.Assert(err, IsNil)
2571+ ch := make(chan struct{}, 1)
2572+ sess.errCh = make(chan error) // unbuffered
2573+ sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
2574+ sess.lastConn = true // -> autoRedial, if the session is in Disconnected
2575+
2576+ sess.KeepConnection()
2577+ defer sess.StopKeepConnection()
2578+
2579+ sess.setState(Error)
2580+ sess.errCh <- errors.New("potato")
2581+ select {
2582+ case <-ch:
2583+ // all ok
2584+ case <-time.After(100 * time.Millisecond):
2585+ c.Fatalf("redialDelay not called (-> autoRedial not called)?")
2586+ }
2587+
2588+ c.Check(cs.log.Captured(),
2589+ Matches, `(?ms).* session error.*potato`)
2590+}
2591+
2592+func (cs *clientSessionSuite) TestErrChIsEmptiedAndLoggedNoAutoRedial(c *C) {
2593+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2594+ c.Assert(err, IsNil)
2595+ ch := make(chan struct{}, 1)
2596+ sess.errCh = make(chan error) // unbuffered
2597+ sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
2598+ sess.connHandler = func(bool) {}
2599+ sess.lastConn = false // so, no autoredial
2600+
2601+ sess.KeepConnection()
2602+ defer sess.StopKeepConnection()
2603+
2604+ sess.errCh <- errors.New("potato")
2605+ c.Assert(sess.State(), Equals, Disconnected)
2606+ select {
2607+ case <-ch:
2608+ c.Fatalf("redialDelay called (-> autoRedial called) when disconnected?")
2609+ case <-time.After(100 * time.Millisecond):
2610+ // all ok
2611+ }
2612+
2613+ c.Check(cs.log.Captured(),
2614+ Matches, `(?ms).* session error.*potato`)
2615+}
2616+
2617+func (cs *clientSessionSuite) TestHandleConnConnFromConnected(c *C) {
2618+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2619+ c.Assert(err, IsNil)
2620+ ch := make(chan struct{}, 1)
2621+ sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
2622+ sess.state = Connected
2623+ sess.lastConn = true
2624+ sess.handleConn(true)
2625+ c.Check(sess.lastConn, Equals, true)
2626+
2627+ select {
2628+ case <-ch:
2629+ // all ok
2630+ case <-time.After(100 * time.Millisecond):
2631+ c.Fatalf("redialDelay not called (-> autoRedial not called)?")
2632+ }
2633+}
2634+
2635+func (cs *clientSessionSuite) TestHandleConnConnFromDisconnected(c *C) {
2636+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2637+ c.Assert(err, IsNil)
2638+ ch := make(chan struct{}, 1)
2639+ sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
2640+ sess.state = Disconnected
2641+ sess.lastConn = false
2642+ sess.handleConn(true)
2643+ c.Check(sess.lastConn, Equals, true)
2644+
2645+ select {
2646+ case <-ch:
2647+ // all ok
2648+ case <-time.After(100 * time.Millisecond):
2649+ c.Fatalf("redialDelay not called (-> autoRedial not called)?")
2650+ }
2651+}
2652+
2653+func (cs *clientSessionSuite) TestHandleConnNotConnFromDisconnected(c *C) {
2654+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2655+ c.Assert(err, IsNil)
2656+ ch := make(chan struct{}, 1)
2657+ sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
2658+ sess.state = Disconnected
2659+ sess.lastConn = false
2660+ sess.handleConn(false)
2661+ c.Check(sess.lastConn, Equals, false)
2662+
2663+ select {
2664+ case <-ch:
2665+ c.Fatalf("redialDelay called (-> autoRedial called)?")
2666+ case <-time.After(100 * time.Millisecond):
2667+ // all ok
2668+ }
2669+ c.Check(cs.log.Captured(), Matches, `(?ms).*-> Disconnected`)
2670+}
2671+
2672+func (cs *clientSessionSuite) TestHandleConnNotConnFromConnected(c *C) {
2673+ sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
2674+ c.Assert(err, IsNil)
2675+ ch := make(chan struct{}, 1)
2676+ sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
2677+ sess.state = Connected
2678+ sess.lastConn = true
2679+ sess.handleConn(false)
2680+ c.Check(sess.lastConn, Equals, false)
2681+
2682+ select {
2683+ case <-ch:
2684+ c.Fatalf("redialDelay called (-> autoRedial called)?")
2685+ case <-time.After(100 * time.Millisecond):
2686+ // all ok
2687+ }
2688+ c.Check(cs.log.Captured(), Matches, `(?ms).*-> Disconnected`)
2689+}
2690
2691=== modified file 'debian/changelog'
2692--- debian/changelog 2015-03-06 13:27:06 +0000
2693+++ debian/changelog 2015-04-02 09:52:21 +0000
2694@@ -1,3 +1,26 @@
2695+ubuntu-push (0.68+15.04.20150306.2-0ubuntu2) UNRELEASED; urgency=medium
2696+
2697+ [Roberto Alsina]
2698+ * click-hook: report failure if hooks_path doesn't exist. [client]
2699+
2700+ [Bret Barker]
2701+ * add a hacky busy sleep loop to workaround go's sleep not
2702+ accounting for suspended time, more logging (lp:1435109). [client]
2703+
2704+ [John R. Lenton]
2705+ * Refactor code maintaining session (better fix for lp:1390663) [client]
2706+
2707+ [Samuele Pedroni]
2708+ * just delegate whether there's a update-worthy image to the
2709+ system-settings helper and system-image. [client]
2710+ * stop waking up for polling if in flight-mode and wireless not
2711+ enabled (lp:1437135). [client]
2712+ * don't hold a lock for a long time on handleErrConn, trigger
2713+ autoRedial on Error more actively (lp:1435109). [client]
2714+ * disallow RC4 and SSLv3. [server]
2715+
2716+ -- Samuele Pedroni <pedronis@ubuntu-64> Thu, 02 Apr 2015 11:38:19 +0200
2717+
2718 ubuntu-push (0.68+15.04.20150306.2-0ubuntu1) vivid; urgency=medium
2719
2720 [ John R. Lenton ]
2721
2722=== modified file 'debian/config.json'
2723--- debian/config.json 2015-01-26 21:00:27 +0000
2724+++ debian/config.json 2015-04-02 09:52:21 +0000
2725@@ -19,5 +19,6 @@
2726 "poll_settle": "20ms",
2727 "poll_net_wait": "1m",
2728 "poll_polld_wait": "3m",
2729- "poll_done_wait": "5s"
2730+ "poll_done_wait": "5s",
2731+ "poll_busy_wait": "1s"
2732 }
2733
2734=== modified file 'launch_helper/kindpool_test.go'
2735--- launch_helper/kindpool_test.go 2014-08-22 14:54:41 +0000
2736+++ launch_helper/kindpool_test.go 2015-04-02 09:52:21 +0000
2737@@ -45,7 +45,7 @@
2738 select {
2739 case res := <-ch:
2740 return res
2741- case <-time.After(100 * time.Millisecond):
2742+ case <-time.After(time.Second):
2743 c.Fatal("timeout waiting for result")
2744 }
2745 return nil
2746
2747=== modified file 'poller/poller.go'
2748--- poller/poller.go 2015-01-21 17:21:42 +0000
2749+++ poller/poller.go 2015-04-02 09:52:21 +0000
2750@@ -1,5 +1,5 @@
2751 /*
2752- Copyright 2014 Canonical Ltd.
2753+ Copyright 2014-2015 Canonical Ltd.
2754
2755 This program is free software: you can redistribute it and/or modify it
2756 under the terms of the GNU General Public License version 3, as published
2757@@ -25,8 +25,10 @@
2758 "time"
2759
2760 "launchpad.net/ubuntu-push/bus"
2761+ "launchpad.net/ubuntu-push/bus/networkmanager"
2762 "launchpad.net/ubuntu-push/bus/polld"
2763 "launchpad.net/ubuntu-push/bus/powerd"
2764+ "launchpad.net/ubuntu-push/bus/urfkill"
2765 "launchpad.net/ubuntu-push/client/session"
2766 "launchpad.net/ubuntu-push/logger"
2767 "launchpad.net/ubuntu-push/util"
2768@@ -48,6 +50,7 @@
2769 NetworkWait time.Duration
2770 PolldWait time.Duration
2771 DoneWait time.Duration
2772+ BusyWait time.Duration
2773 }
2774
2775 type Poller interface {
2776@@ -63,21 +66,29 @@
2777 }
2778
2779 type poller struct {
2780- times Times
2781- log logger.Logger
2782- powerd powerd.Powerd
2783- polld polld.Polld
2784- cookie string
2785- sessionState stater
2786+ times Times
2787+ log logger.Logger
2788+ nm networkmanager.NetworkManager
2789+ powerd powerd.Powerd
2790+ polld polld.Polld
2791+ urfkill urfkill.URfkill
2792+ cookie string
2793+ sessionState stater
2794+ requestWakeupCh chan struct{}
2795+ requestedWakeupErrCh chan error
2796+ holdsWakeLockCh chan bool
2797 }
2798
2799 func New(setup *PollerSetup) Poller {
2800 return &poller{
2801- times: setup.Times,
2802- log: setup.Log,
2803- powerd: nil,
2804- polld: nil,
2805- sessionState: setup.SessionStateGetter,
2806+ times: setup.Times,
2807+ log: setup.Log,
2808+ powerd: nil,
2809+ polld: nil,
2810+ sessionState: setup.SessionStateGetter,
2811+ requestWakeupCh: make(chan struct{}),
2812+ requestedWakeupErrCh: make(chan error),
2813+ holdsWakeLockCh: make(chan bool),
2814 }
2815 }
2816
2817@@ -92,10 +103,17 @@
2818 if p.powerd != nil || p.polld != nil {
2819 return ErrAlreadyStarted
2820 }
2821+ nmEndp := bus.SystemBus.Endpoint(networkmanager.BusAddress, p.log)
2822 powerdEndp := bus.SystemBus.Endpoint(powerd.BusAddress, p.log)
2823 polldEndp := bus.SessionBus.Endpoint(polld.BusAddress, p.log)
2824+ urEndp := bus.SystemBus.Endpoint(urfkill.BusAddress, p.log)
2825 var wg sync.WaitGroup
2826- wg.Add(2)
2827+ wg.Add(4)
2828+ go func() {
2829+ n := util.NewAutoRedialer(nmEndp).Redial()
2830+ p.log.Debugf("NetworkManager dialed on try %d", n)
2831+ wg.Done()
2832+ }()
2833 go func() {
2834 n := util.NewAutoRedialer(powerdEndp).Redial()
2835 p.log.Debugf("powerd dialed on try %d", n)
2836@@ -106,10 +124,31 @@
2837 p.log.Debugf("polld dialed in on try %d", n)
2838 wg.Done()
2839 }()
2840+ go func() {
2841+ n := util.NewAutoRedialer(urEndp).Redial()
2842+ p.log.Debugf("URfkill dialed on try %d", n)
2843+ wg.Done()
2844+ }()
2845 wg.Wait()
2846
2847+ p.nm = networkmanager.New(nmEndp, p.log)
2848 p.powerd = powerd.New(powerdEndp, p.log)
2849 p.polld = polld.New(polldEndp, p.log)
2850+ p.urfkill = urfkill.New(urEndp, p.log)
2851+
2852+ // busy sleep loop to workaround go's timer/sleep
2853+ // not accounting for time when the system is suspended
2854+ // see https://bugs.launchpad.net/ubuntu/+source/ubuntu-push/+bug/1435109
2855+ if p.times.BusyWait > 0 {
2856+ p.log.Debugf("starting busy loop with %s interval", p.times.BusyWait)
2857+ go func() {
2858+ for {
2859+ time.Sleep(p.times.BusyWait)
2860+ }
2861+ }()
2862+ } else {
2863+ p.log.Debugf("skipping busy loop")
2864+ }
2865
2866 return nil
2867 }
2868@@ -118,7 +157,7 @@
2869 if p.log == nil {
2870 return ErrUnconfigured
2871 }
2872- if p.powerd == nil || p.polld == nil {
2873+ if p.nm == nil || p.powerd == nil || p.polld == nil || p.urfkill == nil {
2874 return ErrNotStarted
2875 }
2876 wakeupCh, err := p.powerd.WatchWakeups()
2877@@ -129,10 +168,110 @@
2878 if err != nil {
2879 return err
2880 }
2881- go p.run(wakeupCh, doneCh)
2882+ flightMode := p.urfkill.IsFlightMode()
2883+ wirelessEnabled := p.nm.GetWirelessEnabled()
2884+ flightModeCh, _, err := p.urfkill.WatchFlightMode()
2885+ if err != nil {
2886+ return err
2887+ }
2888+ wirelessEnabledCh, _, err := p.nm.WatchWirelessEnabled()
2889+ if err != nil {
2890+ return err
2891+ }
2892+
2893+ filteredWakeUpCh := make(chan bool)
2894+ go p.control(wakeupCh, filteredWakeUpCh, flightMode, flightModeCh, wirelessEnabled, wirelessEnabledCh)
2895+ go p.run(filteredWakeUpCh, doneCh)
2896 return nil
2897 }
2898
2899+func (p *poller) doRequestWakeup(delta time.Duration) (time.Time, string, error) {
2900+ t := time.Now().Add(delta).Truncate(time.Second)
2901+ cookie, err := p.powerd.RequestWakeup("ubuntu push client", t)
2902+ if err == nil {
2903+ p.log.Debugf("requested wakeup at %s", t)
2904+ } else {
2905+ p.log.Errorf("RequestWakeup got %v", err)
2906+ t = time.Time{}
2907+ cookie = ""
2908+ }
2909+ return t, cookie, err
2910+}
2911+
2912+func (p *poller) control(wakeupCh <-chan bool, filteredWakeUpCh chan<- bool, flightMode bool, flightModeCh <-chan bool, wirelessEnabled bool, wirelessEnabledCh <-chan bool) {
2913+ dontPoll := flightMode && !wirelessEnabled
2914+ var t time.Time
2915+ cookie := ""
2916+ holdsWakeLock := false
2917+ for {
2918+ select {
2919+ case holdsWakeLock = <-p.holdsWakeLockCh:
2920+ case <-p.requestWakeupCh:
2921+ if !t.IsZero() || dontPoll {
2922+ // earlier wakeup or we shouldn't be polling
2923+ // => don't request wakeup
2924+ if dontPoll {
2925+ p.log.Debugf("skip requesting wakeup")
2926+ }
2927+ p.requestedWakeupErrCh <- nil
2928+ break
2929+ }
2930+ var err error
2931+ t, cookie, err = p.doRequestWakeup(p.times.AlarmInterval)
2932+ p.requestedWakeupErrCh <- err
2933+ case b := <-wakeupCh:
2934+ // seems we get here also on clear wakeup, oh well
2935+ if !b {
2936+ panic("WatchWakeups channel produced a false value (??)")
2937+ }
2938+ // the channel will produce a true for every
2939+ // wakeup, not only the one we asked for
2940+ now := time.Now()
2941+ if t.IsZero() {
2942+ p.log.Debugf("got woken up; time is %s", now)
2943+ } else {
2944+ p.log.Debugf("got woken up; time is %s (𝛥: %s)", now, now.Sub(t))
2945+ if !now.Before(t) {
2946+ t = time.Time{}
2947+ filteredWakeUpCh <- true
2948+ }
2949+ }
2950+ case flightMode = <-flightModeCh:
2951+ case wirelessEnabled = <-wirelessEnabledCh:
2952+ }
2953+ newDontPoll := flightMode && !wirelessEnabled
2954+ p.log.Debugf("control: flightMode:%v wirelessEnabled:%v prevDontPoll:%v dontPoll:%v wakeupReq:%v holdsWakeLock:%v", flightMode, wirelessEnabled, dontPoll, newDontPoll, !t.IsZero(), holdsWakeLock)
2955+ if newDontPoll != dontPoll {
2956+ if dontPoll = newDontPoll; dontPoll {
2957+ if !t.IsZero() {
2958+ err := p.powerd.ClearWakeup(cookie)
2959+ if err == nil {
2960+ // cleared
2961+ t = time.Time{}
2962+ p.log.Debugf("cleared wakeup")
2963+ } else {
2964+ p.log.Errorf("ClearWakeup got %v", err)
2965+ }
2966+ }
2967+ } else {
2968+ if t.IsZero() && !holdsWakeLock {
2969+ // reschedule soon
2970+ t, cookie, _ = p.doRequestWakeup(p.times.NetworkWait / 20)
2971+ }
2972+ }
2973+ }
2974+ }
2975+}
2976+
2977+func (p *poller) requestWakeup() error {
2978+ p.requestWakeupCh <- struct{}{}
2979+ return <-p.requestedWakeupErrCh
2980+}
2981+
2982+func (p *poller) holdsWakeLock(has bool) {
2983+ p.holdsWakeLockCh <- has
2984+}
2985+
2986 func (p *poller) run(wakeupCh <-chan bool, doneCh <-chan bool) {
2987 var lockCookie string
2988
2989@@ -143,15 +282,13 @@
2990
2991 func (p *poller) step(wakeupCh <-chan bool, doneCh <-chan bool, lockCookie string) string {
2992
2993- t := time.Now().Add(p.times.AlarmInterval).Truncate(time.Second)
2994- _, err := p.powerd.RequestWakeup("ubuntu push client", t)
2995+ err := p.requestWakeup()
2996 if err != nil {
2997- p.log.Errorf("RequestWakeup got %v", err)
2998 // Don't do this too quickly. Pretend we are just skipping one wakeup
2999 time.Sleep(p.times.AlarmInterval)
3000 return lockCookie
3001 }
3002- p.log.Debugf("requested wakeup at %s", t)
3003+ p.holdsWakeLock(false)
3004 if lockCookie != "" {
3005 if err := p.powerd.ClearWakelock(lockCookie); err != nil {
3006 p.log.Errorf("ClearWakelock(%#v) got %v", lockCookie, err)
3007@@ -160,30 +297,23 @@
3008 }
3009 lockCookie = ""
3010 }
3011- for b := range wakeupCh {
3012- if !b {
3013- panic("WatchWakeups channel produced a false value (??)")
3014- }
3015- // the channel will produce a true for every
3016- // wakeup, not only the one we asked for
3017- now := time.Now()
3018- p.log.Debugf("got woken up; time is %s (𝛥: %s)", now, now.Sub(t))
3019- if !now.Before(t) {
3020- break
3021- }
3022- }
3023+ <-wakeupCh
3024 lockCookie, err = p.powerd.RequestWakelock("ubuntu push client")
3025 if err != nil {
3026 p.log.Errorf("RequestWakelock got %v", err)
3027 return lockCookie
3028 }
3029- p.log.Debugf("got wakelock cookie of %s", lockCookie)
3030+ p.holdsWakeLock(true)
3031+ p.log.Debugf("got wakelock cookie of %s, checking conn state", lockCookie)
3032 time.Sleep(p.times.SessionStateSettle)
3033 for i := 0; i < 20; i++ {
3034 if p.IsConnected() {
3035+ p.log.Debugf("iter %02d: connected", i)
3036 break
3037 }
3038+ p.log.Debugf("iter %02d: not connected, sleeping for %s", i, p.times.NetworkWait/20)
3039 time.Sleep(p.times.NetworkWait / 20)
3040+ p.log.Debugf("iter %02d: slept", i)
3041 }
3042 if !p.IsConnected() {
3043 p.log.Errorf("not connected after %s; giving up", p.times.NetworkWait)
3044@@ -215,7 +345,9 @@
3045 }
3046
3047 // XXX check whether something was actually done before waiting
3048+ p.log.Debugf("sleeping for DoneWait %s", p.times.DoneWait)
3049 time.Sleep(p.times.DoneWait)
3050+ p.log.Debugf("slept")
3051 }
3052
3053 return lockCookie
3054
3055=== modified file 'poller/poller_test.go'
3056--- poller/poller_test.go 2014-08-21 10:56:12 +0000
3057+++ poller/poller_test.go 2015-04-02 09:52:21 +0000
3058@@ -1,5 +1,5 @@
3059 /*
3060- Copyright 2014 Canonical Ltd.
3061+ Copyright 2014-2015 Canonical Ltd.
3062
3063 This program is free software: you can redistribute it and/or modify it
3064 under the terms of the GNU General Public License version 3, as published
3065@@ -42,7 +42,7 @@
3066 reqWakeCookie string
3067 reqWakeErr error
3068 // WatchWakeups
3069- watchWakeCh <-chan bool
3070+ watchWakeCh chan bool
3071 watchWakeErr error
3072 // RequestWakelock
3073 reqLockName string
3074@@ -63,6 +63,9 @@
3075 func (m *myD) RequestWakeup(name string, wakeupTime time.Time) (string, error) {
3076 m.reqWakeName = name
3077 m.reqWakeTime = wakeupTime
3078+ time.AfterFunc(100*time.Millisecond, func() {
3079+ m.watchWakeCh <- true
3080+ })
3081 return m.reqWakeCookie, m.reqWakeErr
3082 }
3083 func (m *myD) RequestWakelock(name string) (string, error) {
3084@@ -73,7 +76,10 @@
3085 m.clearLockCookie = cookie
3086 return m.clearLockErr
3087 }
3088-func (m *myD) ClearWakeup(cookie string) error { panic("clearwakeup called??") }
3089+func (m *myD) ClearWakeup(cookie string) error {
3090+ m.watchWakeCh <- false
3091+ return nil
3092+}
3093 func (m *myD) WatchWakeups() (<-chan bool, error) { return m.watchWakeCh, m.watchWakeErr }
3094 func (m *myD) Poll() error { return m.pollErr }
3095 func (m *myD) WatchDones() (<-chan bool, error) { return m.watchDonesCh, m.watchDonesErr }
3096@@ -86,23 +92,27 @@
3097
3098 func (s *PrSuite) TestStep(c *C) {
3099 p := &poller{
3100- times: Times{},
3101- log: s.log,
3102- powerd: s.myd,
3103- polld: s.myd,
3104- sessionState: s.myd,
3105+ times: Times{},
3106+ log: s.log,
3107+ powerd: s.myd,
3108+ polld: s.myd,
3109+ sessionState: s.myd,
3110+ requestWakeupCh: make(chan struct{}),
3111+ requestedWakeupErrCh: make(chan error),
3112+ holdsWakeLockCh: make(chan bool),
3113 }
3114 s.myd.reqLockCookie = "wakelock cookie"
3115 s.myd.stateState = session.Running
3116- // we'll get the wakeup right away
3117 wakeupCh := make(chan bool, 1)
3118- wakeupCh <- true
3119+ s.myd.watchWakeCh = wakeupCh
3120 // we won't get the "done" signal in time ;)
3121 doneCh := make(chan bool)
3122 // and a channel to get the return value from a goroutine
3123 ch := make(chan string)
3124 // now, run
3125- go func() { ch <- p.step(wakeupCh, doneCh, "old cookie") }()
3126+ filteredWakeUpCh := make(chan bool)
3127+ go p.control(wakeupCh, filteredWakeUpCh, false, nil, true, nil)
3128+ go func() { ch <- p.step(filteredWakeUpCh, doneCh, "old cookie") }()
3129 select {
3130 case s := <-ch:
3131 c.Check(s, Equals, "wakelock cookie")
3132@@ -112,3 +122,53 @@
3133 // check we cleared the old cookie
3134 c.Check(s.myd.clearLockCookie, Equals, "old cookie")
3135 }
3136+
3137+func (s *PrSuite) TestControl(c *C) {
3138+ p := &poller{
3139+ times: Times{},
3140+ log: s.log,
3141+ powerd: s.myd,
3142+ polld: s.myd,
3143+ sessionState: s.myd,
3144+ requestWakeupCh: make(chan struct{}),
3145+ requestedWakeupErrCh: make(chan error),
3146+ holdsWakeLockCh: make(chan bool),
3147+ }
3148+ wakeUpCh := make(chan bool)
3149+ filteredWakeUpCh := make(chan bool)
3150+ s.myd.watchWakeCh = make(chan bool, 1)
3151+ flightModeCh := make(chan bool)
3152+ wirelessModeCh := make(chan bool)
3153+ go p.control(wakeUpCh, filteredWakeUpCh, false, flightModeCh, true, wirelessModeCh)
3154+
3155+ // works
3156+ err := p.requestWakeup()
3157+ c.Assert(err, IsNil)
3158+ c.Check(<-s.myd.watchWakeCh, Equals, true)
3159+
3160+ // there's a wakeup already
3161+ err = p.requestWakeup()
3162+ c.Assert(err, IsNil)
3163+ c.Check(s.myd.watchWakeCh, HasLen, 0)
3164+
3165+ // wakeup happens
3166+ wakeUpCh <- true
3167+ <-filteredWakeUpCh
3168+
3169+ // flight mode
3170+ flightModeCh <- true
3171+ wirelessModeCh <- false
3172+ err = p.requestWakeup()
3173+ c.Assert(err, IsNil)
3174+ c.Check(s.myd.watchWakeCh, HasLen, 0)
3175+
3176+ // wireless on
3177+ wirelessModeCh <- true
3178+ c.Check(<-s.myd.watchWakeCh, Equals, true)
3179+
3180+ // wireless off
3181+ wirelessModeCh <- false
3182+ // pending wakeup was cleared
3183+ c.Check(<-s.myd.watchWakeCh, Equals, false)
3184+
3185+}
3186
3187=== modified file 'scripts/click-hook'
3188--- scripts/click-hook 2014-10-15 11:56:43 +0000
3189+++ scripts/click-hook 2015-04-02 09:52:21 +0000
3190@@ -53,6 +53,8 @@
3191
3192 def collect_helpers(helpers_data_path, helpers_data_path_tmp, hooks_path):
3193 helpers_data = {}
3194+ if not os.path.isdir(hooks_path):
3195+ return True
3196 for hook_fname in os.listdir(hooks_path):
3197 if not hook_fname.endswith(hook_ext):
3198 continue
3199
3200=== modified file 'server/tlsconfig.go'
3201--- server/tlsconfig.go 2014-09-04 12:51:08 +0000
3202+++ server/tlsconfig.go 2015-04-02 09:52:21 +0000
3203@@ -48,6 +48,18 @@
3204 tlsCfg := &tls.Config{
3205 Certificates: []tls.Certificate{cfg.cert},
3206 SessionTicketsDisabled: true,
3207+ // order from crypto/tls/cipher_suites.go, no RC4
3208+ CipherSuites: []uint16{
3209+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
3210+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
3211+ tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
3212+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
3213+ tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
3214+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
3215+ tls.TLS_RSA_WITH_AES_128_CBC_SHA,
3216+ tls.TLS_RSA_WITH_AES_256_CBC_SHA,
3217+ },
3218+ MinVersion: tls.VersionTLS10,
3219 }
3220 return tlsCfg
3221 }

Subscribers

People subscribed via source and target branches