Merge lp:~ralsina/ubuntu-push/merge-automatic into lp:ubuntu-push

Proposed by Roberto Alsina
Status: Merged
Approved by: Roberto Alsina
Approved revision: no longer in the source branch.
Merged at revision: 121
Proposed branch: lp:~ralsina/ubuntu-push/merge-automatic
Merge into: lp:ubuntu-push
Diff against target: 2235 lines (+1306/-207)
28 files modified
bus/notifications/raw.go (+1/-1)
bus/notifications/raw_test.go (+13/-0)
bus/polld/polld.go (+68/-0)
bus/polld/polld_test.go (+86/-0)
bus/powerd/powerd.go (+101/-0)
bus/powerd/powerd_test.go (+161/-0)
click/click.go (+13/-0)
click/click_test.go (+18/-0)
client/client.go (+39/-3)
client/client_test.go (+56/-13)
debian/changelog (+16/-1)
debian/config.json (+6/-1)
dependencies.tsv (+1/-1)
launch_helper/kindpool.go (+1/-1)
launch_helper/kindpool_test.go (+10/-3)
launch_helper/legacy/legacy.go (+36/-18)
launch_helper/legacy/legacy_test.go (+33/-4)
poller/poller.go (+220/-0)
poller/poller_test.go (+114/-0)
scripts/noisy-helper.sh (+7/-0)
server/acceptance/acceptanceclient.go (+15/-16)
server/acceptance/cmd/acceptanceclient.go (+54/-0)
server/acceptance/kit/api.go (+82/-0)
server/acceptance/kit/cliloop.go (+75/-52)
server/acceptance/kit/helpers.go (+46/-0)
server/acceptance/suites/broadcast.go (+14/-27)
server/acceptance/suites/suite.go (+5/-40)
server/acceptance/suites/unicast.go (+15/-26)
To merge this branch: bzr merge lp:~ralsina/ubuntu-push/merge-automatic
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+231942@code.launchpad.net

Description of the change

Merge changes from ubuntu-push automatic branch:

  * Avoid rare race in kindpool_test.go
  * Interface with account-polld's dbus api.
  * Powerd integration.
  * Use symbolic icon for secondary icon in notification.
  * Log legacy helper failures.

To post a comment you must log in.
121. By PS Jenkins bot

* Avoid rare race in kindpool_test.go
* Interface with account-polld's dbus api.
* Powerd integration.
* Use symbolic icon for secondary icon in notification.
* Log legacy helper failures.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bus/notifications/raw.go'
2--- bus/notifications/raw.go 2014-07-27 00:58:08 +0000
3+++ bus/notifications/raw.go 2014-08-25 16:38:20 +0000
4@@ -144,7 +144,7 @@
5 }
6
7 hints := make(map[string]*dbus.Variant)
8- hints["x-canonical-secondary-icon"] = &dbus.Variant{app.Icon()}
9+ hints["x-canonical-secondary-icon"] = &dbus.Variant{app.SymbolicIcon()}
10
11 appId := app.Original()
12 actions := make([]string, 2*len(card.Actions))
13
14=== modified file 'bus/notifications/raw_test.go'
15--- bus/notifications/raw_test.go 2014-07-27 00:58:08 +0000
16+++ bus/notifications/raw_test.go 2014-08-25 16:38:20 +0000
17@@ -210,6 +210,19 @@
18 c.Check(hints["x-canonical-secondary-icon"], NotNil)
19 }
20
21+func (s *RawSuite) TestPresentUsesSymbolic(c *C) {
22+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
23+ raw := Raw(endp, s.log)
24+ worked := raw.Present(s.app, "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true}})
25+ c.Assert(worked, Equals, true)
26+ callArgs := testibus.GetCallArgs(endp)
27+ c.Assert(callArgs, HasLen, 1)
28+ c.Assert(callArgs[0].Args, HasLen, 8)
29+ hints, ok := callArgs[0].Args[6].(map[string]*dbus.Variant)
30+ c.Assert(ok, Equals, true)
31+ c.Check(hints["x-canonical-secondary-icon"].Value.(string), Equals, "-symbolic")
32+}
33+
34 func (s *RawSuite) TestPresentNoNotificationPanics(c *C) {
35 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
36 raw := Raw(endp, s.log)
37
38=== added directory 'bus/polld'
39=== added file 'bus/polld/polld.go'
40--- bus/polld/polld.go 1970-01-01 00:00:00 +0000
41+++ bus/polld/polld.go 2014-08-25 16:38:20 +0000
42@@ -0,0 +1,68 @@
43+/*
44+ Copyright 2014 Canonical Ltd.
45+
46+ This program is free software: you can redistribute it and/or modify it
47+ under the terms of the GNU General Public License version 3, as published
48+ by the Free Software Foundation.
49+
50+ This program is distributed in the hope that it will be useful, but
51+ WITHOUT ANY WARRANTY; without even the implied warranties of
52+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
53+ PURPOSE. See the GNU General Public License for more details.
54+
55+ You should have received a copy of the GNU General Public License along
56+ with this program. If not, see <http://www.gnu.org/licenses/>.
57+*/
58+
59+// Package polld wraps the account-polld dbus interface
60+package polld
61+
62+import (
63+ "errors"
64+
65+ "launchpad.net/ubuntu-push/bus"
66+ "launchpad.net/ubuntu-push/logger"
67+)
68+
69+var (
70+ ErrUnconfigured = errors.New("unconfigured.")
71+)
72+
73+// polld lives on a well-known bus.Address
74+var BusAddress bus.Address = bus.Address{
75+ Interface: "com.ubuntu.AccountPolld",
76+ Path: "/com/ubuntu/AccountPolld",
77+ Name: "com.ubuntu.AccountPolld",
78+}
79+
80+type Polld interface {
81+ Poll() error
82+ WatchDones() (<-chan bool, error)
83+}
84+
85+type polld struct {
86+ endp bus.Endpoint
87+ log logger.Logger
88+}
89+
90+func New(endp bus.Endpoint, log logger.Logger) Polld {
91+ return &polld{endp, log}
92+}
93+
94+func (p *polld) Poll() error {
95+ if p.endp == nil {
96+ return ErrUnconfigured
97+ }
98+ return p.endp.Call("Poll", nil)
99+}
100+
101+func (p *polld) WatchDones() (<-chan bool, error) {
102+ if p.endp == nil {
103+ return nil, ErrUnconfigured
104+ }
105+ ch := make(chan bool)
106+ p.endp.WatchSignal("Done", func(...interface{}) {
107+ ch <- true
108+ }, func() { close(ch) })
109+ return ch, nil
110+}
111
112=== added file 'bus/polld/polld_test.go'
113--- bus/polld/polld_test.go 1970-01-01 00:00:00 +0000
114+++ bus/polld/polld_test.go 2014-08-25 16:38:20 +0000
115@@ -0,0 +1,86 @@
116+/*
117+ Copyright 2014 Canonical Ltd.
118+
119+ This program is free software: you can redistribute it and/or modify it
120+ under the terms of the GNU General Public License version 3, as published
121+ by the Free Software Foundation.
122+
123+ This program is distributed in the hope that it will be useful, but
124+ WITHOUT ANY WARRANTY; without even the implied warranties of
125+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
126+ PURPOSE. See the GNU General Public License for more details.
127+
128+ You should have received a copy of the GNU General Public License along
129+ with this program. If not, see <http://www.gnu.org/licenses/>.
130+*/
131+
132+package polld
133+
134+import (
135+ "testing"
136+ "time"
137+
138+ . "launchpad.net/gocheck"
139+
140+ testibus "launchpad.net/ubuntu-push/bus/testing"
141+ helpers "launchpad.net/ubuntu-push/testing"
142+ "launchpad.net/ubuntu-push/testing/condition"
143+)
144+
145+// hook up gocheck
146+func TestPolld(t *testing.T) { TestingT(t) }
147+
148+type PdSuite struct {
149+ log *helpers.TestLogger
150+}
151+
152+var _ = Suite(&PdSuite{})
153+
154+func (s *PdSuite) SetUpTest(c *C) {
155+ s.log = helpers.NewTestLogger(c, "debug")
156+}
157+
158+func (s *PdSuite) TestPollWorks(c *C) {
159+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
160+ pd := New(endp, s.log)
161+ err := pd.Poll()
162+ c.Assert(err, IsNil)
163+ args := testibus.GetCallArgs(endp)
164+ c.Assert(args, HasLen, 1)
165+ c.Check(args[0].Member, Equals, "Poll")
166+ c.Check(args[0].Args, IsNil)
167+}
168+
169+func (s *PdSuite) TestPollUnconfigured(c *C) {
170+ c.Check(new(polld).Poll(), Equals, ErrUnconfigured)
171+}
172+
173+func (s *PdSuite) TestPollFails(c *C) {
174+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
175+ pd := New(endp, s.log)
176+ c.Check(pd.Poll(), NotNil)
177+}
178+
179+func (s *PdSuite) TestWatchDonesWorks(c *C) {
180+ endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{})
181+ pd := New(endp, s.log)
182+ ch, err := pd.WatchDones()
183+ c.Assert(err, IsNil)
184+ select {
185+ case b := <-ch:
186+ c.Check(b, Equals, true)
187+ case <-time.After(100 * time.Millisecond):
188+ c.Error("timeout waiting for bool")
189+ }
190+ select {
191+ case b := <-ch:
192+ c.Check(b, Equals, false)
193+ case <-time.After(100 * time.Millisecond):
194+ c.Error("timeout waiting for close")
195+ }
196+}
197+
198+func (s *PdSuite) TestWatchDonesUnconfigured(c *C) {
199+ _, err := new(polld).WatchDones()
200+ c.Check(err, Equals, ErrUnconfigured)
201+}
202
203=== added directory 'bus/powerd'
204=== added file 'bus/powerd/powerd.go'
205--- bus/powerd/powerd.go 1970-01-01 00:00:00 +0000
206+++ bus/powerd/powerd.go 2014-08-25 16:38:20 +0000
207@@ -0,0 +1,101 @@
208+/*
209+ Copyright 2014 Canonical Ltd.
210+
211+ This program is free software: you can redistribute it and/or modify it
212+ under the terms of the GNU General Public License version 3, as published
213+ by the Free Software Foundation.
214+
215+ This program is distributed in the hope that it will be useful, but
216+ WITHOUT ANY WARRANTY; without even the implied warranties of
217+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
218+ PURPOSE. See the GNU General Public License for more details.
219+
220+ You should have received a copy of the GNU General Public License along
221+ with this program. If not, see <http://www.gnu.org/licenses/>.
222+*/
223+
224+// Package powerd is an interface to powerd via dbus.
225+package powerd
226+
227+import (
228+ "errors"
229+ "time"
230+
231+ "launchpad.net/ubuntu-push/bus"
232+ "launchpad.net/ubuntu-push/logger"
233+)
234+
235+// powerd lives on a well-known bus.Address
236+var BusAddress bus.Address = bus.Address{
237+ Interface: "com.canonical.powerd",
238+ Path: "/com/canonical/powerd",
239+ Name: "com.canonical.powerd",
240+}
241+
242+// Powerd exposes a subset of powerd
243+type Powerd interface {
244+ RequestWakeup(name string, wakeupTime time.Time) (string, error)
245+ ClearWakeup(cookie string) error
246+ WatchWakeups() (<-chan bool, error)
247+ RequestWakelock(name string) (string, error)
248+ ClearWakelock(cookie string) error
249+}
250+
251+type powerd struct {
252+ endp bus.Endpoint
253+ log logger.Logger
254+}
255+
256+var (
257+ ErrUnconfigured = errors.New("unconfigured.")
258+)
259+
260+// New builds a new Powerd that uses the provided bus.Endpoint
261+func New(endp bus.Endpoint, log logger.Logger) Powerd {
262+ return &powerd{endp, log}
263+}
264+
265+func (p *powerd) RequestWakeup(name string, wakeupTime time.Time) (string, error) {
266+ if p.endp == nil {
267+ return "", ErrUnconfigured
268+ }
269+ var res string
270+ err := p.endp.Call("requestWakeup", bus.Args(name, uint64(wakeupTime.Unix())), &res)
271+ return res, err
272+}
273+
274+func (p *powerd) ClearWakeup(cookie string) error {
275+ if p.endp == nil {
276+ return ErrUnconfigured
277+ }
278+ return p.endp.Call("clearWakeup", bus.Args(cookie))
279+}
280+
281+func (p *powerd) WatchWakeups() (<-chan bool, error) {
282+ if p.endp == nil {
283+ return nil, ErrUnconfigured
284+ }
285+ ch := make(chan bool)
286+ p.endp.WatchSignal("Wakeup", func(...interface{}) {
287+ ch <- true
288+ }, func() { close(ch) })
289+ return ch, nil
290+}
291+
292+func (p *powerd) RequestWakelock(name string) (string, error) {
293+ // wakelocks are documented on https://wiki.ubuntu.com/powerd#API
294+ // (requestSysState with state=1)
295+ if p.endp == nil {
296+ return "", ErrUnconfigured
297+ }
298+ var res string
299+ err := p.endp.Call("requestSysState", bus.Args(name, int32(1)), &res)
300+ return res, err
301+}
302+
303+func (p *powerd) ClearWakelock(cookie string) error {
304+ if p.endp == nil {
305+ return ErrUnconfigured
306+ }
307+ return p.endp.Call("clearSysState", bus.Args(cookie))
308+}
309
310=== added file 'bus/powerd/powerd_test.go'
311--- bus/powerd/powerd_test.go 1970-01-01 00:00:00 +0000
312+++ bus/powerd/powerd_test.go 2014-08-25 16:38:20 +0000
313@@ -0,0 +1,161 @@
314+/*
315+ Copyright 2014 Canonical Ltd.
316+
317+ This program is free software: you can redistribute it and/or modify it
318+ under the terms of the GNU General Public License version 3, as published
319+ by the Free Software Foundation.
320+
321+ This program is distributed in the hope that it will be useful, but
322+ WITHOUT ANY WARRANTY; without even the implied warranties of
323+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
324+ PURPOSE. See the GNU General Public License for more details.
325+
326+ You should have received a copy of the GNU General Public License along
327+ with this program. If not, see <http://www.gnu.org/licenses/>.
328+*/
329+
330+package powerd
331+
332+import (
333+ "testing"
334+ "time"
335+
336+ . "launchpad.net/gocheck"
337+
338+ testibus "launchpad.net/ubuntu-push/bus/testing"
339+ helpers "launchpad.net/ubuntu-push/testing"
340+ "launchpad.net/ubuntu-push/testing/condition"
341+)
342+
343+// hook up gocheck
344+func TestPowerd(t *testing.T) { TestingT(t) }
345+
346+type PdSuite struct {
347+ log *helpers.TestLogger
348+}
349+
350+var _ = Suite(&PdSuite{})
351+
352+func (s *PdSuite) SetUpTest(c *C) {
353+ s.log = helpers.NewTestLogger(c, "debug")
354+}
355+
356+func (s *PdSuite) TestRequestWakeupWorks(c *C) {
357+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), "cookie")
358+ pd := New(endp, s.log)
359+ t := time.Now().Add(5 * time.Minute)
360+ ck, err := pd.RequestWakeup("name", t)
361+ c.Assert(err, IsNil)
362+ c.Check(ck, Equals, "cookie")
363+ args := testibus.GetCallArgs(endp)
364+ c.Assert(args, HasLen, 1)
365+ c.Check(args[0].Member, Equals, "requestWakeup")
366+ c.Check(args[0].Args, DeepEquals, []interface{}{"name", uint64(t.Unix())})
367+}
368+
369+func (s *PdSuite) TestRequestWakeupUnconfigured(c *C) {
370+ _, err := new(powerd).RequestWakeup("name", time.Now())
371+ c.Assert(err, Equals, ErrUnconfigured)
372+}
373+
374+func (s *PdSuite) TestRequestWakeupFails(c *C) {
375+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
376+ pd := New(endp, s.log)
377+ t := time.Now().Add(5 * time.Minute)
378+ _, err := pd.RequestWakeup("name", t)
379+ c.Assert(err, NotNil)
380+}
381+
382+func (s *PdSuite) TestClearWakeupWorks(c *C) {
383+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
384+ pd := New(endp, s.log)
385+ err := pd.ClearWakeup("cookie")
386+ c.Assert(err, IsNil)
387+ args := testibus.GetCallArgs(endp)
388+ c.Assert(args, HasLen, 1)
389+ c.Check(args[0].Member, Equals, "clearWakeup")
390+ c.Check(args[0].Args, DeepEquals, []interface{}{"cookie"})
391+}
392+
393+func (s *PdSuite) TestClearWakeupUnconfigured(c *C) {
394+ err := new(powerd).ClearWakeup("cookie")
395+ c.Assert(err, Equals, ErrUnconfigured)
396+}
397+
398+func (s *PdSuite) TestClearWakeupFails(c *C) {
399+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
400+ pd := New(endp, s.log)
401+ err := pd.ClearWakeup("cookie")
402+ c.Assert(err, NotNil)
403+}
404+
405+func (s *PdSuite) TestRequestWakelockWorks(c *C) {
406+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), "cookie")
407+ pd := New(endp, s.log)
408+ ck, err := pd.RequestWakelock("name")
409+ c.Assert(err, IsNil)
410+ c.Check(ck, Equals, "cookie")
411+ args := testibus.GetCallArgs(endp)
412+ c.Assert(args, HasLen, 1)
413+ // wakelocks are documented on https://wiki.ubuntu.com/powerd#API
414+ c.Check(args[0].Member, Equals, "requestSysState")
415+ c.Check(args[0].Args, DeepEquals, []interface{}{"name", int32(1)})
416+}
417+
418+func (s *PdSuite) TestRequestWakelockUnconfigured(c *C) {
419+ _, err := new(powerd).RequestWakelock("name")
420+ c.Assert(err, Equals, ErrUnconfigured)
421+}
422+
423+func (s *PdSuite) TestRequestWakelockFails(c *C) {
424+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
425+ pd := New(endp, s.log)
426+ _, err := pd.RequestWakelock("name")
427+ c.Assert(err, NotNil)
428+}
429+
430+func (s *PdSuite) TestClearWakelockWorks(c *C) {
431+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
432+ pd := New(endp, s.log)
433+ err := pd.ClearWakelock("cookie")
434+ c.Assert(err, IsNil)
435+ args := testibus.GetCallArgs(endp)
436+ c.Assert(args, HasLen, 1)
437+ c.Check(args[0].Member, Equals, "clearSysState")
438+ c.Check(args[0].Args, DeepEquals, []interface{}{"cookie"})
439+}
440+
441+func (s *PdSuite) TestClearWakelockUnconfigured(c *C) {
442+ c.Check(new(powerd).ClearWakelock("cookie"), NotNil)
443+}
444+
445+func (s *PdSuite) TestClearWakelockFails(c *C) {
446+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
447+ pd := New(endp, s.log)
448+ err := pd.ClearWakelock("cookie")
449+ c.Assert(err, NotNil)
450+}
451+
452+func (s *PdSuite) TestWatchWakeupsWorks(c *C) {
453+ endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{})
454+ pd := New(endp, s.log)
455+ ch, err := pd.WatchWakeups()
456+ c.Assert(err, IsNil)
457+ select {
458+ case b := <-ch:
459+ c.Check(b, Equals, true)
460+ case <-time.After(100 * time.Millisecond):
461+ c.Error("timeout waiting for bool")
462+ }
463+ select {
464+ case b := <-ch:
465+ c.Check(b, Equals, false)
466+ case <-time.After(100 * time.Millisecond):
467+ c.Error("timeout waiting for close")
468+ }
469+}
470+
471+func (s *PdSuite) TestWatchWakeupsUnconfigured(c *C) {
472+ _, err := new(powerd).WatchWakeups()
473+ c.Check(err, Equals, ErrUnconfigured)
474+}
475
476=== modified file 'click/click.go'
477--- click/click.go 2014-08-06 02:44:06 +0000
478+++ click/click.go 2014-08-25 16:38:20 +0000
479@@ -136,6 +136,19 @@
480 return cappinfo.AppIconFromDesktopId(app.DesktopId())
481 }
482
483+func _symbolic(icon string) string {
484+ if strings.ContainsRune(icon, '/') {
485+ return icon
486+ }
487+ return icon + "-symbolic"
488+}
489+
490+var symbolic = _symbolic
491+
492+func (app *AppId) SymbolicIcon() string {
493+ return symbolic(app.Icon())
494+}
495+
496 func (app *AppId) MarshalJSON() ([]byte, error) {
497 return json.Marshal(app.Original())
498 }
499
500=== modified file 'click/click_test.go'
501--- click/click_test.go 2014-08-06 03:50:14 +0000
502+++ click/click_test.go 2014-08-25 16:38:20 +0000
503@@ -182,3 +182,21 @@
504 c.Check(app, NotNil)
505 c.Check(app.Original(), Equals, "_non-existent-app")
506 }
507+
508+func (s *clickSuite) TestSymbolicAppendsSymbolicIfIconIsName(c *C) {
509+ symb := symbolic("foo")
510+ c.Check(symb, Equals, "foo-symbolic")
511+}
512+
513+func (s *clickSuite) TestSymbolicLeavesAloneIfIconIsPath(c *C) {
514+ symb := symbolic("foo/bar")
515+ c.Check(symb, Equals, "foo/bar")
516+}
517+
518+func (s *clickSuite) TestSymbolicIconCallsSymbolic(c *C) {
519+ symbolic = func(string) string { return "xyzzy" }
520+ defer func() { symbolic = _symbolic }()
521+ app, err := ParseAppId("_python3.4")
522+ c.Assert(err, IsNil)
523+ c.Check(app.SymbolicIcon(), Equals, "xyzzy")
524+}
525
526=== modified file 'client/client.go'
527--- client/client.go 2014-08-12 00:32:32 +0000
528+++ client/client.go 2014-08-25 16:38:20 +0000
529@@ -44,6 +44,7 @@
530 "launchpad.net/ubuntu-push/identifier"
531 "launchpad.net/ubuntu-push/launch_helper"
532 "launchpad.net/ubuntu-push/logger"
533+ "launchpad.net/ubuntu-push/poller"
534 "launchpad.net/ubuntu-push/protocol"
535 "launchpad.net/ubuntu-push/util"
536 )
537@@ -71,6 +72,12 @@
538 // fallback values for simplified notification usage
539 FallbackVibration *launch_helper.Vibration `json:"fallback_vibration"`
540 FallbackSound string `json:"fallback_sound"`
541+ // times for the poller
542+ PollInterval config.ConfigTimeDuration `json:"poll_interval"`
543+ PollSettle config.ConfigTimeDuration `json:"poll_settle"`
544+ PollNetworkWait config.ConfigTimeDuration `json:"poll_net_wait"`
545+ PollPolldWait config.ConfigTimeDuration `json:"poll_polld_wait"`
546+ PollDoneWait config.ConfigTimeDuration `json:"poll_done_wait"`
547 }
548
549 // PushService is the interface we use of service.PushService.
550@@ -115,6 +122,7 @@
551 unregisterCh chan *click.AppId
552 trackAddressees map[string]*click.AppId
553 installedChecker click.InstalledChecker
554+ poller poller.Poller
555 }
556
557 // Creates a new Ubuntu Push Notifications client-side daemon that will use
558@@ -221,6 +229,21 @@
559 }
560 }
561
562+// derivePollerSetup derives the Poller setup from the client configuration bits.
563+func (client *PushClient) derivePollerSetup() *poller.PollerSetup {
564+ return &poller.PollerSetup{
565+ Times: poller.Times{
566+ AlarmInterval: client.config.PollInterval.TimeDuration(),
567+ SessionStateSettle: client.config.PollSettle.TimeDuration(),
568+ NetworkWait: client.config.PollNetworkWait.TimeDuration(),
569+ PolldWait: client.config.PollPolldWait.TimeDuration(),
570+ DoneWait: client.config.PollDoneWait.TimeDuration(),
571+ },
572+ Log: client.log,
573+ SessionStateGetter: client.session,
574+ }
575+}
576+
577 // getAuthorization gets the authorization blob to send to the server
578 func (client *PushClient) getAuthorization(url string) string {
579 client.log.Debugf("getting authorization for %s", url)
580@@ -266,8 +289,8 @@
581 return err
582 }
583
584-// initSession creates the session object
585-func (client *PushClient) initSession() error {
586+// initSessionAndPoller creates the session and the poller objects
587+func (client *PushClient) initSessionAndPoller() error {
588 info := map[string]interface{}{
589 "device": client.systemImageInfo.Device,
590 "channel": client.systemImageInfo.Channel,
591@@ -280,6 +303,18 @@
592 return err
593 }
594 client.session = sess
595+ client.poller = poller.New(client.derivePollerSetup())
596+ return nil
597+}
598+
599+// runPoller starts and runs the poller
600+func (client *PushClient) runPoller() error {
601+ if err := client.poller.Start(); err != nil {
602+ return err
603+ }
604+ if err := client.poller.Run(); err != nil {
605+ return err
606+ }
607 return nil
608 }
609
610@@ -509,6 +544,7 @@
611 client.startPushService,
612 client.startPostalService,
613 client.takeTheBus,
614- client.initSession,
615+ client.initSessionAndPoller,
616+ client.runPoller,
617 )
618 }
619
620=== modified file 'client/client_test.go'
621--- client/client_test.go 2014-08-12 00:32:32 +0000
622+++ client/client_test.go 2014-08-25 16:38:20 +0000
623@@ -45,6 +45,7 @@
624 "launchpad.net/ubuntu-push/identifier"
625 idtesting "launchpad.net/ubuntu-push/identifier/testing"
626 "launchpad.net/ubuntu-push/launch_helper"
627+ "launchpad.net/ubuntu-push/poller"
628 "launchpad.net/ubuntu-push/protocol"
629 helpers "launchpad.net/ubuntu-push/testing"
630 "launchpad.net/ubuntu-push/testing/condition"
631@@ -143,7 +144,11 @@
632
633 func (cs *clientSuite) SetUpSuite(c *C) {
634 config.IgnoreParsedFlags = true // because configure() uses <flags>
635- newIdentifier = func() (identifier.Id, error) { return idtesting.Settable(), nil }
636+ newIdentifier = func() (identifier.Id, error) {
637+ id := idtesting.Settable()
638+ id.Set("42") // must be hex of len 32
639+ return id, nil
640+ }
641 cs.timeouts = util.SwapTimeouts([]time.Duration{0})
642 cs.leveldbPath = ""
643 }
644@@ -173,6 +178,11 @@
645 "session_url": "xyzzy://",
646 "registration_url": "reg://",
647 "log_level": "debug",
648+ "poll_interval": "5m",
649+ "poll_settle": "20ms",
650+ "poll_net_wait": "1m",
651+ "poll_polld_wait": "3m",
652+ "poll_done_wait": "5s",
653 }
654 for k, v := range overrides {
655 cfgMap[k] = v
656@@ -502,6 +512,39 @@
657 }
658
659 /*****************************************************************
660+ derivePollerSetup tests
661+******************************************************************/
662+func (cs *clientSuite) TestDerivePollerSetup(c *C) {
663+ cs.writeTestConfig(map[string]interface{}{})
664+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
665+ cli.session = new(session.ClientSession)
666+ err := cli.configure()
667+ c.Assert(err, IsNil)
668+ expected := &poller.PollerSetup{
669+ Times: poller.Times{
670+ AlarmInterval: 5 * time.Minute,
671+ SessionStateSettle: 20 * time.Millisecond,
672+ NetworkWait: time.Minute,
673+ PolldWait: 3 * time.Minute,
674+ DoneWait: 5 * time.Second,
675+ },
676+ Log: cli.log,
677+ SessionStateGetter: cli.session,
678+ }
679+ // sanity check that we are looking at all fields
680+ vExpected := reflect.ValueOf(expected).Elem()
681+ nf := vExpected.NumField()
682+ for i := 0; i < nf; i++ {
683+ fv := vExpected.Field(i)
684+ // field isn't empty/zero
685+ c.Assert(fv.Interface(), Not(DeepEquals), reflect.Zero(fv.Type()).Interface(), Commentf("forgot about: %s", vExpected.Type().Field(i).Name))
686+ }
687+ // finally compare
688+ setup := cli.derivePollerSetup()
689+ c.Check(setup, DeepEquals, expected)
690+}
691+
692+/*****************************************************************
693 startService tests
694 ******************************************************************/
695
696@@ -653,7 +696,7 @@
697 cli := NewPushClient(cs.configPath, cs.leveldbPath)
698 cli.log = cs.log
699 cli.systemImageInfo = siInfoRes
700- c.Assert(cli.initSession(), IsNil)
701+ c.Assert(cli.initSessionAndPoller(), IsNil)
702 cs.log.ResetCapture()
703 cli.hasConnectivity = true
704 cli.handleErr(errors.New("bananas"))
705@@ -686,7 +729,7 @@
706 cli := NewPushClient(cs.configPath, cs.leveldbPath)
707 cli.log = cs.log
708 cli.systemImageInfo = siInfoRes
709- c.Assert(cli.initSession(), IsNil)
710+ c.Assert(cli.initSessionAndPoller(), IsNil)
711
712 c.Assert(cli.hasConnectivity, Equals, false)
713 cli.handleConnState(true)
714@@ -958,7 +1001,7 @@
715 cli.systemImageInfo = siInfoRes
716 cli.connCh = make(chan bool, 1)
717 cli.connCh <- true
718- c.Assert(cli.initSession(), IsNil)
719+ c.Assert(cli.initSessionAndPoller(), IsNil)
720
721 ch := make(chan bool, 1)
722 go cli.doLoop(func(bool) { ch <- true }, nopBcast, nopUcast, nopError, nopUnregister)
723@@ -969,7 +1012,7 @@
724 cli := NewPushClient(cs.configPath, cs.leveldbPath)
725 cli.log = cs.log
726 cli.systemImageInfo = siInfoRes
727- c.Assert(cli.initSession(), IsNil)
728+ c.Assert(cli.initSessionAndPoller(), IsNil)
729 cli.session.BroadcastCh = make(chan *session.BroadcastNotification, 1)
730 cli.session.BroadcastCh <- &session.BroadcastNotification{}
731
732@@ -982,7 +1025,7 @@
733 cli := NewPushClient(cs.configPath, cs.leveldbPath)
734 cli.log = cs.log
735 cli.systemImageInfo = siInfoRes
736- c.Assert(cli.initSession(), IsNil)
737+ c.Assert(cli.initSessionAndPoller(), IsNil)
738 cli.session.NotificationsCh = make(chan session.AddressedNotification, 1)
739 cli.session.NotificationsCh <- session.AddressedNotification{}
740
741@@ -995,7 +1038,7 @@
742 cli := NewPushClient(cs.configPath, cs.leveldbPath)
743 cli.log = cs.log
744 cli.systemImageInfo = siInfoRes
745- c.Assert(cli.initSession(), IsNil)
746+ c.Assert(cli.initSessionAndPoller(), IsNil)
747 cli.session.ErrCh = make(chan error, 1)
748 cli.session.ErrCh <- nil
749
750@@ -1008,7 +1051,7 @@
751 cli := NewPushClient(cs.configPath, cs.leveldbPath)
752 cli.log = cs.log
753 cli.systemImageInfo = siInfoRes
754- c.Assert(cli.initSession(), IsNil)
755+ c.Assert(cli.initSessionAndPoller(), IsNil)
756 cli.unregisterCh = make(chan *click.AppId, 1)
757 cli.unregisterCh <- app1
758
759@@ -1060,7 +1103,7 @@
760 cli.postalService = d
761 c.Assert(cli.startPostalService(), IsNil)
762
763- c.Assert(cli.initSession(), IsNil)
764+ c.Assert(cli.initSessionAndPoller(), IsNil)
765
766 cli.session.BroadcastCh = make(chan *session.BroadcastNotification)
767 cli.session.ErrCh = make(chan error)
768@@ -1141,7 +1184,7 @@
769 // and now everthing is better! We have a config,
770 c.Check(string(cli.config.Addr), Equals, ":0")
771 // and a device id,
772- c.Check(cli.deviceId, HasLen, 32)
773+ c.Check(cli.deviceId, HasLen, 40)
774 // and a session,
775 c.Check(cli.session, NotNil)
776 // and a bus,
777@@ -1161,13 +1204,13 @@
778 c.Check(err, NotNil)
779 }
780
781-func (cs *clientSuite) TestInitSessionErr(c *C) {
782+func (cs *clientSuite) TestinitSessionAndPollerErr(c *C) {
783 cli := NewPushClient(cs.configPath, cs.leveldbPath)
784 cli.log = cs.log
785 cli.systemImageInfo = siInfoRes
786- // change the cli.pem value so initSession fails
787+ // change the cli.pem value so initSessionAndPoller fails
788 cli.pem = []byte("foo")
789- c.Assert(cli.initSession(), NotNil)
790+ c.Assert(cli.initSessionAndPoller(), NotNil)
791 }
792
793 /*****************************************************************
794
795=== modified file 'debian/changelog'
796--- debian/changelog 2014-08-12 02:33:16 +0000
797+++ debian/changelog 2014-08-25 16:38:20 +0000
798@@ -1,3 +1,18 @@
799+ubuntu-push (0.62) UNRELEASED; urgency=medium
800+
801+ [ Samuele Pedroni ]
802+ * Avoid rare race in kindpool_test.go
803+
804+ [ John R. Lenton]
805+ * Interface with account-polld's dbus api.
806+ * Powerd integration.
807+ * Use symbolic icon for secondary icon in notification.
808+
809+ [Roberto Alsina]
810+ * Log legacy helper failures.
811+
812+ -- Roberto Alsina <ralsina@yoga> Fri, 22 Aug 2014 16:00:31 -0300
813+
814 ubuntu-push (0.61+14.10.20140812.4-0ubuntu1) utopic; urgency=medium
815
816 [ Guillermo Gonzalez ]
817@@ -13,7 +28,7 @@
818 * Make messaging menu entries show current time instead of epoch for timestamp of 0.
819 * Tweak the upstart script, start after unity.
820 * Correctly report invalid app ids, missing apps, and package/app id mismatches as separate errors over dbus.
821-
822+
823 [Roberto Alsina]
824 * Check that sound paths don't go up into the tree.
825 * Initial draft of QML-based doc
826
827=== modified file 'debian/config.json'
828--- debian/config.json 2014-08-08 09:03:42 +0000
829+++ debian/config.json 2014-08-25 16:38:20 +0000
830@@ -14,5 +14,10 @@
831 "connectivity_check_md5": "4589f42e1546aa47ca181e5d949d310b",
832 "log_level": "debug",
833 "fallback_vibration": {"pattern": [100, 100], "repeat": 2},
834- "fallback_sound": "sounds/ubuntu/notifications/Slick.ogg"
835+ "fallback_sound": "sounds/ubuntu/notifications/Slick.ogg",
836+ "poll_interval": "5m",
837+ "poll_settle": "20ms",
838+ "poll_net_wait": "1m",
839+ "poll_polld_wait": "3m",
840+ "poll_done_wait": "5s"
841 }
842
843=== modified file 'dependencies.tsv'
844--- dependencies.tsv 2014-07-26 07:28:59 +0000
845+++ dependencies.tsv 2014-08-25 16:38:20 +0000
846@@ -1,5 +1,5 @@
847 code.google.com/p/go-uuid hg 7dda39b2e7d5e265014674c5af696ba4186679e9 11
848 code.google.com/p/gosqlite hg 74691fb6f83716190870cde1b658538dd4b18eb0 15
849-launchpad.net/go-dbus/v1 bzr james@jamesh.id.au-20140530132806-hpqbkxczif6o1dpz 127
850+launchpad.net/go-dbus/v1 bzr james@jamesh.id.au-20140801110939-lzfql7fk76dt6ckd 128
851 launchpad.net/go-xdg/v0 bzr john.lenton@canonical.com-20140208094800-gubd5md7cro3mtxa 10
852 launchpad.net/gocheck bzr gustavo@niemeyer.net-20140225173054-xu9zlkf9kxhvow02 87
853
854=== modified file 'launch_helper/kindpool.go'
855--- launch_helper/kindpool.go 2014-07-29 15:24:01 +0000
856+++ launch_helper/kindpool.go 2014-08-25 16:38:20 +0000
857@@ -72,7 +72,7 @@
858 func DefaultLaunchers(log logger.Logger) map[string]HelperLauncher {
859 return map[string]HelperLauncher{
860 "click": cual.New(log),
861- "legacy": legacy.New(),
862+ "legacy": legacy.New(log),
863 }
864 }
865
866
867=== modified file 'launch_helper/kindpool_test.go'
868--- launch_helper/kindpool_test.go 2014-08-08 09:03:42 +0000
869+++ launch_helper/kindpool_test.go 2014-08-25 16:38:20 +0000
870@@ -102,16 +102,22 @@
871 return args
872 }
873
874+func (s *poolSuite) SetUpSuite(c *C) {
875+ xdgCacheHome = c.MkDir
876+}
877+
878+func (s *poolSuite) TearDownSuite(c *C) {
879+ xdgCacheHome = xdg.Cache.Home
880+}
881+
882 func (s *poolSuite) SetUpTest(c *C) {
883 s.log = helpers.NewTestLogger(c, "debug")
884 s.fakeLauncher = &fakeHelperLauncher{argCh: make(chan [5]string, 10)}
885 s.pool = NewHelperPool(map[string]HelperLauncher{"fake": s.fakeLauncher}, s.log)
886- xdgCacheHome = c.MkDir
887 }
888
889 func (s *poolSuite) TearDownTest(c *C) {
890 s.pool = nil
891- xdgCacheHome = xdg.Cache.Home
892 }
893
894 func (s *poolSuite) TestDefaultLaunchers(c *C) {
895@@ -379,8 +385,9 @@
896 }
897
898 func (s *poolSuite) TestGetTempFilename(c *C) {
899+ tmpDir := c.MkDir()
900 GetTempDir = func(pkgName string) (string, error) {
901- return c.MkDir(), nil
902+ return tmpDir, nil
903 }
904 // restore it when we are done
905 defer func() {
906
907=== modified file 'launch_helper/legacy/legacy.go'
908--- launch_helper/legacy/legacy.go 2014-07-18 20:45:21 +0000
909+++ launch_helper/legacy/legacy.go 2014-08-25 16:38:20 +0000
910@@ -18,20 +18,23 @@
911 package legacy
912
913 import (
914+ "bytes"
915 "os"
916 "os/exec"
917 "path/filepath"
918 "strconv"
919
920 "launchpad.net/ubuntu-push/click"
921+ "launchpad.net/ubuntu-push/logger"
922 )
923
924 type legacyHelperLauncher struct {
925+ log logger.Logger
926 done func(string)
927 }
928
929-func New() *legacyHelperLauncher {
930- return new(legacyHelperLauncher)
931+func New(log logger.Logger) *legacyHelperLauncher {
932+ return &legacyHelperLauncher{log: log}
933 }
934
935 func (lhl *legacyHelperLauncher) InstallObserver(done func(string)) error {
936@@ -47,27 +50,42 @@
937
938 func (*legacyHelperLauncher) RemoveObserver() error { return nil }
939
940+type msg struct {
941+ id string
942+ err error
943+}
944+
945 func (lhl *legacyHelperLauncher) Launch(_, progname, f1, f2 string) (string, error) {
946- cmd := exec.Command(progname, f1, f2)
947- cmd.Stdin = nil
948- cmd.Stdout = nil
949- cmd.Stderr = nil
950+ comm := make(chan msg)
951
952- err := cmd.Start()
953- if err != nil {
954- return "", err
955- }
956- proc := cmd.Process
957- if proc == nil {
958- panic("cmd.Process is nil after successful cmd.Start()??")
959- }
960- id := strconv.FormatInt((int64)(proc.Pid), 36)
961 go func() {
962- proc.Wait()
963+ cmd := exec.Command(progname, f1, f2)
964+ var stdout bytes.Buffer
965+ cmd.Stdout = &stdout
966+ var stderr bytes.Buffer
967+ cmd.Stderr = &stderr
968+ err := cmd.Start()
969+ if err != nil {
970+ comm <- msg{"", err}
971+ return
972+ }
973+ proc := cmd.Process
974+ if proc == nil {
975+ panic("cmd.Process is nil after successful cmd.Start()??")
976+ }
977+ id := strconv.FormatInt((int64)(proc.Pid), 36)
978+ comm <- msg{id, nil}
979+ p_err := cmd.Wait()
980+ if p_err != nil {
981+ // Helper failed or got killed, log output/errors
982+ lhl.log.Errorf("Legacy helper failed: %v", p_err)
983+ lhl.log.Errorf("Legacy helper failed. Stdout: %s", stdout)
984+ lhl.log.Errorf("Legacy helper failed. Stderr: %s", stderr)
985+ }
986 lhl.done(id)
987 }()
988-
989- return id, nil
990+ msg := <-comm
991+ return msg.id, msg.err
992 }
993
994 func (lhl *legacyHelperLauncher) Stop(_, id string) error {
995
996=== modified file 'launch_helper/legacy/legacy_test.go'
997--- launch_helper/legacy/legacy_test.go 2014-07-18 20:45:21 +0000
998+++ launch_helper/legacy/legacy_test.go 2014-08-25 16:38:20 +0000
999@@ -32,7 +32,7 @@
1000 select {
1001 case s := <-ch:
1002 return s
1003- case <-time.After(time.Second):
1004+ case <-time.After(5 * time.Second):
1005 c.Fatal("timed out waiting for value")
1006 return ""
1007 }
1008@@ -42,12 +42,14 @@
1009
1010 type legacySuite struct {
1011 lhl *legacyHelperLauncher
1012+ log *helpers.TestLogger
1013 }
1014
1015 var _ = Suite(&legacySuite{})
1016
1017 func (ls *legacySuite) SetUpTest(c *C) {
1018- ls.lhl = New()
1019+ ls.log = helpers.NewTestLogger(c, "info")
1020+ ls.lhl = New(ls.log)
1021 }
1022
1023 func (ls *legacySuite) TestInstallObserver(c *C) {
1024@@ -94,12 +96,39 @@
1025 c.Assert(err, NotNil)
1026 }
1027
1028+func (ls *legacySuite) TestHelperFails(c *C) {
1029+ ch := make(chan string, 1)
1030+ c.Assert(ls.lhl.InstallObserver(func(id string) { ch <- id }), IsNil)
1031+
1032+ _, err := ls.lhl.Launch("", "/bin/false", "", "")
1033+ c.Assert(err, IsNil)
1034+
1035+ takeNext(ch, c)
1036+ c.Check(ls.log.Captured(), Matches, "(?s).*Legacy helper failed.*")
1037+}
1038+
1039+func (ls *legacySuite) TestHelperFailsLog(c *C) {
1040+ ch := make(chan string, 1)
1041+ c.Assert(ls.lhl.InstallObserver(func(id string) { ch <- id }), IsNil)
1042+
1043+ exe := helpers.ScriptAbsPath("noisy-helper.sh")
1044+ _, err := ls.lhl.Launch("", exe, "", "")
1045+ c.Assert(err, IsNil)
1046+
1047+ takeNext(ch, c)
1048+ c.Check(ls.log.Captured(), Matches, "(?s).*BOOM-1.*")
1049+ c.Check(ls.log.Captured(), Matches, "(?s).*BANG-1.*")
1050+ c.Check(ls.log.Captured(), Matches, "(?s).*BOOM-20.*")
1051+ c.Check(ls.log.Captured(), Matches, "(?s).*BANG-20.*")
1052+}
1053+
1054 func (ls *legacySuite) TestStop(c *C) {
1055 ch := make(chan string, 1)
1056 c.Assert(ls.lhl.InstallObserver(func(id string) { ch <- id }), IsNil)
1057
1058- exe := helpers.ScriptAbsPath("slow-helper.sh")
1059- id, err := ls.lhl.Launch("", exe, "", "")
1060+ // exe := helpers.ScriptAbsPath("slow-helper.sh")
1061+ id, err := ls.lhl.Launch("", "/bin/sleep", "9", "1")
1062+ c.Assert(err, IsNil)
1063
1064 err = ls.lhl.Stop("", "===")
1065 c.Check(err, NotNil) // not a valid id
1066
1067=== added directory 'poller'
1068=== added file 'poller/poller.go'
1069--- poller/poller.go 1970-01-01 00:00:00 +0000
1070+++ poller/poller.go 2014-08-25 16:38:20 +0000
1071@@ -0,0 +1,220 @@
1072+/*
1073+ Copyright 2014 Canonical Ltd.
1074+
1075+ This program is free software: you can redistribute it and/or modify it
1076+ under the terms of the GNU General Public License version 3, as published
1077+ by the Free Software Foundation.
1078+
1079+ This program is distributed in the hope that it will be useful, but
1080+ WITHOUT ANY WARRANTY; without even the implied warranties of
1081+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1082+ PURPOSE. See the GNU General Public License for more details.
1083+
1084+ You should have received a copy of the GNU General Public License along
1085+ with this program. If not, see <http://www.gnu.org/licenses/>.
1086+*/
1087+
1088+// Package poller implements Poller, a thing that uses (hw) alarms to
1089+// wake the device up from deep sleep periodically, check for
1090+// notifications, and poke polld.
1091+package poller
1092+
1093+import (
1094+ "errors"
1095+ "sync"
1096+ "time"
1097+
1098+ "launchpad.net/ubuntu-push/bus"
1099+ "launchpad.net/ubuntu-push/bus/polld"
1100+ "launchpad.net/ubuntu-push/bus/powerd"
1101+ "launchpad.net/ubuntu-push/client/session"
1102+ "launchpad.net/ubuntu-push/logger"
1103+ "launchpad.net/ubuntu-push/util"
1104+)
1105+
1106+var (
1107+ ErrUnconfigured = errors.New("not configured")
1108+ ErrAlreadyStarted = errors.New("already started")
1109+ ErrNotStarted = errors.New("not started")
1110+)
1111+
1112+type stater interface {
1113+ State() session.ClientSessionState
1114+}
1115+
1116+type Times struct {
1117+ AlarmInterval time.Duration
1118+ SessionStateSettle time.Duration
1119+ NetworkWait time.Duration
1120+ PolldWait time.Duration
1121+ DoneWait time.Duration
1122+}
1123+
1124+type Poller interface {
1125+ IsConnected() bool
1126+ Start() error
1127+ Run() error
1128+}
1129+
1130+type PollerSetup struct {
1131+ Times Times
1132+ Log logger.Logger
1133+ SessionStateGetter stater
1134+}
1135+
1136+type poller struct {
1137+ times Times
1138+ log logger.Logger
1139+ powerd powerd.Powerd
1140+ polld polld.Polld
1141+ cookie string
1142+ sessionState stater
1143+}
1144+
1145+func New(setup *PollerSetup) Poller {
1146+ return &poller{
1147+ times: setup.Times,
1148+ log: setup.Log,
1149+ powerd: nil,
1150+ polld: nil,
1151+ sessionState: setup.SessionStateGetter,
1152+ }
1153+}
1154+
1155+func (p *poller) IsConnected() bool {
1156+ return p.sessionState.State() == session.Running
1157+}
1158+
1159+func (p *poller) Start() error {
1160+ if p.log == nil {
1161+ return ErrUnconfigured
1162+ }
1163+ if p.powerd != nil || p.polld != nil {
1164+ return ErrAlreadyStarted
1165+ }
1166+ powerdEndp := bus.SystemBus.Endpoint(powerd.BusAddress, p.log)
1167+ polldEndp := bus.SessionBus.Endpoint(polld.BusAddress, p.log)
1168+ var wg sync.WaitGroup
1169+ wg.Add(2)
1170+ go func() {
1171+ n := util.NewAutoRedialer(powerdEndp).Redial()
1172+ p.log.Debugf("powerd dialed on try %d", n)
1173+ wg.Done()
1174+ }()
1175+ go func() {
1176+ n := util.NewAutoRedialer(polldEndp).Redial()
1177+ p.log.Debugf("polld dialed in on try %d", n)
1178+ wg.Done()
1179+ }()
1180+ wg.Wait()
1181+
1182+ p.powerd = powerd.New(powerdEndp, p.log)
1183+ p.polld = polld.New(polldEndp, p.log)
1184+
1185+ return nil
1186+}
1187+
1188+func (p *poller) Run() error {
1189+ if p.log == nil {
1190+ return ErrUnconfigured
1191+ }
1192+ if p.powerd == nil || p.polld == nil {
1193+ return ErrNotStarted
1194+ }
1195+ wakeupCh, err := p.powerd.WatchWakeups()
1196+ if err != nil {
1197+ return err
1198+ }
1199+ doneCh, err := p.polld.WatchDones()
1200+ if err != nil {
1201+ return err
1202+ }
1203+ go p.run(wakeupCh, doneCh)
1204+ return nil
1205+}
1206+
1207+func (p *poller) run(wakeupCh <-chan bool, doneCh <-chan bool) {
1208+ var lockCookie string
1209+
1210+ for {
1211+ lockCookie = p.step(wakeupCh, doneCh, lockCookie)
1212+ }
1213+}
1214+
1215+func (p *poller) step(wakeupCh <-chan bool, doneCh <-chan bool, lockCookie string) string {
1216+
1217+ t := time.Now().Add(p.times.AlarmInterval).Truncate(time.Second)
1218+ _, err := p.powerd.RequestWakeup("ubuntu push client", t)
1219+ if err != nil {
1220+ p.log.Errorf("RequestWakeup got %v", err)
1221+ return lockCookie
1222+ }
1223+ p.log.Debugf("requested wakeup at %s", t)
1224+ if lockCookie != "" {
1225+ if err := p.powerd.ClearWakelock(lockCookie); err != nil {
1226+ p.log.Errorf("ClearWakelock(%#v) got %v", lockCookie, err)
1227+ } else {
1228+ p.log.Debugf("cleared wakelock cookie %s.", lockCookie)
1229+ }
1230+ lockCookie = ""
1231+ }
1232+ for b := range wakeupCh {
1233+ if !b {
1234+ panic("WatchWakeups channel produced a false value (??)")
1235+ }
1236+ // the channel will produce a true for every
1237+ // wakeup, not only the one we asked for
1238+ now := time.Now()
1239+ p.log.Debugf("got woken up; time is %s", now)
1240+ if !now.Before(t) {
1241+ break
1242+ }
1243+ }
1244+ lockCookie, err = p.powerd.RequestWakelock("ubuntu push client")
1245+ if err != nil {
1246+ p.log.Errorf("RequestWakelock got %v", err)
1247+ return lockCookie
1248+ }
1249+ p.log.Debugf("got wakelock cookie of %s", lockCookie)
1250+ time.Sleep(p.times.SessionStateSettle)
1251+ for i := 0; i < 20; i++ {
1252+ if p.IsConnected() {
1253+ break
1254+ }
1255+ time.Sleep(p.times.NetworkWait / 20)
1256+ }
1257+ if !p.IsConnected() {
1258+ p.log.Errorf("not connected after %s; giving up", p.times.NetworkWait)
1259+ } else {
1260+ p.log.Debugf("poking polld.")
1261+ // drain the doneCH
1262+ drain:
1263+ for {
1264+ select {
1265+ case <-doneCh:
1266+ default:
1267+ break drain
1268+ }
1269+ }
1270+
1271+ if err := p.polld.Poll(); err != nil {
1272+ p.log.Errorf("Poll got %v", err)
1273+ } else {
1274+ p.log.Debugf("waiting for polld to signal Done.")
1275+ select {
1276+ case b := <-doneCh:
1277+ if !b {
1278+ panic("WatchDones channel produced a false value (??)")
1279+ }
1280+ p.log.Debugf("polld Done.")
1281+ case <-time.After(p.times.PolldWait):
1282+ p.log.Errorf("polld still not done after %s; giving up", p.times.PolldWait)
1283+ }
1284+ }
1285+
1286+ // XXX check whether something was actually done before waiting
1287+ time.Sleep(p.times.DoneWait)
1288+ }
1289+
1290+ return lockCookie
1291+}
1292
1293=== added file 'poller/poller_test.go'
1294--- poller/poller_test.go 1970-01-01 00:00:00 +0000
1295+++ poller/poller_test.go 2014-08-25 16:38:20 +0000
1296@@ -0,0 +1,114 @@
1297+/*
1298+ Copyright 2014 Canonical Ltd.
1299+
1300+ This program is free software: you can redistribute it and/or modify it
1301+ under the terms of the GNU General Public License version 3, as published
1302+ by the Free Software Foundation.
1303+
1304+ This program is distributed in the hope that it will be useful, but
1305+ WITHOUT ANY WARRANTY; without even the implied warranties of
1306+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1307+ PURPOSE. See the GNU General Public License for more details.
1308+
1309+ You should have received a copy of the GNU General Public License along
1310+ with this program. If not, see <http://www.gnu.org/licenses/>.
1311+*/
1312+package poller
1313+
1314+import (
1315+ "testing"
1316+ "time"
1317+
1318+ . "launchpad.net/gocheck"
1319+
1320+ "launchpad.net/ubuntu-push/client/session"
1321+ helpers "launchpad.net/ubuntu-push/testing"
1322+)
1323+
1324+// hook up gocheck
1325+func TestPoller(t *testing.T) { TestingT(t) }
1326+
1327+type PrSuite struct {
1328+ log *helpers.TestLogger
1329+ myd *myD
1330+}
1331+
1332+var _ = Suite(&PrSuite{})
1333+
1334+type myD struct {
1335+ // in/out for RequestWakeup
1336+ reqWakeName string
1337+ reqWakeTime time.Time
1338+ reqWakeCookie string
1339+ reqWakeErr error
1340+ // WatchWakeups
1341+ watchWakeCh <-chan bool
1342+ watchWakeErr error
1343+ // RequestWakelock
1344+ reqLockName string
1345+ reqLockCookie string
1346+ reqLockErr error
1347+ // ClearWakelock
1348+ clearLockCookie string
1349+ clearLockErr error
1350+ // Poll
1351+ pollErr error
1352+ // WatchDones
1353+ watchDonesCh <-chan bool
1354+ watchDonesErr error
1355+ // State
1356+ stateState session.ClientSessionState
1357+}
1358+
1359+func (m *myD) RequestWakeup(name string, wakeupTime time.Time) (string, error) {
1360+ m.reqWakeName = name
1361+ m.reqWakeTime = wakeupTime
1362+ return m.reqWakeCookie, m.reqWakeErr
1363+}
1364+func (m *myD) RequestWakelock(name string) (string, error) {
1365+ m.reqLockName = name
1366+ return m.reqLockCookie, m.reqLockErr
1367+}
1368+func (m *myD) ClearWakelock(cookie string) error {
1369+ m.clearLockCookie = cookie
1370+ return m.clearLockErr
1371+}
1372+func (m *myD) ClearWakeup(cookie string) error { panic("clearwakeup called??") }
1373+func (m *myD) WatchWakeups() (<-chan bool, error) { return m.watchWakeCh, m.watchWakeErr }
1374+func (m *myD) Poll() error { return m.pollErr }
1375+func (m *myD) WatchDones() (<-chan bool, error) { return m.watchDonesCh, m.watchDonesErr }
1376+func (m *myD) State() session.ClientSessionState { return m.stateState }
1377+
1378+func (s *PrSuite) SetUpTest(c *C) {
1379+ s.log = helpers.NewTestLogger(c, "debug")
1380+ s.myd = &myD{}
1381+}
1382+
1383+func (s *PrSuite) TestStep(c *C) {
1384+ p := &poller{
1385+ times: Times{},
1386+ log: s.log,
1387+ powerd: s.myd,
1388+ polld: s.myd,
1389+ sessionState: s.myd,
1390+ }
1391+ s.myd.reqLockCookie = "wakelock cookie"
1392+ s.myd.stateState = session.Running
1393+ // we'll get the wakeup right away
1394+ wakeupCh := make(chan bool, 1)
1395+ wakeupCh <- true
1396+ // we won't get the "done" signal in time ;)
1397+ doneCh := make(chan bool)
1398+ // and a channel to get the return value from a goroutine
1399+ ch := make(chan string)
1400+ // now, run
1401+ go func() { ch <- p.step(wakeupCh, doneCh, "old cookie") }()
1402+ select {
1403+ case s := <-ch:
1404+ c.Check(s, Equals, "wakelock cookie")
1405+ case <-time.After(time.Second):
1406+ c.Fatal("timeout waiting for step")
1407+ }
1408+ // check we cleared the old cookie
1409+ c.Check(s.myd.clearLockCookie, Equals, "old cookie")
1410+}
1411
1412=== added file 'scripts/noisy-helper.sh'
1413--- scripts/noisy-helper.sh 1970-01-01 00:00:00 +0000
1414+++ scripts/noisy-helper.sh 2014-08-25 16:38:20 +0000
1415@@ -0,0 +1,7 @@
1416+#!/bin/sh
1417+for a in `seq 1 100`
1418+do
1419+echo BOOM-$a
1420+>&2 echo BANG-$a
1421+done
1422+exit 1
1423
1424=== modified file 'server/acceptance/acceptanceclient.go'
1425--- server/acceptance/acceptanceclient.go 2014-06-05 09:32:43 +0000
1426+++ server/acceptance/acceptanceclient.go 2014-08-25 16:38:20 +0000
1427@@ -19,9 +19,7 @@
1428
1429 import (
1430 "crypto/tls"
1431- "crypto/x509"
1432 "encoding/json"
1433- "errors"
1434 "fmt"
1435 "net"
1436 "strings"
1437@@ -40,35 +38,36 @@
1438 ImageChannel string
1439 ServerAddr string
1440 ExchangeTimeout time.Duration
1441- CertPEMBlock []byte
1442 ReportPings bool
1443 Levels map[string]int64
1444- Insecure bool // don't verify certs
1445+ TLSConfig *tls.Config
1446 Prefix string // prefix for events
1447 Auth string
1448 // connection
1449 Connection net.Conn
1450 }
1451
1452-// Dial connects to a server using the configuration in the ClientSession
1453-// and sets up the connection.
1454+// Dial connects to a server using the configuration in the
1455+// ClientSession and sets up the connection.
1456 func (sess *ClientSession) Dial() error {
1457 conn, err := net.DialTimeout("tcp", sess.ServerAddr, sess.ExchangeTimeout)
1458 if err != nil {
1459 return err
1460 }
1461- tlsConfig := &tls.Config{}
1462- if sess.CertPEMBlock != nil {
1463- cp := x509.NewCertPool()
1464- ok := cp.AppendCertsFromPEM(sess.CertPEMBlock)
1465- if !ok {
1466- return errors.New("dial: could not parse certificate")
1467- }
1468- tlsConfig.RootCAs = cp
1469+ sess.TLSWrapAndSet(conn)
1470+ return nil
1471+}
1472+
1473+// TLSWrapAndSet wraps a socket connection in tls and sets it as
1474+// session.Connection. For use instead of Dial().
1475+func (sess *ClientSession) TLSWrapAndSet(conn net.Conn) {
1476+ var tlsConfig *tls.Config
1477+ if sess.TLSConfig != nil {
1478+ tlsConfig = sess.TLSConfig
1479+ } else {
1480+ tlsConfig = &tls.Config{}
1481 }
1482- tlsConfig.InsecureSkipVerify = sess.Insecure
1483 sess.Connection = tls.Client(conn, tlsConfig)
1484- return nil
1485 }
1486
1487 type serverMsg struct {
1488
1489=== added file 'server/acceptance/cmd/acceptanceclient.go'
1490--- server/acceptance/cmd/acceptanceclient.go 1970-01-01 00:00:00 +0000
1491+++ server/acceptance/cmd/acceptanceclient.go 2014-08-25 16:38:20 +0000
1492@@ -0,0 +1,54 @@
1493+/*
1494+ Copyright 2013-2014 Canonical Ltd.
1495+
1496+ This program is free software: you can redistribute it and/or modify it
1497+ under the terms of the GNU General Public License version 3, as published
1498+ by the Free Software Foundation.
1499+
1500+ This program is distributed in the hope that it will be useful, but
1501+ WITHOUT ANY WARRANTY; without even the implied warranties of
1502+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1503+ PURPOSE. See the GNU General Public License for more details.
1504+
1505+ You should have received a copy of the GNU General Public License along
1506+ with this program. If not, see <http://www.gnu.org/licenses/>.
1507+*/
1508+
1509+// acceptanceclient command for playing.
1510+package main
1511+
1512+import (
1513+ "log"
1514+ "os/exec"
1515+ "strings"
1516+
1517+ "launchpad.net/ubuntu-push/server/acceptance"
1518+ "launchpad.net/ubuntu-push/server/acceptance/kit"
1519+)
1520+
1521+type configuration struct {
1522+ kit.Configuration
1523+ AuthHelper string `json:"auth_helper"`
1524+ WaitFor string `json:"wait_for"`
1525+}
1526+
1527+func main() {
1528+ kit.Defaults["auth_helper"] = ""
1529+ kit.Defaults["wait_for"] = ""
1530+ cfg := &configuration{}
1531+ kit.CliLoop(cfg, &cfg.Configuration, func(session *acceptance.ClientSession, cfgDir string) {
1532+ log.Printf("with: %#v", session)
1533+ }, func(url string) string {
1534+ if cfg.AuthHelper == "" {
1535+ return ""
1536+ }
1537+ auth, err := exec.Command(cfg.AuthHelper, url).Output()
1538+ if err != nil {
1539+ log.Fatalf("auth helper: %v", err)
1540+ }
1541+ return strings.TrimSpace(string(auth))
1542+ }, func() string {
1543+ return cfg.WaitFor
1544+ }, func() {
1545+ })
1546+}
1547
1548=== added directory 'server/acceptance/kit'
1549=== added file 'server/acceptance/kit/api.go'
1550--- server/acceptance/kit/api.go 1970-01-01 00:00:00 +0000
1551+++ server/acceptance/kit/api.go 2014-08-25 16:38:20 +0000
1552@@ -0,0 +1,82 @@
1553+/*
1554+ Copyright 2013-2014 Canonical Ltd.
1555+
1556+ This program is free software: you can redistribute it and/or modify it
1557+ under the terms of the GNU General Public License version 3, as published
1558+ by the Free Software Foundation.
1559+
1560+ This program is distributed in the hope that it will be useful, but
1561+ WITHOUT ANY WARRANTY; without even the implied warranties of
1562+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1563+ PURPOSE. See the GNU General Public License for more details.
1564+
1565+ You should have received a copy of the GNU General Public License along
1566+ with this program. If not, see <http://www.gnu.org/licenses/>.
1567+*/
1568+
1569+// Package kit contains reusable building blocks for acceptance.
1570+package kit
1571+
1572+import (
1573+ "bytes"
1574+ "crypto/tls"
1575+ "encoding/json"
1576+ "errors"
1577+ "io/ioutil"
1578+ "net/http"
1579+)
1580+
1581+// APIClient helps making api requests.
1582+type APIClient struct {
1583+ ServerAPIURL string
1584+ // hook to adjust requests
1585+ MassageRequest func(req *http.Request, message interface{}) *http.Request
1586+ // other state
1587+ httpClient *http.Client
1588+}
1589+
1590+// SetupClient sets up the http client to make requests.
1591+func (api *APIClient) SetupClient(tlsConfig *tls.Config) {
1592+ api.httpClient = &http.Client{
1593+ Transport: &http.Transport{TLSClientConfig: tlsConfig},
1594+ }
1595+}
1596+
1597+var ErrNOk = errors.New("not ok")
1598+
1599+// Post a API request.
1600+func (api *APIClient) PostRequest(path string, message interface{}) (map[string]interface{}, error) {
1601+ packedMessage, err := json.Marshal(message)
1602+ if err != nil {
1603+ panic(err)
1604+ }
1605+ reader := bytes.NewReader(packedMessage)
1606+
1607+ url := api.ServerAPIURL + path
1608+ request, _ := http.NewRequest("POST", url, reader)
1609+ request.ContentLength = int64(reader.Len())
1610+ request.Header.Set("Content-Type", "application/json")
1611+
1612+ if api.MassageRequest != nil {
1613+ request = api.MassageRequest(request, message)
1614+ }
1615+
1616+ resp, err := api.httpClient.Do(request)
1617+ if err != nil {
1618+ return nil, err
1619+ }
1620+ defer resp.Body.Close()
1621+ body, err := ioutil.ReadAll(resp.Body)
1622+ if err != nil {
1623+ return nil, err
1624+ }
1625+ var res map[string]interface{}
1626+ err = json.Unmarshal(body, &res)
1627+ if err != nil {
1628+ return nil, err
1629+ }
1630+ if ok, _ := res["ok"].(bool); !ok {
1631+ return res, ErrNOk
1632+ }
1633+ return res, nil
1634+}
1635
1636=== renamed file 'server/acceptance/cmd/acceptanceclient.go' => 'server/acceptance/kit/cliloop.go'
1637--- server/acceptance/cmd/acceptanceclient.go 2014-07-11 21:24:21 +0000
1638+++ server/acceptance/kit/cliloop.go 2014-08-25 16:38:20 +0000
1639@@ -14,15 +14,13 @@
1640 with this program. If not, see <http://www.gnu.org/licenses/>.
1641 */
1642
1643-// acceptanceclient command for playing.
1644-package main
1645+package kit
1646
1647 import (
1648 "flag"
1649 "fmt"
1650 "log"
1651 "os"
1652- "os/exec"
1653 "path/filepath"
1654 "regexp"
1655 "strings"
1656@@ -32,27 +30,58 @@
1657 "launchpad.net/ubuntu-push/server/acceptance"
1658 )
1659
1660-var (
1661- insecureFlag = flag.Bool("insecure", false, "disable checking of server certificate and hostname")
1662- reportPingsFlag = flag.Bool("reportPings", true, "report each Ping from the server")
1663- deviceModel = flag.String("model", "?", "device image model")
1664- imageChannel = flag.String("imageChannel", "?", "image channel")
1665-)
1666-
1667-type configuration struct {
1668+type Configuration struct {
1669 // session configuration
1670 ExchangeTimeout config.ConfigTimeDuration `json:"exchange_timeout"`
1671 // server connection config
1672- Addr config.ConfigHostPort
1673- CertPEMFile string `json:"cert_pem_file"`
1674- AuthHelper string `json:"auth_helper"`
1675- RunTimeout config.ConfigTimeDuration `json:"run_timeout"`
1676- WaitFor string `json:"wait_for"`
1677-}
1678-
1679-func main() {
1680+ Target string `json:"target"`
1681+ Addr config.ConfigHostPort `json:"addr"`
1682+ CertPEMFile string `json:"cert_pem_file"`
1683+ Insecure bool `json:"insecure" help:"disable checking of server certificate and hostname"`
1684+ Domain string `json:"domain" help:"domain for tls connect"`
1685+ // run timeout
1686+ RunTimeout config.ConfigTimeDuration `json:"run_timeout"`
1687+ // flags
1688+ ReportPings bool `json:"reportPings" help:"report each Ping from the server"`
1689+ DeviceModel string `json:"model" help:"device image model"`
1690+ ImageChannel string `json:"imageChannel" help:"image channel"`
1691+}
1692+
1693+func (cfg *Configuration) PickByTarget(what, productionValue, stagingValue string) (value string) {
1694+ switch cfg.Target {
1695+ case "production":
1696+ value = productionValue
1697+ case "staging":
1698+ value = stagingValue
1699+ case "":
1700+ log.Fatalf("either %s or target must be given", what)
1701+ default:
1702+ log.Fatalf("if specified target should be production|staging")
1703+ }
1704+ return
1705+}
1706+
1707+// Control.
1708+var (
1709+ Name = "acceptanceclient"
1710+ Defaults = map[string]interface{}{
1711+ "target": "",
1712+ "addr": ":0",
1713+ "exchange_timeout": "5s",
1714+ "cert_pem_file": "",
1715+ "insecure": false,
1716+ "domain": "",
1717+ "run_timeout": "0s",
1718+ "reportPings": true,
1719+ "model": "?",
1720+ "imageChannel": "?",
1721+ }
1722+)
1723+
1724+// CliLoop parses command line arguments and runs a client loop.
1725+func CliLoop(totalCfg interface{}, cfg *Configuration, onSetup func(sess *acceptance.ClientSession, cfgDir string), auth func(string) string, waitFor func() string, onConnect func()) {
1726 flag.Usage = func() {
1727- fmt.Fprintf(os.Stderr, "Usage: acceptancclient [options] <device id>\n")
1728+ fmt.Fprintf(os.Stderr, "Usage: %s [options] <device id>\n", Name)
1729 flag.PrintDefaults()
1730 }
1731 missingArg := func(what string) {
1732@@ -60,14 +89,7 @@
1733 flag.Usage()
1734 os.Exit(2)
1735 }
1736- cfg := &configuration{}
1737- err := config.ReadFilesDefaults(cfg, map[string]interface{}{
1738- "exchange_timeout": "5s",
1739- "cert_pem_file": "",
1740- "auth_helper": "",
1741- "run_timeout": "0s",
1742- "wait_for": "",
1743- }, "<flags>")
1744+ err := config.ReadFilesDefaults(totalCfg, Defaults, "<flags>")
1745 if err != nil {
1746 log.Fatalf("reading config: %v", err)
1747 }
1748@@ -76,36 +98,34 @@
1749 case narg < 1:
1750 missingArg("device-id")
1751 }
1752+ addr := ""
1753+ if cfg.Addr == ":0" {
1754+ addr = cfg.PickByTarget("addr", "push-delivery.ubuntu.com:443",
1755+ "push-delivery.staging.ubuntu.com:443")
1756+ } else {
1757+ addr = cfg.Addr.HostPort()
1758+ }
1759 session := &acceptance.ClientSession{
1760 ExchangeTimeout: cfg.ExchangeTimeout.TimeDuration(),
1761- ServerAddr: cfg.Addr.HostPort(),
1762+ ServerAddr: addr,
1763 DeviceId: flag.Arg(0),
1764 // flags
1765- Model: *deviceModel,
1766- ImageChannel: *imageChannel,
1767- ReportPings: *reportPingsFlag,
1768- Insecure: *insecureFlag,
1769- }
1770- log.Printf("with: %#v", session)
1771- if !*insecureFlag && cfg.CertPEMFile != "" {
1772- cfgDir := filepath.Dir(flag.Lookup("cfg@").Value.String())
1773- log.Printf("cert: %v relToDir: %v", cfg.CertPEMFile, cfgDir)
1774- session.CertPEMBlock, err = config.LoadFile(cfg.CertPEMFile, cfgDir)
1775- if err != nil {
1776- log.Fatalf("reading CertPEMFile: %v", err)
1777- }
1778- }
1779- if len(cfg.AuthHelper) != 0 {
1780- auth, err := exec.Command(cfg.AuthHelper, "https://push.ubuntu.com/").Output()
1781- if err != nil {
1782- log.Fatalf("auth helper: %v", err)
1783- }
1784- session.Auth = strings.TrimSpace(string(auth))
1785- }
1786+ Model: cfg.DeviceModel,
1787+ ImageChannel: cfg.ImageChannel,
1788+ ReportPings: cfg.ReportPings,
1789+ }
1790+ cfgDir := filepath.Dir(flag.Lookup("cfg@").Value.String())
1791+ onSetup(session, cfgDir)
1792+ session.TLSConfig, err = MakeTLSConfig(cfg.Domain, cfg.Insecure, cfg.CertPEMFile, cfgDir)
1793+ if err != nil {
1794+ log.Fatalf("tls config: %v", err)
1795+ }
1796+ session.Auth = auth("https://push.ubuntu.com/")
1797 var waitForRegexp *regexp.Regexp
1798- if cfg.WaitFor != "" {
1799+ waitForStr := waitFor()
1800+ if waitForStr != "" {
1801 var err error
1802- waitForRegexp, err = regexp.Compile(cfg.WaitFor)
1803+ waitForRegexp, err = regexp.Compile(waitForStr)
1804 if err != nil {
1805 log.Fatalf("wait_for regexp: %v", err)
1806 }
1807@@ -118,6 +138,9 @@
1808 go func() {
1809 for {
1810 ev := <-events
1811+ if strings.HasPrefix(ev, "connected") {
1812+ onConnect()
1813+ }
1814 if waitForRegexp != nil && waitForRegexp.MatchString(ev) {
1815 log.Println("<matching-event>:", ev)
1816 os.Exit(0)
1817
1818=== added file 'server/acceptance/kit/helpers.go'
1819--- server/acceptance/kit/helpers.go 1970-01-01 00:00:00 +0000
1820+++ server/acceptance/kit/helpers.go 2014-08-25 16:38:20 +0000
1821@@ -0,0 +1,46 @@
1822+/*
1823+ Copyright 2013-2014 Canonical Ltd.
1824+
1825+ This program is free software: you can redistribute it and/or modify it
1826+ under the terms of the GNU General Public License version 3, as published
1827+ by the Free Software Foundation.
1828+
1829+ This program is distributed in the hope that it will be useful, but
1830+ WITHOUT ANY WARRANTY; without even the implied warranties of
1831+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1832+ PURPOSE. See the GNU General Public License for more details.
1833+
1834+ You should have received a copy of the GNU General Public License along
1835+ with this program. If not, see <http://www.gnu.org/licenses/>.
1836+*/
1837+
1838+package kit
1839+
1840+import (
1841+ "crypto/tls"
1842+ "crypto/x509"
1843+ "fmt"
1844+
1845+ "launchpad.net/ubuntu-push/config"
1846+)
1847+
1848+// MakeTLSConfig makes a tls.Config, optionally reading a cert from
1849+// disk, possibly relative to relDir.
1850+func MakeTLSConfig(domain string, insecure bool, certPEMFile string, relDir string) (*tls.Config, error) {
1851+ tlsConfig := &tls.Config{}
1852+ tlsConfig.ServerName = domain
1853+ tlsConfig.InsecureSkipVerify = insecure
1854+ if !insecure && certPEMFile != "" {
1855+ certPEMBlock, err := config.LoadFile(certPEMFile, relDir)
1856+ if err != nil {
1857+ return nil, fmt.Errorf("reading cert: %v", err)
1858+ }
1859+ cp := x509.NewCertPool()
1860+ ok := cp.AppendCertsFromPEM(certPEMBlock)
1861+ if !ok {
1862+ return nil, fmt.Errorf("could not parse certificate")
1863+ }
1864+ tlsConfig.RootCAs = cp
1865+ }
1866+ return tlsConfig, nil
1867+}
1868
1869=== modified file 'server/acceptance/suites/broadcast.go'
1870--- server/acceptance/suites/broadcast.go 2014-06-25 11:00:15 +0000
1871+++ server/acceptance/suites/broadcast.go 2014-08-25 16:38:20 +0000
1872@@ -41,8 +41,7 @@
1873 ExpireOn: future,
1874 Data: json.RawMessage(`{"img1/m1": 42}`),
1875 })
1876- c.Assert(err, IsNil)
1877- c.Assert(got, Matches, OK)
1878+ c.Assert(err, IsNil, Commentf("%v", got))
1879 c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"img1/m1":42}]`)
1880 stop()
1881 c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`)
1882@@ -56,14 +55,13 @@
1883 ExpireOn: future,
1884 Data: json.RawMessage(`{"img1/m2": 10}`),
1885 })
1886- c.Assert(err, IsNil)
1887+ c.Assert(err, IsNil, Commentf("%v", got))
1888 got, err = s.PostRequest("/broadcast", &api.Broadcast{
1889 Channel: "system",
1890 ExpireOn: future,
1891 Data: json.RawMessage(`{"img1/m1": 20}`),
1892 })
1893- c.Assert(err, IsNil)
1894- c.Assert(got, Matches, OK)
1895+ c.Assert(err, IsNil, Commentf("%v", got))
1896 c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:2 payloads:[{"img1/m1":20}]`)
1897 stop()
1898 c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`)
1899@@ -77,8 +75,7 @@
1900 ExpireOn: future,
1901 Data: json.RawMessage(`{"img1/m1": 1}`),
1902 })
1903- c.Assert(err, IsNil)
1904- c.Assert(got, Matches, OK)
1905+ c.Assert(err, IsNil, Commentf("%v", got))
1906
1907 events, errCh, stop := s.StartClient(c, "DEVB", nil)
1908 // gettting pending on connect
1909@@ -97,8 +94,7 @@
1910 ExpireOn: future,
1911 Data: json.RawMessage(fmt.Sprintf(payloadFmt, i)),
1912 })
1913- c.Assert(err, IsNil)
1914- c.Assert(got, Matches, OK)
1915+ c.Assert(err, IsNil, Commentf("%v", got))
1916 }
1917
1918 events, errCh, stop := s.StartClient(c, "DEVC", nil)
1919@@ -130,8 +126,7 @@
1920 ExpireOn: future,
1921 Data: json.RawMessage(`{"img1/m1": 42}`),
1922 })
1923- c.Assert(err, IsNil)
1924- c.Assert(got, Matches, OK)
1925+ c.Assert(err, IsNil, Commentf("%v", got))
1926 c.Check(NextEvent(events1, errCh1), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"img1/m1":42}]`)
1927 c.Check(NextEvent(events2, errCh2), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"img1/m1":42}]`)
1928 stop1()
1929@@ -149,8 +144,7 @@
1930 ExpireOn: future,
1931 Data: json.RawMessage(`{"img1/m1": 1}`),
1932 })
1933- c.Assert(err, IsNil)
1934- c.Assert(got, Matches, OK)
1935+ c.Assert(err, IsNil, Commentf("%v", got))
1936 c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"img1/m1":1}]`)
1937 stop()
1938 c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`)
1939@@ -161,8 +155,7 @@
1940 ExpireOn: future,
1941 Data: json.RawMessage(`{"img1/m1": 2}`),
1942 })
1943- c.Assert(err, IsNil)
1944- c.Assert(got, Matches, OK)
1945+ c.Assert(err, IsNil, Commentf("%v", got))
1946 // reconnect, provide levels, get only later notification
1947 events, errCh, stop = s.StartClient(c, "DEVD", map[string]int64{
1948 protocol.SystemChannelId: 1,
1949@@ -180,15 +173,13 @@
1950 ExpireOn: future,
1951 Data: json.RawMessage(`{"img1/m1": 1}`),
1952 })
1953- c.Assert(err, IsNil)
1954- c.Assert(got, Matches, OK)
1955+ c.Assert(err, IsNil, Commentf("%v", got))
1956 got, err = s.PostRequest("/broadcast", &api.Broadcast{
1957 Channel: "system",
1958 ExpireOn: future,
1959 Data: json.RawMessage(`{"img1/m1": 2}`),
1960 })
1961- c.Assert(err, IsNil)
1962- c.Assert(got, Matches, OK)
1963+ c.Assert(err, IsNil, Commentf("%v", got))
1964
1965 events, errCh, stop := s.StartClient(c, "DEVB", map[string]int64{
1966 protocol.SystemChannelId: 10,
1967@@ -219,15 +210,13 @@
1968 ExpireOn: future,
1969 Data: json.RawMessage(`{"img1/m1": 1}`),
1970 })
1971- c.Assert(err, IsNil)
1972- c.Assert(got, Matches, OK)
1973+ c.Assert(err, IsNil, Commentf("%v", got))
1974 got, err = s.PostRequest("/broadcast", &api.Broadcast{
1975 Channel: "system",
1976 ExpireOn: future,
1977 Data: json.RawMessage(`{"img1/m1": 2}`),
1978 })
1979- c.Assert(err, IsNil)
1980- c.Assert(got, Matches, OK)
1981+ c.Assert(err, IsNil, Commentf("%v", got))
1982
1983 events, errCh, stop := s.StartClient(c, "DEVB", map[string]int64{
1984 protocol.SystemChannelId: -10,
1985@@ -246,15 +235,13 @@
1986 ExpireOn: future,
1987 Data: json.RawMessage(`{"img1/m1": 1}`),
1988 })
1989- c.Assert(err, IsNil)
1990- c.Assert(got, Matches, OK)
1991+ c.Assert(err, IsNil, Commentf("%v", got))
1992 got, err = s.PostRequest("/broadcast", &api.Broadcast{
1993 Channel: "system",
1994 ExpireOn: time.Now().Add(1 * time.Second).Format(time.RFC3339),
1995 Data: json.RawMessage(`{"img1/m1": 2}`),
1996 })
1997- c.Assert(err, IsNil)
1998- c.Assert(got, Matches, OK)
1999+ c.Assert(err, IsNil, Commentf("%v", got))
2000
2001 time.Sleep(2 * time.Second)
2002 // second broadcast is expired
2003
2004=== modified file 'server/acceptance/suites/suite.go'
2005--- server/acceptance/suites/suite.go 2014-06-25 11:00:15 +0000
2006+++ server/acceptance/suites/suite.go 2014-08-25 16:38:20 +0000
2007@@ -18,13 +18,9 @@
2008 package suites
2009
2010 import (
2011- "bytes"
2012- "encoding/json"
2013 "flag"
2014 "fmt"
2015- "io/ioutil"
2016 "net"
2017- "net/http"
2018 "os"
2019 "runtime"
2020 "time"
2021@@ -32,6 +28,7 @@
2022 . "launchpad.net/gocheck"
2023
2024 "launchpad.net/ubuntu-push/server/acceptance"
2025+ "launchpad.net/ubuntu-push/server/acceptance/kit"
2026 helpers "launchpad.net/ubuntu-push/testing"
2027 )
2028
2029@@ -84,14 +81,10 @@
2030 StartServer func(c *C, s *AcceptanceSuite, handle *ServerHandle)
2031 // populated by StartServer
2032 ServerHandle
2033- ServerAPIURL string
2034+ kit.APIClient // has ServerAPIURL
2035 // KillGroup should be populated by StartServer with functions
2036 // to kill the server process
2037 KillGroup map[string]func(os.Signal)
2038- // hook to adjust requests
2039- MassageRequest func(req *http.Request, message interface{}) *http.Request
2040- // other state
2041- httpClient *http.Client
2042 }
2043
2044 // Start a new server for each test.
2045@@ -101,7 +94,7 @@
2046 c.Assert(s.ServerHandle.ServerEvents, NotNil)
2047 c.Assert(s.ServerHandle.ServerAddr, Not(Equals), "")
2048 c.Assert(s.ServerAPIURL, Not(Equals), "")
2049- s.httpClient = &http.Client{}
2050+ s.SetupClient(nil)
2051 }
2052
2053 func (s *AcceptanceSuite) TearDownTest(c *C) {
2054@@ -110,45 +103,19 @@
2055 }
2056 }
2057
2058-// Post a API request.
2059-func (s *AcceptanceSuite) PostRequest(path string, message interface{}) (string, error) {
2060- packedMessage, err := json.Marshal(message)
2061- if err != nil {
2062- panic(err)
2063- }
2064- reader := bytes.NewReader(packedMessage)
2065-
2066- url := s.ServerAPIURL + path
2067- request, _ := http.NewRequest("POST", url, reader)
2068- request.ContentLength = int64(reader.Len())
2069- request.Header.Set("Content-Type", "application/json")
2070-
2071- if s.MassageRequest != nil {
2072- request = s.MassageRequest(request, message)
2073- }
2074-
2075- resp, err := s.httpClient.Do(request)
2076- if err != nil {
2077- panic(err)
2078- }
2079- defer resp.Body.Close()
2080- body, err := ioutil.ReadAll(resp.Body)
2081- return string(body), err
2082-}
2083-
2084 func testClientSession(addr string, deviceId, model, imageChannel string, reportPings bool) *acceptance.ClientSession {
2085- certPEMBlock, err := ioutil.ReadFile(helpers.SourceRelative("../ssl/testing.cert"))
2086+ tlsConfig, err := kit.MakeTLSConfig("", false, helpers.SourceRelative("../ssl/testing.cert"), "")
2087 if err != nil {
2088 panic(fmt.Sprintf("could not read ssl/testing.cert: %v", err))
2089 }
2090 return &acceptance.ClientSession{
2091 ExchangeTimeout: 100 * time.Millisecond,
2092 ServerAddr: addr,
2093- CertPEMBlock: certPEMBlock,
2094 DeviceId: deviceId,
2095 Model: model,
2096 ImageChannel: imageChannel,
2097 ReportPings: reportPings,
2098+ TLSConfig: tlsConfig,
2099 }
2100 }
2101
2102@@ -198,5 +165,3 @@
2103
2104 // Long after the end of the tests.
2105 var future = time.Now().Add(9 * time.Hour).Format(time.RFC3339)
2106-
2107-const OK = `.*"ok":true.*`
2108
2109=== modified file 'server/acceptance/suites/unicast.go'
2110--- server/acceptance/suites/unicast.go 2014-07-14 15:23:17 +0000
2111+++ server/acceptance/suites/unicast.go 2014-08-25 16:38:20 +0000
2112@@ -23,6 +23,7 @@
2113
2114 . "launchpad.net/gocheck"
2115
2116+ "launchpad.net/ubuntu-push/server/acceptance/kit"
2117 "launchpad.net/ubuntu-push/server/api"
2118 )
2119
2120@@ -45,20 +46,15 @@
2121 DeviceId: "DEV1",
2122 AppId: "app1",
2123 })
2124- c.Assert(err, IsNil)
2125- c.Assert(res, Matches, OK)
2126- var reg map[string]interface{}
2127- err = json.Unmarshal([]byte(res), &reg)
2128- c.Assert(err, IsNil)
2129+ c.Assert(err, IsNil, Commentf("%v", res))
2130 events, errCh, stop := s.StartClientAuth(c, "DEV1", nil, auth)
2131 got, err := s.PostRequest("/notify", &api.Unicast{
2132- Token: reg["token"].(string),
2133+ Token: res["token"].(string),
2134 AppId: "app1",
2135 ExpireOn: future,
2136 Data: json.RawMessage(`{"a": 42}`),
2137 })
2138- c.Assert(err, IsNil)
2139- c.Assert(got, Matches, OK)
2140+ c.Assert(err, IsNil, Commentf("%v", got))
2141 c.Check(NextEvent(events, errCh), Equals, `unicast app:app1 payload:{"a":42};`)
2142 stop()
2143 c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`)
2144@@ -80,8 +76,7 @@
2145 ExpireOn: future,
2146 Data: json.RawMessage(`{"to": 1}`),
2147 })
2148- c.Assert(err, IsNil)
2149- c.Assert(got, Matches, OK)
2150+ c.Assert(err, IsNil, Commentf("%v", got))
2151 got, err = s.PostRequest("/notify", &api.Unicast{
2152 UserId: userId2,
2153 DeviceId: "DEV2",
2154@@ -89,8 +84,7 @@
2155 ExpireOn: future,
2156 Data: json.RawMessage(`{"to": 2}`),
2157 })
2158- c.Assert(err, IsNil)
2159- c.Assert(got, Matches, OK)
2160+ c.Assert(err, IsNil, Commentf("%v", got))
2161 c.Check(NextEvent(events1, errCh1), Equals, `unicast app:app1 payload:{"to":1};`)
2162 c.Check(NextEvent(events2, errCh2), Equals, `unicast app:app1 payload:{"to":2};`)
2163 stop1()
2164@@ -111,8 +105,7 @@
2165 ExpireOn: future,
2166 Data: json.RawMessage(`{"a": 42}`),
2167 })
2168- c.Assert(err, IsNil)
2169- c.Assert(got, Matches, OK)
2170+ c.Assert(err, IsNil, Commentf("%v", got))
2171
2172 // get pending on connect
2173 events, errCh, stop := s.StartClientAuth(c, "DEV1", nil, auth)
2174@@ -134,8 +127,7 @@
2175 ExpireOn: future,
2176 Data: json.RawMessage(fmt.Sprintf(payloadFmt, i)),
2177 })
2178- c.Assert(err, IsNil)
2179- c.Assert(got, Matches, OK)
2180+ c.Assert(err, IsNil, Commentf("%v", got))
2181 }
2182
2183 events, errCh, stop := s.StartClientAuth(c, "DEV2", nil, auth)
2184@@ -168,8 +160,7 @@
2185 ExpireOn: future,
2186 Data: json.RawMessage(fmt.Sprintf(payloadFmt, i)),
2187 })
2188- c.Assert(err, IsNil)
2189- c.Assert(got, Matches, OK)
2190+ c.Assert(err, IsNil, Commentf("%v", got))
2191 }
2192
2193 got, err := s.PostRequest("/notify", &api.Unicast{
2194@@ -179,8 +170,9 @@
2195 ExpireOn: future,
2196 Data: json.RawMessage(fmt.Sprintf(payloadFmt, MaxNotificationsPerApplication)),
2197 })
2198- c.Assert(err, IsNil)
2199- c.Assert(got, Matches, `.*"error":"too-many-pending".*`)
2200+ c.Assert(err, Equals, kit.ErrNOk, Commentf("%v", got))
2201+ errorStr, _ := got["error"].(string)
2202+ c.Assert(errorStr, Equals, "too-many-pending")
2203
2204 // clear all pending
2205 got, err = s.PostRequest("/notify", &api.Unicast{
2206@@ -191,8 +183,7 @@
2207 Data: json.RawMessage(fmt.Sprintf(payloadFmt, 1000)),
2208 ClearPending: true,
2209 })
2210- c.Assert(err, IsNil)
2211- c.Assert(got, Matches, OK)
2212+ c.Assert(err, IsNil, Commentf("%v", got))
2213
2214 events, errCh, stop := s.StartClientAuth(c, "DEV2", nil, auth)
2215 // getting the 1 pending on connect
2216@@ -214,8 +205,7 @@
2217 Data: json.RawMessage(`{"m": 1}`),
2218 ReplaceTag: "tagFoo",
2219 })
2220- c.Assert(err, IsNil)
2221- c.Assert(got, Matches, OK)
2222+ c.Assert(err, IsNil, Commentf("%v", got))
2223
2224 // replace
2225 got, err = s.PostRequest("/notify", &api.Unicast{
2226@@ -226,8 +216,7 @@
2227 Data: json.RawMessage(`{"m": 2}`),
2228 ReplaceTag: "tagFoo",
2229 })
2230- c.Assert(err, IsNil)
2231- c.Assert(got, Matches, OK)
2232+ c.Assert(err, IsNil, Commentf("%v", got))
2233
2234 events, errCh, stop := s.StartClientAuth(c, "DEV2", nil, auth)
2235 // getting the 1 pending on connect

Subscribers

People subscribed via source and target branches