Merge lp:~chipaca/ubuntu-push/cherrypickings into lp:ubuntu-push
- cherrypickings
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 101 |
Proposed branch: | lp:~chipaca/ubuntu-push/cherrypickings |
Merge into: | lp:ubuntu-push |
Diff against target: |
1220 lines (+640/-59) 15 files modified
client/client.go (+6/-9) client/client_test.go (+15/-1) client/gethosts/gethost.go (+7/-5) client/gethosts/gethost_test.go (+6/-2) client/session/session.go (+52/-4) client/session/session_test.go (+181/-11) config/config.go (+130/-21) config/config_test.go (+105/-0) debian/changelog (+19/-0) logger/logger.go (+27/-0) logger/logger_test.go (+25/-0) server/acceptance/cmd/acceptanceclient.go (+7/-2) ubuntu-push-client.go (+25/-0) whoopsie/identifier/identifier.go (+20/-4) whoopsie/identifier/identifier_test.go (+15/-0) |
To merge this branch: | bzr merge lp:~chipaca/ubuntu-push/cherrypickings |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Push Hackers | Pending | ||
Review via email: mp+216466@code.launchpad.net |
Commit message
[ Samuele Pedroni ]
* gave the client the ability to get config from commandline
( => easier automated testing) (LP: #1311600)
[ John Lenton ]
* Ensure ubuntu-push-client is the only one running in the session.
(LP: #1309432)
* Remove supurious numbers in brackets in notifications. (LP: #1308145)
* Check the server certificate and server name. (LP: #1297969)
* Loop whoopsie_
* In the session: set a flag on connect, clear it on successfully
replying to ping or broadcast messages, check it at the top of
autoredial. Also track the last autoredial, and set the delay flag if
autoredial is re-called too quickly. (LP: #1309231)
Description of the change
[ Samuele Pedroni ]
* gave the client the ability to get config from commandline
( => easier automated testing) (LP: #1311600)
[ John Lenton ]
* Ensure ubuntu-push-client is the only one running in the session.
(LP: #1309432)
* Remove supurious numbers in brackets in notifications. (LP: #1308145)
* Check the server certificate and server name. (LP: #1297969)
* Loop whoopsie_
* In the session: set a flag on connect, clear it on successfully
replying to ping or broadcast messages, check it at the top of
autoredial. Also track the last autoredial, and set the delay flag if
autoredial is re-called too quickly. (LP: #1309231)
- 109. By John Lenton
-
fix changelog
- 110. By John Lenton
-
updated changelog with link to lp:216466
- 111. By John Lenton
-
updated changelog with link to lp:1311600 which is what it should be
Preview Diff
1 | === modified file 'client/client.go' |
2 | --- client/client.go 2014-04-11 16:37:48 +0000 |
3 | +++ client/client.go 2014-04-23 11:53:01 +0000 |
4 | @@ -57,7 +57,7 @@ |
5 | // The PEM-encoded server certificate |
6 | CertPEMFile string `json:"cert_pem_file"` |
7 | // The logging level (one of "debug", "info", "error") |
8 | - LogLevel string `json:"log_level"` |
9 | + LogLevel logger.ConfigLogLevel `json:"log_level"` |
10 | } |
11 | |
12 | // PushClient is the Ubuntu Push Notifications client-side daemon. |
13 | @@ -95,13 +95,13 @@ |
14 | |
15 | // configure loads its configuration, and sets it up. |
16 | func (client *PushClient) configure() error { |
17 | - f, err := os.Open(client.configPath) |
18 | + _, err := os.Stat(client.configPath) |
19 | if err != nil { |
20 | - return fmt.Errorf("opening config: %v", err) |
21 | + return fmt.Errorf("config: %v", err) |
22 | } |
23 | - err = config.ReadConfig(f, &client.config) |
24 | + err = config.ReadFiles(&client.config, client.configPath, "<flags>") |
25 | if err != nil { |
26 | - return fmt.Errorf("reading config: %v", err) |
27 | + return fmt.Errorf("config: %v", err) |
28 | } |
29 | // ignore spaces |
30 | client.config.Addr = strings.Replace(client.config.Addr, " ", "", -1) |
31 | @@ -110,7 +110,7 @@ |
32 | } |
33 | |
34 | // later, we'll be specifying more logging options in the config file |
35 | - client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel) |
36 | + client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel.Level()) |
37 | |
38 | // overridden for testing |
39 | client.idder = identifier.New() |
40 | @@ -285,9 +285,6 @@ |
41 | h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}} |
42 | nots := notifications.Raw(client.notificationsEndp, client.log) |
43 | body := "Tap to open the system updater." |
44 | - if msg != nil { |
45 | - body = fmt.Sprintf("[%d] %s", msg.TopLevel, body) |
46 | - } |
47 | not_id, err := nots.Notify( |
48 | "ubuntu-push-client", // app name |
49 | uint32(0), // id |
50 | |
51 | === modified file 'client/client_test.go' |
52 | --- client/client_test.go 2014-04-11 16:21:45 +0000 |
53 | +++ client/client_test.go 2014-04-23 11:53:01 +0000 |
54 | @@ -19,10 +19,12 @@ |
55 | import ( |
56 | "encoding/json" |
57 | "errors" |
58 | + "flag" |
59 | "fmt" |
60 | "io/ioutil" |
61 | "net/http" |
62 | "net/http/httptest" |
63 | + "os" |
64 | "path/filepath" |
65 | "reflect" |
66 | "testing" |
67 | @@ -37,6 +39,7 @@ |
68 | testibus "launchpad.net/ubuntu-push/bus/testing" |
69 | "launchpad.net/ubuntu-push/client/session" |
70 | "launchpad.net/ubuntu-push/client/session/levelmap" |
71 | + "launchpad.net/ubuntu-push/config" |
72 | helpers "launchpad.net/ubuntu-push/testing" |
73 | "launchpad.net/ubuntu-push/testing/condition" |
74 | "launchpad.net/ubuntu-push/util" |
75 | @@ -79,6 +82,7 @@ |
76 | } |
77 | |
78 | func (cs *clientSuite) SetUpSuite(c *C) { |
79 | + config.IgnoreParsedFlags = true // because configure() uses <flags> |
80 | cs.timeouts = util.SwapTimeouts([]time.Duration{0}) |
81 | cs.leveldbPath = "" |
82 | } |
83 | @@ -142,6 +146,16 @@ |
84 | c.Check(cli.config.ExchangeTimeout.TimeDuration(), Equals, time.Duration(10*time.Millisecond)) |
85 | } |
86 | |
87 | +func (cs *clientSuite) TestConfigureWorksWithFlags(c *C) { |
88 | + flag.CommandLine = flag.NewFlagSet("client", flag.ContinueOnError) |
89 | + os.Args = []string{"client", "-addr", "foo:7777"} |
90 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
91 | + err := cli.configure() |
92 | + c.Assert(err, IsNil) |
93 | + c.Assert(cli.config, NotNil) |
94 | + c.Check(cli.config.Addr, Equals, "foo:7777") |
95 | +} |
96 | + |
97 | func (cs *clientSuite) TestConfigureSetsUpLog(c *C) { |
98 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
99 | c.Check(cli.log, IsNil) |
100 | @@ -163,7 +177,7 @@ |
101 | c.Check(cli.idder, IsNil) |
102 | err := cli.configure() |
103 | c.Assert(err, IsNil) |
104 | - c.Assert(cli.idder, DeepEquals, identifier.New()) |
105 | + c.Assert(cli.idder, FitsTypeOf, identifier.New()) |
106 | } |
107 | |
108 | func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) { |
109 | |
110 | === modified file 'client/gethosts/gethost.go' |
111 | --- client/gethosts/gethost.go 2014-03-24 15:32:29 +0000 |
112 | +++ client/gethosts/gethost.go 2014-04-23 11:53:01 +0000 |
113 | @@ -49,8 +49,10 @@ |
114 | } |
115 | } |
116 | |
117 | -type expected struct { |
118 | - Hosts []string |
119 | +// Host contains the domain and hosts returned by the remote endpoint |
120 | +type Host struct { |
121 | + Domain string |
122 | + Hosts []string |
123 | } |
124 | |
125 | var ( |
126 | @@ -60,7 +62,7 @@ |
127 | ) |
128 | |
129 | // Get gets a list of hosts consulting the endpoint. |
130 | -func (gh *GetHost) Get() ([]string, error) { |
131 | +func (gh *GetHost) Get() (*Host, error) { |
132 | resp, err := gh.cli.Get(gh.endpointUrl + "?h=" + gh.hash) |
133 | if err != nil { |
134 | return nil, err |
135 | @@ -80,7 +82,7 @@ |
136 | if err != nil { |
137 | return nil, err |
138 | } |
139 | - var parsed expected |
140 | + var parsed Host |
141 | err = json.Unmarshal(body, &parsed) |
142 | if err != nil { |
143 | return nil, ErrTemporary |
144 | @@ -88,5 +90,5 @@ |
145 | if len(parsed.Hosts) == 0 { |
146 | return nil, ErrTemporary |
147 | } |
148 | - return parsed.Hosts, nil |
149 | + return &parsed, nil |
150 | } |
151 | |
152 | === modified file 'client/gethosts/gethost_test.go' |
153 | --- client/gethosts/gethost_test.go 2014-03-31 14:31:07 +0000 |
154 | +++ client/gethosts/gethost_test.go 2014-04-23 11:53:01 +0000 |
155 | @@ -45,7 +45,8 @@ |
156 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
157 | x := r.FormValue("h") |
158 | b, err := json.Marshal(map[string]interface{}{ |
159 | - "hosts": []string{"http://" + x}, |
160 | + "domain": "example.com", |
161 | + "hosts": []string{"http://" + x}, |
162 | }) |
163 | if err != nil { |
164 | panic(err) |
165 | @@ -57,7 +58,8 @@ |
166 | gh := New("foobar", ts.URL, 1*time.Second) |
167 | res, err := gh.Get() |
168 | c.Assert(err, IsNil) |
169 | - c.Check(res, DeepEquals, []string{"http://c1130408a700afe0"}) |
170 | + c.Check(*res, DeepEquals, |
171 | + Host{Domain: "example.com", Hosts: []string{"http://c1130408a700afe0"}}) |
172 | } |
173 | |
174 | func (s *getHostsSuite) TestGetTimeout(c *C) { |
175 | @@ -97,4 +99,6 @@ |
176 | |
177 | scenario(http.StatusOK, "{", ErrTemporary) |
178 | scenario(http.StatusOK, "{}", ErrTemporary) |
179 | + scenario(http.StatusOK, `{"domain": "example.com"}`, ErrTemporary) |
180 | + scenario(http.StatusOK, `{"hosts": ["one"]}`, nil) |
181 | } |
182 | |
183 | === modified file 'client/session/session.go' |
184 | --- client/session/session.go 2014-04-04 13:55:00 +0000 |
185 | +++ client/session/session.go 2014-04-23 11:53:01 +0000 |
186 | @@ -73,7 +73,7 @@ |
187 | ) |
188 | |
189 | type hostGetter interface { |
190 | - Get() ([]string, error) |
191 | + Get() (*gethosts.Host, error) |
192 | } |
193 | |
194 | // ClientSessionConfig groups the client session configuration. |
195 | @@ -115,6 +115,26 @@ |
196 | stateP *uint32 |
197 | ErrCh chan error |
198 | MsgCh chan *Notification |
199 | + // autoredial knobs |
200 | + shouldDelayP *uint32 |
201 | + lastAutoRedial time.Time |
202 | + redialDelay func(*ClientSession) time.Duration |
203 | + redialJitter func(time.Duration) time.Duration |
204 | + redialDelays []time.Duration |
205 | + redialDelaysIdx int |
206 | +} |
207 | + |
208 | +func redialDelay(sess *ClientSession) time.Duration { |
209 | + if sess.ShouldDelay() { |
210 | + t := sess.redialDelays[sess.redialDelaysIdx] |
211 | + if len(sess.redialDelays) > sess.redialDelaysIdx+1 { |
212 | + sess.redialDelaysIdx++ |
213 | + } |
214 | + return t + sess.redialJitter(t) |
215 | + } else { |
216 | + sess.redialDelaysIdx = 0 |
217 | + return 0 |
218 | + } |
219 | } |
220 | |
221 | func NewSession(serverAddrSpec string, conf ClientSessionConfig, |
222 | @@ -131,6 +151,7 @@ |
223 | if hostsEndpoint != "" { |
224 | getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout) |
225 | } |
226 | + var shouldDelay uint32 = 0 |
227 | sess := &ClientSession{ |
228 | ClientSessionConfig: conf, |
229 | getHost: getHost, |
230 | @@ -139,10 +160,14 @@ |
231 | Log: log, |
232 | Protocolator: protocol.NewProtocol0, |
233 | Levels: levels, |
234 | - TLS: &tls.Config{InsecureSkipVerify: true}, // XXX |
235 | + TLS: &tls.Config{}, |
236 | stateP: &state, |
237 | timeSince: time.Since, |
238 | + shouldDelayP: &shouldDelay, |
239 | + redialDelay: redialDelay, |
240 | + redialDelays: util.Timeouts(), |
241 | } |
242 | + sess.redialJitter = sess.Jitter |
243 | if sess.PEM != nil { |
244 | cp := x509.NewCertPool() |
245 | ok := cp.AppendCertsFromPEM(sess.PEM) |
246 | @@ -154,6 +179,18 @@ |
247 | return sess, nil |
248 | } |
249 | |
250 | +func (sess *ClientSession) ShouldDelay() bool { |
251 | + return atomic.LoadUint32(sess.shouldDelayP) != 0 |
252 | +} |
253 | + |
254 | +func (sess *ClientSession) setShouldDelay() { |
255 | + atomic.StoreUint32(sess.shouldDelayP, uint32(1)) |
256 | +} |
257 | + |
258 | +func (sess *ClientSession) clearShouldDelay() { |
259 | + atomic.StoreUint32(sess.shouldDelayP, uint32(0)) |
260 | +} |
261 | + |
262 | func (sess *ClientSession) State() ClientSessionState { |
263 | return ClientSessionState(atomic.LoadUint32(sess.stateP)) |
264 | } |
265 | @@ -180,14 +217,17 @@ |
266 | if sess.deliveryHosts != nil && sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime { |
267 | return nil |
268 | } |
269 | - hosts, err := sess.getHost.Get() |
270 | + host, err := sess.getHost.Get() |
271 | if err != nil { |
272 | sess.Log.Errorf("getHosts: %v", err) |
273 | sess.setState(Error) |
274 | return err |
275 | } |
276 | sess.deliveryHostsTimestamp = time.Now() |
277 | - sess.deliveryHosts = hosts |
278 | + sess.deliveryHosts = host.Hosts |
279 | + if sess.TLS != nil { |
280 | + sess.TLS.ServerName = host.Domain |
281 | + } |
282 | } else { |
283 | sess.deliveryHosts = sess.fallbackHosts |
284 | } |
285 | @@ -234,6 +274,7 @@ |
286 | // connect to a server using the configuration in the ClientSession |
287 | // and set up the connection. |
288 | func (sess *ClientSession) connect() error { |
289 | + sess.setShouldDelay() |
290 | sess.startConnectionAttempt() |
291 | var err error |
292 | var conn net.Conn |
293 | @@ -263,7 +304,12 @@ |
294 | |
295 | func (sess *ClientSession) AutoRedial(doneCh chan uint32) { |
296 | sess.stopRedial() |
297 | + if time.Since(sess.lastAutoRedial) < 2*time.Second { |
298 | + sess.setShouldDelay() |
299 | + } |
300 | + time.Sleep(sess.redialDelay(sess)) |
301 | sess.retrier = util.NewAutoRedialer(sess) |
302 | + sess.lastAutoRedial = time.Now() |
303 | go func() { doneCh <- sess.retrier.Redial() }() |
304 | } |
305 | |
306 | @@ -289,6 +335,7 @@ |
307 | err := sess.proto.WriteMessage(protocol.PingPongMsg{Type: "pong"}) |
308 | if err == nil { |
309 | sess.Log.Debugf("ping.") |
310 | + sess.clearShouldDelay() |
311 | } else { |
312 | sess.setState(Error) |
313 | sess.Log.Errorf("unable to pong: %s", err) |
314 | @@ -330,6 +377,7 @@ |
315 | sess.Log.Errorf("unable to ack broadcast: %s", err) |
316 | return err |
317 | } |
318 | + sess.clearShouldDelay() |
319 | sess.Log.Debugf("broadcast chan:%v app:%v topLevel:%d payloads:%s", |
320 | bcast.ChanId, bcast.AppId, bcast.TopLevel, bcast.Payloads) |
321 | if bcast.ChanId == protocol.SystemChannelId { |
322 | |
323 | === modified file 'client/session/session_test.go' |
324 | --- client/session/session_test.go 2014-04-04 13:55:00 +0000 |
325 | +++ client/session/session_test.go 2014-04-23 11:53:01 +0000 |
326 | @@ -32,12 +32,13 @@ |
327 | |
328 | . "launchpad.net/gocheck" |
329 | |
330 | + "launchpad.net/ubuntu-push/client/gethosts" |
331 | "launchpad.net/ubuntu-push/client/session/levelmap" |
332 | - //"launchpad.net/ubuntu-push/client/gethosts" |
333 | "launchpad.net/ubuntu-push/logger" |
334 | "launchpad.net/ubuntu-push/protocol" |
335 | helpers "launchpad.net/ubuntu-push/testing" |
336 | "launchpad.net/ubuntu-push/testing/condition" |
337 | + "launchpad.net/ubuntu-push/util" |
338 | ) |
339 | |
340 | func TestSession(t *testing.T) { TestingT(t) } |
341 | @@ -214,6 +215,10 @@ |
342 | c.Check(sess, NotNil) |
343 | c.Check(err, IsNil) |
344 | c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"}) |
345 | + // the session is happy and redial delayer is default |
346 | + c.Check(sess.ShouldDelay(), Equals, false) |
347 | + c.Check(fmt.Sprintf("%#v", sess.redialDelay), Equals, fmt.Sprintf("%#v", redialDelay)) |
348 | + c.Check(sess.redialDelays, DeepEquals, util.Timeouts()) |
349 | // but no root CAs set |
350 | c.Check(sess.TLS.RootCAs, IsNil) |
351 | c.Check(sess.State(), Equals, Disconnected) |
352 | @@ -264,16 +269,17 @@ |
353 | } |
354 | |
355 | type testHostGetter struct { |
356 | - hosts []string |
357 | - err error |
358 | + domain string |
359 | + hosts []string |
360 | + err error |
361 | } |
362 | |
363 | -func (thg *testHostGetter) Get() ([]string, error) { |
364 | - return thg.hosts, thg.err |
365 | +func (thg *testHostGetter) Get() (*gethosts.Host, error) { |
366 | + return &gethosts.Host{thg.domain, thg.hosts}, thg.err |
367 | } |
368 | |
369 | func (cs *clientSessionSuite) TestGetHostsRemote(c *C) { |
370 | - hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil} |
371 | + hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil} |
372 | sess := &ClientSession{getHost: hostGetter, timeSince: time.Since} |
373 | err := sess.getHosts() |
374 | c.Assert(err, IsNil) |
375 | @@ -284,7 +290,7 @@ |
376 | sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log) |
377 | c.Assert(err, IsNil) |
378 | hostsErr := errors.New("failed") |
379 | - hostGetter := &testHostGetter{nil, hostsErr} |
380 | + hostGetter := &testHostGetter{"", nil, hostsErr} |
381 | sess.getHost = hostGetter |
382 | err = sess.getHosts() |
383 | c.Assert(err, Equals, hostsErr) |
384 | @@ -293,7 +299,7 @@ |
385 | } |
386 | |
387 | func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) { |
388 | - hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil} |
389 | + hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil} |
390 | sess := &ClientSession{ |
391 | getHost: hostGetter, |
392 | ClientSessionConfig: ClientSessionConfig{ |
393 | @@ -318,7 +324,7 @@ |
394 | } |
395 | |
396 | func (cs *clientSessionSuite) TestGetHostsRemoteCachingReset(c *C) { |
397 | - hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil} |
398 | + hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil} |
399 | sess := &ClientSession{ |
400 | getHost: hostGetter, |
401 | ClientSessionConfig: ClientSessionConfig{ |
402 | @@ -427,7 +433,9 @@ |
403 | sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
404 | c.Assert(err, IsNil) |
405 | sess.deliveryHosts = []string{"nowhere"} |
406 | + sess.clearShouldDelay() |
407 | err = sess.connect() |
408 | + c.Check(sess.ShouldDelay(), Equals, true) |
409 | c.Check(err, ErrorMatches, ".*connect.*address.*") |
410 | c.Check(sess.State(), Equals, Error) |
411 | } |
412 | @@ -439,7 +447,9 @@ |
413 | sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
414 | c.Assert(err, IsNil) |
415 | sess.deliveryHosts = []string{srv.Addr().String()} |
416 | + sess.clearShouldDelay() |
417 | err = sess.connect() |
418 | + c.Check(sess.ShouldDelay(), Equals, true) |
419 | c.Check(err, IsNil) |
420 | c.Check(sess.Connection, NotNil) |
421 | c.Check(sess.State(), Equals, Connected) |
422 | @@ -452,7 +462,9 @@ |
423 | sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
424 | c.Assert(err, IsNil) |
425 | sess.deliveryHosts = []string{"nowhere", srv.Addr().String()} |
426 | + sess.clearShouldDelay() |
427 | err = sess.connect() |
428 | + c.Check(sess.ShouldDelay(), Equals, true) |
429 | c.Check(err, IsNil) |
430 | c.Check(sess.Connection, NotNil) |
431 | c.Check(sess.State(), Equals, Connected) |
432 | @@ -466,7 +478,9 @@ |
433 | srv.Close() |
434 | c.Assert(err, IsNil) |
435 | sess.deliveryHosts = []string{srv.Addr().String()} |
436 | + sess.clearShouldDelay() |
437 | err = sess.connect() |
438 | + c.Check(sess.ShouldDelay(), Equals, true) |
439 | c.Check(err, ErrorMatches, ".*connection refused") |
440 | c.Check(sess.State(), Equals, Error) |
441 | } |
442 | @@ -548,6 +562,27 @@ |
443 | c.Check(<-ch, Not(Equals), 0) |
444 | } |
445 | |
446 | +func (cs *clientSessionSuite) TestAutoRedialCallsRedialDelay(c *C) { |
447 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
448 | + c.Assert(err, IsNil) |
449 | + flag := false |
450 | + sess.redialDelay = func(sess *ClientSession) time.Duration { flag = true; return 0 } |
451 | + sess.AutoRedial(nil) |
452 | + c.Check(flag, Equals, true) |
453 | +} |
454 | + |
455 | +func (cs *clientSessionSuite) TestAutoRedialSetsRedialDelayIfTooQuick(c *C) { |
456 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
457 | + c.Assert(err, IsNil) |
458 | + sess.redialDelay = func(sess *ClientSession) time.Duration { return 0 } |
459 | + sess.AutoRedial(nil) |
460 | + c.Check(sess.ShouldDelay(), Equals, false) |
461 | + sess.stopRedial() |
462 | + sess.clearShouldDelay() |
463 | + sess.AutoRedial(nil) |
464 | + c.Check(sess.ShouldDelay(), Equals, true) |
465 | +} |
466 | + |
467 | /**************************************************************** |
468 | handlePing() tests |
469 | ****************************************************************/ |
470 | @@ -594,6 +629,24 @@ |
471 | c.Check(s.sess.State(), Equals, Error) |
472 | } |
473 | |
474 | +func (s *msgSuite) TestHandlePingClearsDelay(c *C) { |
475 | + s.sess.setShouldDelay() |
476 | + s.upCh <- nil // no error |
477 | + c.Check(s.sess.handlePing(), IsNil) |
478 | + c.Assert(len(s.downCh), Equals, 1) |
479 | + c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"}) |
480 | + c.Check(s.sess.ShouldDelay(), Equals, false) |
481 | +} |
482 | + |
483 | +func (s *msgSuite) TestHandlePingDoesNotClearsDelayOnError(c *C) { |
484 | + s.sess.setShouldDelay() |
485 | + s.upCh <- errors.New("Pong") |
486 | + c.Check(s.sess.handlePing(), NotNil) |
487 | + c.Assert(len(s.downCh), Equals, 1) |
488 | + c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"}) |
489 | + c.Check(s.sess.ShouldDelay(), Equals, true) |
490 | +} |
491 | + |
492 | /**************************************************************** |
493 | handleBroadcast() tests |
494 | ****************************************************************/ |
495 | @@ -687,6 +740,32 @@ |
496 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"nak"}) |
497 | } |
498 | |
499 | +func (s *msgSuite) TestHandleBroadcastClearsDelay(c *C) { |
500 | + s.sess.setShouldDelay() |
501 | + |
502 | + msg := serverMsg{"broadcast", protocol.BroadcastMsg{}, |
503 | + protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}} |
504 | + go func() { s.errCh <- s.sess.handleBroadcast(&msg) }() |
505 | + c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
506 | + s.upCh <- nil // ack ok |
507 | + c.Check(<-s.errCh, IsNil) |
508 | + |
509 | + c.Check(s.sess.ShouldDelay(), Equals, false) |
510 | +} |
511 | + |
512 | +func (s *msgSuite) TestHandleBroadcastDoesNotClearDelayOnError(c *C) { |
513 | + s.sess.setShouldDelay() |
514 | + |
515 | + msg := serverMsg{"broadcast", protocol.BroadcastMsg{}, |
516 | + protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}} |
517 | + go func() { s.errCh <- s.sess.handleBroadcast(&msg) }() |
518 | + c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
519 | + s.upCh <- errors.New("bcast") |
520 | + c.Check(<-s.errCh, NotNil) |
521 | + |
522 | + c.Check(s.sess.ShouldDelay(), Equals, true) |
523 | +} |
524 | + |
525 | /**************************************************************** |
526 | handleConnBroken() tests |
527 | ****************************************************************/ |
528 | @@ -1087,9 +1166,64 @@ |
529 | |
530 | var ( |
531 | dialTestTimeout = 100 * time.Millisecond |
532 | - dialTestConf = ClientSessionConfig{ExchangeTimeout: dialTestTimeout} |
533 | + dialTestConf = ClientSessionConfig{ |
534 | + ExchangeTimeout: dialTestTimeout, |
535 | + PEM: helpers.TestCertPEMBlock, |
536 | + } |
537 | ) |
538 | |
539 | +func (cs *clientSessionSuite) TestDialBadServerName(c *C) { |
540 | + // a borked server name |
541 | + cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock) |
542 | + c.Assert(err, IsNil) |
543 | + tlsCfg := &tls.Config{ |
544 | + Certificates: []tls.Certificate{cert}, |
545 | + SessionTicketsDisabled: true, |
546 | + } |
547 | + |
548 | + lst, err := tls.Listen("tcp", "localhost:0", tlsCfg) |
549 | + c.Assert(err, IsNil) |
550 | + // advertise |
551 | + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
552 | + b, err := json.Marshal(map[string]interface{}{ |
553 | + "domain": "xyzzy", // <-- *** THIS *** is the bit that'll break it |
554 | + "hosts": []string{"nowhere", lst.Addr().String()}, |
555 | + }) |
556 | + if err != nil { |
557 | + panic(err) |
558 | + } |
559 | + w.Header().Set("Content-Type", "application/json") |
560 | + w.Write(b) |
561 | + })) |
562 | + defer ts.Close() |
563 | + |
564 | + sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log) |
565 | + c.Assert(err, IsNil) |
566 | + tconn := &testConn{} |
567 | + sess.Connection = tconn |
568 | + |
569 | + upCh := make(chan interface{}, 5) |
570 | + downCh := make(chan interface{}, 5) |
571 | + errCh := make(chan error, 1) |
572 | + proto := &testProtocol{up: upCh, down: downCh} |
573 | + sess.Protocolator = func(net.Conn) protocol.Protocol { return proto } |
574 | + |
575 | + go func() { |
576 | + errCh <- sess.Dial() |
577 | + }() |
578 | + |
579 | + srv, err := lst.Accept() |
580 | + c.Assert(err, IsNil) |
581 | + |
582 | + // connect done |
583 | + |
584 | + _, err = protocol.ReadWireFormatVersion(srv, dialTestTimeout) |
585 | + c.Check(err, NotNil) |
586 | + |
587 | + c.Check(<-errCh, NotNil) |
588 | + c.Check(sess.State(), Equals, Error) |
589 | +} |
590 | + |
591 | func (cs *clientSessionSuite) TestDialWorks(c *C) { |
592 | // happy path thoughts |
593 | cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock) |
594 | @@ -1104,7 +1238,8 @@ |
595 | // advertise |
596 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
597 | b, err := json.Marshal(map[string]interface{}{ |
598 | - "hosts": []string{"nowhere", lst.Addr().String()}, |
599 | + "domain": "localhost", |
600 | + "hosts": []string{"nowhere", lst.Addr().String()}, |
601 | }) |
602 | if err != nil { |
603 | panic(err) |
604 | @@ -1223,3 +1358,38 @@ |
605 | c.Assert(err, IsNil) |
606 | // connect done |
607 | } |
608 | + |
609 | +/**************************************************************** |
610 | + redialDelay() tests |
611 | +****************************************************************/ |
612 | + |
613 | +func (cs *clientSessionSuite) TestShouldDelay(c *C) { |
614 | + sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log) |
615 | + c.Assert(err, IsNil) |
616 | + c.Check(sess.ShouldDelay(), Equals, false) |
617 | + sess.setShouldDelay() |
618 | + c.Check(sess.ShouldDelay(), Equals, true) |
619 | + sess.clearShouldDelay() |
620 | + c.Check(sess.ShouldDelay(), Equals, false) |
621 | +} |
622 | + |
623 | +func (cs *clientSessionSuite) TestRedialDelay(c *C) { |
624 | + sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log) |
625 | + c.Assert(err, IsNil) |
626 | + sess.redialDelays = []time.Duration{17, 42} |
627 | + n := 0 |
628 | + sess.redialJitter = func(time.Duration) time.Duration { n++; return 0 } |
629 | + // we get increasing delays while we're unhappy |
630 | + sess.setShouldDelay() |
631 | + c.Check(redialDelay(sess), Equals, time.Duration(17)) |
632 | + c.Check(redialDelay(sess), Equals, time.Duration(42)) |
633 | + c.Check(redialDelay(sess), Equals, time.Duration(42)) |
634 | + // once we're happy, delays drop to 0 |
635 | + sess.clearShouldDelay() |
636 | + c.Check(redialDelay(sess), Equals, time.Duration(0)) |
637 | + // and start again from the top if we become unhappy again |
638 | + sess.setShouldDelay() |
639 | + c.Check(redialDelay(sess), Equals, time.Duration(17)) |
640 | + // and redialJitter got called every time shouldDelay was true |
641 | + c.Check(n, Equals, 4) |
642 | +} |
643 | |
644 | === modified file 'config/config.go' |
645 | --- config/config.go 2014-03-25 18:49:18 +0000 |
646 | +++ config/config.go 2014-04-23 11:53:01 +0000 |
647 | @@ -20,6 +20,7 @@ |
648 | import ( |
649 | "encoding/json" |
650 | "errors" |
651 | + "flag" |
652 | "fmt" |
653 | "io" |
654 | "io/ioutil" |
655 | @@ -27,6 +28,7 @@ |
656 | "os" |
657 | "path/filepath" |
658 | "reflect" |
659 | + "strconv" |
660 | "strings" |
661 | "time" |
662 | ) |
663 | @@ -118,6 +120,22 @@ |
664 | return fillDestConfig(destValue, p1) |
665 | } |
666 | |
667 | +// FromString are config holders that can be set by parsing a string. |
668 | +type FromString interface { |
669 | + SetFromString(enc string) error |
670 | +} |
671 | + |
672 | +// UnmarshalJSONViaString helps unmarshalling from JSON for FromString |
673 | +// supporting config holders. |
674 | +func UnmarshalJSONViaString(dest FromString, b []byte) error { |
675 | + var enc string |
676 | + err := json.Unmarshal(b, &enc) |
677 | + if err != nil { |
678 | + return err |
679 | + } |
680 | + return dest.SetFromString(enc) |
681 | +} |
682 | + |
683 | // ConfigTimeDuration can hold a time.Duration in a configuration struct, |
684 | // that is parsed from a string as supported by time.ParseDuration. |
685 | type ConfigTimeDuration struct { |
686 | @@ -125,13 +143,11 @@ |
687 | } |
688 | |
689 | func (ctd *ConfigTimeDuration) UnmarshalJSON(b []byte) error { |
690 | - var enc string |
691 | - var v time.Duration |
692 | - err := json.Unmarshal(b, &enc) |
693 | - if err != nil { |
694 | - return err |
695 | - } |
696 | - v, err = time.ParseDuration(enc) |
697 | + return UnmarshalJSONViaString(ctd, b) |
698 | +} |
699 | + |
700 | +func (ctd *ConfigTimeDuration) SetFromString(enc string) error { |
701 | + v, err := time.ParseDuration(enc) |
702 | if err != nil { |
703 | return err |
704 | } |
705 | @@ -148,12 +164,11 @@ |
706 | type ConfigHostPort string |
707 | |
708 | func (chp *ConfigHostPort) UnmarshalJSON(b []byte) error { |
709 | - var enc string |
710 | - err := json.Unmarshal(b, &enc) |
711 | - if err != nil { |
712 | - return err |
713 | - } |
714 | - _, _, err = net.SplitHostPort(enc) |
715 | + return UnmarshalJSONViaString(chp, b) |
716 | +} |
717 | + |
718 | +func (chp *ConfigHostPort) SetFromString(enc string) error { |
719 | + _, _, err := net.SplitHostPort(enc) |
720 | if err != nil { |
721 | return err |
722 | } |
723 | @@ -198,23 +213,117 @@ |
724 | return ioutil.ReadFile(p) |
725 | } |
726 | |
727 | -// ReadFiles reads configuration from a set of files. Uses ReadConfig internally. |
728 | +// used to implement getting config values with flag.Parse() |
729 | +type val struct { |
730 | + destField destField |
731 | + accu map[string]json.RawMessage |
732 | +} |
733 | + |
734 | +func (v *val) String() string { // used to show default |
735 | + return string(v.accu[v.destField.configName()]) |
736 | +} |
737 | + |
738 | +func (v *val) IsBoolFlag() bool { |
739 | + return v.destField.fld.Type.Kind() == reflect.Bool |
740 | +} |
741 | + |
742 | +func (v *val) marshalAsNeeded(s string) (json.RawMessage, error) { |
743 | + var toMarshal interface{} |
744 | + switch v.destField.dest.(type) { |
745 | + case *string, FromString: |
746 | + toMarshal = s |
747 | + case *bool: |
748 | + bit, err := strconv.ParseBool(s) |
749 | + if err != nil { |
750 | + return nil, err |
751 | + } |
752 | + toMarshal = bit |
753 | + default: |
754 | + return json.RawMessage(s), nil |
755 | + } |
756 | + return json.Marshal(toMarshal) |
757 | +} |
758 | + |
759 | +func (v *val) Set(s string) error { |
760 | + marshalled, err := v.marshalAsNeeded(s) |
761 | + if err != nil { |
762 | + return err |
763 | + } |
764 | + v.accu[v.destField.configName()] = marshalled |
765 | + return nil |
766 | +} |
767 | + |
768 | +func readOneConfig(accu map[string]json.RawMessage, cfgPath string) error { |
769 | + r, err := os.Open(cfgPath) |
770 | + if err != nil { |
771 | + return err |
772 | + } |
773 | + defer r.Close() |
774 | + err = json.NewDecoder(r).Decode(&accu) |
775 | + if err != nil { |
776 | + return err |
777 | + } |
778 | + return nil |
779 | +} |
780 | + |
781 | +// used to implement -cfg@= |
782 | +type readConfigAtVal struct { |
783 | + accu map[string]json.RawMessage |
784 | +} |
785 | + |
786 | +func (v *readConfigAtVal) String() string { |
787 | + return "<config.json>" |
788 | +} |
789 | + |
790 | +func (v *readConfigAtVal) Set(path string) error { |
791 | + return readOneConfig(v.accu, path) |
792 | +} |
793 | + |
794 | +// readUsingFlags gets config values from command line flags. |
795 | +func readUsingFlags(accu map[string]json.RawMessage, destValue reflect.Value) error { |
796 | + if flag.Parsed() { |
797 | + if IgnoreParsedFlags { |
798 | + return nil |
799 | + } |
800 | + return fmt.Errorf("too late, flags already parsed") |
801 | + } |
802 | + destStruct := destValue.Elem() |
803 | + for destField := range traverseStruct(destStruct) { |
804 | + help := destField.fld.Tag.Get("help") |
805 | + flag.Var(&val{destField, accu}, destField.configName(), help) |
806 | + } |
807 | + flag.Var(&readConfigAtVal{accu}, "cfg@", "get config values from file") |
808 | + flag.Parse() |
809 | + return nil |
810 | +} |
811 | + |
812 | +// IgnoreParsedFlags will just have ReadFiles ignore <flags> if the |
813 | +// command line was already parsed. |
814 | +var IgnoreParsedFlags = false |
815 | + |
816 | +// ReadFiles reads configuration from a set of files. The string |
817 | +// "<flags>" can be used as a pseudo file-path, it will consider |
818 | +// command line flags, invoking flag.Parse(). Among those the flag |
819 | +// -cfg@=FILE can be used to get further config values from FILE. |
820 | func ReadFiles(destConfig interface{}, cfgFpaths ...string) error { |
821 | destValue, err := checkDestConfig("destConfig", destConfig) |
822 | if err != nil { |
823 | return err |
824 | } |
825 | // do the parsing in two phases for better error handling |
826 | - var p1 map[string]json.RawMessage |
827 | + p1 := make(map[string]json.RawMessage) |
828 | readOne := false |
829 | for _, cfgPath := range cfgFpaths { |
830 | + if cfgPath == "<flags>" { |
831 | + err := readUsingFlags(p1, destValue) |
832 | + if err != nil { |
833 | + return err |
834 | + } |
835 | + readOne = true |
836 | + continue |
837 | + } |
838 | if _, err := os.Stat(cfgPath); err == nil { |
839 | - r, err := os.Open(cfgPath) |
840 | - if err != nil { |
841 | - return err |
842 | - } |
843 | - defer r.Close() |
844 | - err = json.NewDecoder(r).Decode(&p1) |
845 | + err := readOneConfig(p1, cfgPath) |
846 | if err != nil { |
847 | return err |
848 | } |
849 | |
850 | === modified file 'config/config_test.go' |
851 | --- config/config_test.go 2014-03-25 18:49:18 +0000 |
852 | +++ config/config_test.go 2014-04-23 11:53:01 +0000 |
853 | @@ -18,6 +18,9 @@ |
854 | |
855 | import ( |
856 | "bytes" |
857 | + "encoding/json" |
858 | + "flag" |
859 | + "fmt" |
860 | "io/ioutil" |
861 | "os" |
862 | "path/filepath" |
863 | @@ -230,3 +233,105 @@ |
864 | c.Check(res, DeepEquals, []string{"b", "c_list", "d"}) |
865 | |
866 | } |
867 | + |
868 | +type testConfig3 struct { |
869 | + A bool |
870 | + B string |
871 | + C []string `json:"c_list"` |
872 | + D ConfigTimeDuration `help:"duration"` |
873 | + E ConfigHostPort |
874 | + F string |
875 | +} |
876 | + |
877 | +type configFlagsSuite struct{} |
878 | + |
879 | +var _ = Suite(&configFlagsSuite{}) |
880 | + |
881 | +func (s *configFlagsSuite) SetUpTest(c *C) { |
882 | + flag.CommandLine = flag.NewFlagSet("cmd", flag.PanicOnError) |
883 | + // supress outputs |
884 | + flag.Usage = func() { flag.PrintDefaults() } |
885 | + flag.CommandLine.SetOutput(ioutil.Discard) |
886 | +} |
887 | + |
888 | +func (s *configFlagsSuite) TestReadUsingFlags(c *C) { |
889 | + os.Args = []string{"cmd", "-a=1", "-b=foo", "-c_list", `["x","y"]`, "-d", "10s", "-e=localhost:80"} |
890 | + var cfg testConfig3 |
891 | + p := make(map[string]json.RawMessage) |
892 | + err := readUsingFlags(p, reflect.ValueOf(&cfg)) |
893 | + c.Assert(err, IsNil) |
894 | + c.Check(p, DeepEquals, map[string]json.RawMessage{ |
895 | + "a": json.RawMessage("true"), |
896 | + "b": json.RawMessage(`"foo"`), |
897 | + "c_list": json.RawMessage(`["x","y"]`), |
898 | + "d": json.RawMessage(`"10s"`), |
899 | + "e": json.RawMessage(`"localhost:80"`), |
900 | + }) |
901 | +} |
902 | + |
903 | +func (s *configFlagsSuite) TestReadUsingFlagsBoolError(c *C) { |
904 | + os.Args = []string{"cmd", "-a=zoo"} |
905 | + var cfg testConfig3 |
906 | + p := make(map[string]json.RawMessage) |
907 | + c.Check(func() { readUsingFlags(p, reflect.ValueOf(&cfg)) }, PanicMatches, ".*invalid boolean.*-a.*") |
908 | +} |
909 | + |
910 | +func (s *configFlagsSuite) TestReadFilesAndFlags(c *C) { |
911 | + // test <flags> pseudo file |
912 | + os.Args = []string{"cmd", "-b=x"} |
913 | + tmpDir := c.MkDir() |
914 | + cfgPath := filepath.Join(tmpDir, "cfg.json") |
915 | + err := ioutil.WriteFile(cfgPath, []byte(`{"a": 42, "c_list": ["y", "z"]}`), os.ModePerm) |
916 | + c.Assert(err, IsNil) |
917 | + var cfg testConfig1 |
918 | + err = ReadFiles(&cfg, cfgPath, "<flags>") |
919 | + c.Assert(err, IsNil) |
920 | + c.Check(cfg.A, Equals, 42) |
921 | + c.Check(cfg.B, Equals, "x") |
922 | + c.Check(cfg.C, DeepEquals, []string{"y", "z"}) |
923 | +} |
924 | + |
925 | +func (s *configFlagsSuite) TestReadFilesAndFlagsConfigAtSupport(c *C) { |
926 | + // test <flags> pseudo file |
927 | + tmpDir := c.MkDir() |
928 | + cfgPath := filepath.Join(tmpDir, "cfg.json") |
929 | + os.Args = []string{"cmd", "-a=42", fmt.Sprintf("-cfg@=%s", cfgPath)} |
930 | + err := ioutil.WriteFile(cfgPath, []byte(`{"b": "x", "c_list": ["y", "z"]}`), os.ModePerm) |
931 | + c.Assert(err, IsNil) |
932 | + var cfg testConfig1 |
933 | + err = ReadFiles(&cfg, "<flags>") |
934 | + c.Assert(err, IsNil) |
935 | + c.Check(cfg.A, Equals, 42) |
936 | + c.Check(cfg.B, Equals, "x") |
937 | + c.Check(cfg.C, DeepEquals, []string{"y", "z"}) |
938 | +} |
939 | + |
940 | +func (s *configFlagsSuite) TestReadUsingFlagsHelp(c *C) { |
941 | + os.Args = []string{"cmd", "-h"} |
942 | + buf := bytes.NewBufferString("") |
943 | + flag.CommandLine.Init("cmd", flag.ContinueOnError) |
944 | + flag.CommandLine.SetOutput(buf) |
945 | + var cfg testConfig3 |
946 | + p := map[string]json.RawMessage{ |
947 | + "d": json.RawMessage(`"2s"`), |
948 | + } |
949 | + readUsingFlags(p, reflect.ValueOf(&cfg)) |
950 | + c.Check(buf.String(), Matches, `(?s).*-cfg@=<config.json>: get config values from file\n.*-d="2s": duration.*`) |
951 | +} |
952 | + |
953 | +func (s *configFlagsSuite) TestReadUsingFlagsAlreadyParsed(c *C) { |
954 | + os.Args = []string{"cmd"} |
955 | + flag.Parse() |
956 | + var cfg struct{} |
957 | + p := make(map[string]json.RawMessage) |
958 | + err := readUsingFlags(p, reflect.ValueOf(&cfg)) |
959 | + c.Assert(err, ErrorMatches, "too late, flags already parsed") |
960 | + err = ReadFiles(&cfg, "<flags>") |
961 | + c.Assert(err, ErrorMatches, "too late, flags already parsed") |
962 | + IgnoreParsedFlags = true |
963 | + defer func() { |
964 | + IgnoreParsedFlags = false |
965 | + }() |
966 | + err = ReadFiles(&cfg, "<flags>") |
967 | + c.Assert(err, IsNil) |
968 | +} |
969 | |
970 | === modified file 'debian/changelog' |
971 | --- debian/changelog 2014-04-11 18:31:57 +0000 |
972 | +++ debian/changelog 2014-04-23 11:53:01 +0000 |
973 | @@ -1,3 +1,22 @@ |
974 | +ubuntu-push (0.2.1-0ubuntu1) UNRELEASED; urgency=high |
975 | + |
976 | + [ Samuele Pedroni ] |
977 | + * gave the client the ability to get config from commandline |
978 | + ( => easier automated testing) (LP: #1311600) |
979 | + |
980 | + [ John Lenton ] |
981 | + * Ensure ubuntu-push-client is the only one running in the session. |
982 | + (LP: #1309432) |
983 | + * Remove supurious numbers in brackets in notifications. (LP: #1308145) |
984 | + * Check the server certificate and server name. (LP: #1297969) |
985 | + * Loop whoopsie_identifier_generate until it starts working. (LP: #1309237) |
986 | + * In the session: set a flag on connect, clear it on successfully |
987 | + replying to ping or broadcast messages, check it at the top of |
988 | + autoredial. Also track the last autoredial, and set the delay flag if |
989 | + autoredial is re-called too quickly. (LP: #1309231) |
990 | + |
991 | + -- John Lenton <john.lenton@canonical.com> Fri, 18 Apr 2014 10:42:31 +0100 |
992 | + |
993 | ubuntu-push (0.2+14.04.20140411-0ubuntu1) trusty; urgency=medium |
994 | |
995 | [ John Lenton ] |
996 | |
997 | === modified file 'logger/logger.go' |
998 | --- logger/logger.go 2014-02-24 10:27:38 +0000 |
999 | +++ logger/logger.go 2014-04-23 11:53:01 +0000 |
1000 | @@ -23,6 +23,8 @@ |
1001 | "log" |
1002 | "os" |
1003 | "runtime" |
1004 | + |
1005 | + "launchpad.net/ubuntu-push/config" |
1006 | ) |
1007 | |
1008 | // Logger is a simple logger interface with logging at levels. |
1009 | @@ -119,3 +121,28 @@ |
1010 | lg.outputFunc(2, fmt.Sprintf("DEBUG "+format, v...)) |
1011 | } |
1012 | } |
1013 | + |
1014 | +// config bits |
1015 | + |
1016 | +// ConfigLogLevel can hold a log level in a configuration struct. |
1017 | +type ConfigLogLevel string |
1018 | + |
1019 | +func (cll *ConfigLogLevel) ConfigFromJSONString() {} |
1020 | + |
1021 | +func (cll *ConfigLogLevel) UnmarshalJSON(b []byte) error { |
1022 | + return config.UnmarshalJSONViaString(cll, b) |
1023 | +} |
1024 | + |
1025 | +func (cll *ConfigLogLevel) SetFromString(enc string) error { |
1026 | + _, ok := levelToNLevel[enc] |
1027 | + if !ok { |
1028 | + return fmt.Errorf("not a log level: %s", enc) |
1029 | + } |
1030 | + *cll = ConfigLogLevel(enc) |
1031 | + return nil |
1032 | +} |
1033 | + |
1034 | +// Level returns the log level string held in cll. |
1035 | +func (cll ConfigLogLevel) Level() string { |
1036 | + return string(cll) |
1037 | +} |
1038 | |
1039 | === modified file 'logger/logger_test.go' |
1040 | --- logger/logger_test.go 2014-02-10 22:51:43 +0000 |
1041 | +++ logger/logger_test.go 2014-04-23 11:53:01 +0000 |
1042 | @@ -25,6 +25,8 @@ |
1043 | "testing" |
1044 | |
1045 | . "launchpad.net/gocheck" |
1046 | + |
1047 | + "launchpad.net/ubuntu-push/config" |
1048 | ) |
1049 | |
1050 | func TestLogger(t *testing.T) { TestingT(t) } |
1051 | @@ -138,3 +140,26 @@ |
1052 | logger.Output(1, "foobaz") |
1053 | c.Check(buf.String(), Matches, "logger_test.go:[0-9]+: foobar\nlogger_test.go:[0-9]+: foobaz\n") |
1054 | } |
1055 | + |
1056 | +type testLogLevelConfig struct { |
1057 | + Lvl ConfigLogLevel |
1058 | +} |
1059 | + |
1060 | +func (s *loggerSuite) TestReadConfigLogLevel(c *C) { |
1061 | + buf := bytes.NewBufferString(`{"lvl": "debug"}`) |
1062 | + var cfg testLogLevelConfig |
1063 | + err := config.ReadConfig(buf, &cfg) |
1064 | + c.Assert(err, IsNil) |
1065 | + c.Check(cfg.Lvl.Level(), Equals, "debug") |
1066 | +} |
1067 | + |
1068 | +func (s *loggerSuite) TestReadConfigLogLevelErrors(c *C) { |
1069 | + var cfg testLogLevelConfig |
1070 | + checkError := func(jsonCfg string, expectedError string) { |
1071 | + buf := bytes.NewBufferString(jsonCfg) |
1072 | + err := config.ReadConfig(buf, &cfg) |
1073 | + c.Check(err, ErrorMatches, expectedError) |
1074 | + } |
1075 | + checkError(`{"lvl": 1}`, "lvl:.*type string") |
1076 | + checkError(`{"lvl": "foo"}`, "lvl: not a log level: foo") |
1077 | +} |
1078 | |
1079 | === modified file 'server/acceptance/cmd/acceptanceclient.go' |
1080 | --- server/acceptance/cmd/acceptanceclient.go 2014-04-10 13:52:31 +0000 |
1081 | +++ server/acceptance/cmd/acceptanceclient.go 2014-04-23 11:53:01 +0000 |
1082 | @@ -48,13 +48,18 @@ |
1083 | fmt.Fprintf(os.Stderr, "Usage: acceptancclient [options] <config.json> <device id>\n") |
1084 | flag.PrintDefaults() |
1085 | } |
1086 | + missingArg := func(what string) { |
1087 | + fmt.Fprintf(os.Stderr, "missing %s\n", what) |
1088 | + flag.Usage() |
1089 | + os.Exit(2) |
1090 | + } |
1091 | flag.Parse() |
1092 | narg := flag.NArg() |
1093 | switch { |
1094 | case narg < 1: |
1095 | - log.Fatal("missing config file") |
1096 | + missingArg("config file") |
1097 | case narg < 2: |
1098 | - log.Fatal("missing device-id") |
1099 | + missingArg("device-id") |
1100 | } |
1101 | configFName := flag.Arg(0) |
1102 | f, err := os.Open(configFName) |
1103 | |
1104 | === modified file 'ubuntu-push-client.go' |
1105 | --- ubuntu-push-client.go 2014-03-12 13:25:20 +0000 |
1106 | +++ ubuntu-push-client.go 2014-04-23 11:53:01 +0000 |
1107 | @@ -19,12 +19,37 @@ |
1108 | import ( |
1109 | "log" |
1110 | |
1111 | + "launchpad.net/go-dbus/v1" |
1112 | "launchpad.net/go-xdg/v0" |
1113 | |
1114 | "launchpad.net/ubuntu-push/client" |
1115 | ) |
1116 | |
1117 | +const NAME = "com.ubuntu.PushNotifications" |
1118 | + |
1119 | +// grabName grabs ownership of the dbus name, and bails the client as |
1120 | +// soon as somebody else grabs it. |
1121 | +func grabName() { |
1122 | + conn, err := dbus.Connect(dbus.SessionBus) |
1123 | + if err != nil { |
1124 | + log.Fatalf("bus: %v", err) |
1125 | + } |
1126 | + |
1127 | + flags := dbus.NameFlagAllowReplacement | dbus.NameFlagReplaceExisting |
1128 | + n := conn.RequestName(NAME, flags) |
1129 | + go func() { |
1130 | + for err := range n.C { |
1131 | + if err != nil { |
1132 | + log.Fatalf("FATAL: name channel got: %v", err) |
1133 | + } |
1134 | + } |
1135 | + }() |
1136 | +} |
1137 | + |
1138 | func main() { |
1139 | + // XXX: this is a quick hack to ensure unicity |
1140 | + grabName() |
1141 | + |
1142 | cfgFname, err := xdg.Config.Find("ubuntu-push-client/config.json") |
1143 | if err != nil { |
1144 | log.Fatalf("unable to find a configuration file: %v", err) |
1145 | |
1146 | === modified file 'whoopsie/identifier/identifier.go' |
1147 | --- whoopsie/identifier/identifier.go 2014-02-21 16:17:28 +0000 |
1148 | +++ whoopsie/identifier/identifier.go 2014-04-23 11:53:01 +0000 |
1149 | @@ -27,6 +27,7 @@ |
1150 | import "C" |
1151 | import "unsafe" |
1152 | import "errors" |
1153 | +import "time" |
1154 | |
1155 | // an Id knows how to generate itself, and how to stringify itself. |
1156 | type Id interface { |
1157 | @@ -36,12 +37,17 @@ |
1158 | |
1159 | // Identifier is the default Id implementation. |
1160 | type Identifier struct { |
1161 | - value string |
1162 | + value string |
1163 | + generator func(**C.char, **C.GError) |
1164 | +} |
1165 | + |
1166 | +func generator(csp **C.char, errp **C.GError) { |
1167 | + C.whoopsie_identifier_generate(csp, errp) |
1168 | } |
1169 | |
1170 | // New creates an Identifier, but does not call Generate() on it. |
1171 | func New() Id { |
1172 | - return &Identifier{} |
1173 | + return &Identifier{generator: generator} |
1174 | } |
1175 | |
1176 | // Generate makes the Identifier create the identifier itself. |
1177 | @@ -49,8 +55,18 @@ |
1178 | var gerr *C.GError |
1179 | var cs *C.char |
1180 | defer C.g_free((C.gpointer)(unsafe.Pointer(cs))) |
1181 | - C.whoopsie_identifier_generate(&cs, &gerr) |
1182 | - |
1183 | + |
1184 | + for i := 0; i < 200; i++ { |
1185 | + id.generator(&cs, &gerr) |
1186 | + |
1187 | + if cs != nil || gerr != nil { |
1188 | + goto SuccessMaybe |
1189 | + } |
1190 | + time.Sleep(600 * time.Millisecond) |
1191 | + } |
1192 | + return errors.New("whoopsie_identifier_generate still bad after 2m; giving up") |
1193 | + |
1194 | +SuccessMaybe: |
1195 | if gerr != nil { |
1196 | return errors.New(C.GoString((*C.char)(gerr.message))) |
1197 | } else { |
1198 | |
1199 | === modified file 'whoopsie/identifier/identifier_test.go' |
1200 | --- whoopsie/identifier/identifier_test.go 2014-01-15 15:51:50 +0000 |
1201 | +++ whoopsie/identifier/identifier_test.go 2014-04-23 11:53:01 +0000 |
1202 | @@ -41,3 +41,18 @@ |
1203 | func (s *IdentifierSuite) TestIdentifierInterface(c *C) { |
1204 | _ = []Id{New()} |
1205 | } |
1206 | + |
1207 | +// TestFailure checks that Identifier survives whoopsie shenanigans |
1208 | +func (s *IdentifierSuite) TestIdentifierSurvivesShenanigans(c *C) { |
1209 | + count := 0 |
1210 | + // using _Ctype* as a workaround for gocheck also having a C |
1211 | + gen := func(csp **_Ctype_char, errp **_Ctype_GError) { |
1212 | + count++ |
1213 | + if count > 3 { |
1214 | + generator(csp, errp) |
1215 | + } |
1216 | + } |
1217 | + id := &Identifier{generator: gen} |
1218 | + id.Generate() |
1219 | + c.Check(id.String(), HasLen, 128) |
1220 | +} |