Merge lp:~pedronis/ubuntu-push/be-nice into lp:ubuntu-push
- be-nice
- Merge into trunk
Proposed by
Samuele Pedroni
Status: | Superseded |
---|---|
Proposed branch: | lp:~pedronis/ubuntu-push/be-nice |
Merge into: | lp:ubuntu-push |
Diff against target: |
1379 lines (+633/-144) 12 files modified
.bzrignore (+2/-0) Makefile (+18/-6) README (+12/-3) client/client.go (+31/-5) client/client_test.go (+93/-44) client/gethosts/gethost_test.go (+5/-7) client/session/session.go (+156/-31) client/session/session_test.go (+307/-42) debian/config.json (+3/-0) sampleconfigs/dev.json (+2/-2) server/acceptance/suites/helpers.go (+2/-2) server/acceptance/suites/suite.go (+2/-2) |
To merge this branch: | bzr merge lp:~pedronis/ubuntu-push/be-nice |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Push Hackers | Pending | ||
Review via email: mp+213511@code.launchpad.net |
Commit message
update README, Makefile with more targets, move sample dev config to sampleconfigs
Description of the change
update README, Makefile with more targets, move sample dev config to sampleconfigs
To post a comment you must log in.
lp:~pedronis/ubuntu-push/be-nice
updated
- 101. By Samuele Pedroni
-
newline
- 102. By Samuele Pedroni
-
api port => 8080
- 103. By Samuele Pedroni
-
final tweaks
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2014-02-07 19:36:38 +0000 |
3 | +++ .bzrignore 2014-03-31 16:43:44 +0000 |
4 | @@ -11,3 +11,5 @@ |
5 | debian/*.ex |
6 | debian/*.EX |
7 | debian/*.substvars |
8 | +ubuntu-push-client |
9 | +push-server-dev |
10 | |
11 | === modified file 'Makefile' |
12 | --- Makefile 2014-03-12 13:23:26 +0000 |
13 | +++ Makefile 2014-03-31 16:43:44 +0000 |
14 | @@ -12,6 +12,8 @@ |
15 | GODEPS += launchpad.net/go-xdg/v0 |
16 | GODEPS += code.google.com/p/gosqlite/sqlite3 |
17 | |
18 | +TOTEST = $(shell env GOPATH=$(GOPATH) go list $(PROJECT)/...|grep -v acceptance{|grep -v http13client ) |
19 | + |
20 | bootstrap: |
21 | mkdir -p $(GOPATH)/bin |
22 | mkdir -p $(GOPATH)/pkg |
23 | @@ -21,17 +23,26 @@ |
24 | go install $(GODEPS) |
25 | |
26 | check: |
27 | - go test $(TESTFLAGS) $(PROJECT)/... |
28 | + go test $(TESTFLAGS) $(TOTEST) |
29 | |
30 | check-race: |
31 | - go test $(TESTFLAGS) -race $(PROJECT)/... |
32 | + go test $(TESTFLAGS) -race $(TOTEST) |
33 | + |
34 | +acceptance: |
35 | + cd server/acceptance; ./acceptance.sh |
36 | + |
37 | +build-client: |
38 | + go build ubuntu-push-client.go |
39 | + |
40 | +build-server-dev: |
41 | + go build -o push-server-dev launchpad.net/ubuntu-push/server/dev |
42 | |
43 | coverage-summary: |
44 | - go test $(TESTFLAGS) -a -cover $(PROJECT)/... |
45 | + go test $(TESTFLAGS) -a -cover $(TOTEST) |
46 | |
47 | coverage-html: |
48 | mkdir -p coverhtml |
49 | - for pkg in $$(go list $(PROJECT)/...|grep -v acceptance ); do \ |
50 | + for pkg in $(TOTEST); do \ |
51 | relname="$${pkg#$(PROJECT)/}" ; \ |
52 | mkdir -p coverhtml/$$(dirname $${relname}) ; \ |
53 | go test $(TESTFLAGS) -a -coverprofile=coverhtml/$${relname}.out $$pkg ; \ |
54 | @@ -52,5 +63,6 @@ |
55 | # requires graphviz installed |
56 | dot -Tsvg $< > $@ |
57 | |
58 | -.PHONY: bootstrap check check-race format check-format coverage-summary \ |
59 | - coverage-html protocol-diagrams |
60 | +.PHONY: bootstrap check check-race format check-format \ |
61 | + acceptance build-client bluild-server-dev \ |
62 | + coverage-summary coverage-html protocol-diagrams |
63 | |
64 | === modified file 'README' |
65 | --- README 2014-02-21 16:17:28 +0000 |
66 | +++ README 2014-03-31 16:43:44 +0000 |
67 | @@ -15,8 +15,7 @@ |
68 | make check |
69 | |
70 | To produce coverage reports you need Go 1.2 (default on Trusty) and |
71 | -the cover tool (the latter can be obtained atm with something like: |
72 | -sudo GOPATH=<go-workspace> go get code.google.com/p/go.tools/cmd/cover ), |
73 | +the cover tool (in the golang-go.tools package), |
74 | then run: |
75 | |
76 | make coverage-summary |
77 | @@ -31,4 +30,14 @@ |
78 | |
79 | To run the acceptance tests, change to the acceptance subdir and run: |
80 | |
81 | - ./acceptance.sh |
82 | + make acceptance |
83 | + |
84 | +There are build targets to build the client: |
85 | + |
86 | + make build-client |
87 | + |
88 | +building ubuntu-push-client, and the development server: |
89 | + |
90 | + make build-server-dev |
91 | + |
92 | +building push-server-dev. |
93 | \ No newline at end of file |
94 | |
95 | === modified file 'client/client.go' |
96 | --- client/client.go 2014-03-26 16:26:36 +0000 |
97 | +++ client/client.go 2014-03-31 16:43:44 +0000 |
98 | @@ -20,9 +20,14 @@ |
99 | |
100 | import ( |
101 | "encoding/pem" |
102 | + "errors" |
103 | "fmt" |
104 | "io/ioutil" |
105 | + "os" |
106 | + "strings" |
107 | + |
108 | "launchpad.net/go-dbus/v1" |
109 | + |
110 | "launchpad.net/ubuntu-push/bus" |
111 | "launchpad.net/ubuntu-push/bus/connectivity" |
112 | "launchpad.net/ubuntu-push/bus/networkmanager" |
113 | @@ -34,7 +39,6 @@ |
114 | "launchpad.net/ubuntu-push/logger" |
115 | "launchpad.net/ubuntu-push/util" |
116 | "launchpad.net/ubuntu-push/whoopsie/identifier" |
117 | - "os" |
118 | ) |
119 | |
120 | // ClientConfig holds the client configuration |
121 | @@ -42,8 +46,13 @@ |
122 | connectivity.ConnectivityConfig // q.v. |
123 | // A reasonably large timeout for receive/answer pairs |
124 | ExchangeTimeout config.ConfigTimeDuration `json:"exchange_timeout"` |
125 | - // The server to connect to |
126 | - Addr config.ConfigHostPort |
127 | + // A timeout to use when trying to connect to the server |
128 | + ConnectTimeout config.ConfigTimeDuration `json:"connect_timeout"` |
129 | + // The server to connect to or url to query for hosts to connect to |
130 | + Addr string |
131 | + // Host list management |
132 | + HostsCachingExpiryTime config.ConfigTimeDuration `json:"hosts_cache_expiry"` // potentially refresh host list after |
133 | + ExpectAllRepairedTime config.ConfigTimeDuration `json:"expect_all_repaired"` // worth retrying all servers after |
134 | // The PEM-encoded server certificate |
135 | CertPEMFile string `json:"cert_pem_file"` |
136 | // The logging level (one of "debug", "info", "error") |
137 | @@ -89,6 +98,12 @@ |
138 | if err != nil { |
139 | return fmt.Errorf("reading config: %v", err) |
140 | } |
141 | + // ignore spaces |
142 | + client.config.Addr = strings.Replace(client.config.Addr, " ", "", -1) |
143 | + if client.config.Addr == "" { |
144 | + return errors.New("no hosts specified") |
145 | + } |
146 | + |
147 | // later, we'll be specifying more logging options in the config file |
148 | client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel) |
149 | |
150 | @@ -116,6 +131,17 @@ |
151 | return nil |
152 | } |
153 | |
154 | +// deriveSessionConfig dervies the session configuration from the client configuration bits. |
155 | +func (client *PushClient) deriveSessionConfig() session.ClientSessionConfig { |
156 | + return session.ClientSessionConfig{ |
157 | + ConnectTimeout: client.config.ConnectTimeout.TimeDuration(), |
158 | + ExchangeTimeout: client.config.ExchangeTimeout.TimeDuration(), |
159 | + HostsCachingExpiryTime: client.config.HostsCachingExpiryTime.TimeDuration(), |
160 | + ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(), |
161 | + PEM: client.pem, |
162 | + } |
163 | +} |
164 | + |
165 | // getDeviceId gets the whoopsie identifier for the device |
166 | func (client *PushClient) getDeviceId() error { |
167 | err := client.idder.Generate() |
168 | @@ -143,8 +169,8 @@ |
169 | |
170 | // initSession creates the session object |
171 | func (client *PushClient) initSession() error { |
172 | - sess, err := session.NewSession(string(client.config.Addr), client.pem, |
173 | - client.config.ExchangeTimeout.Duration, client.deviceId, |
174 | + sess, err := session.NewSession(client.config.Addr, |
175 | + client.deriveSessionConfig(), client.deviceId, |
176 | client.levelMapFactory, client.log) |
177 | if err != nil { |
178 | return err |
179 | |
180 | === modified file 'client/client_test.go' |
181 | --- client/client_test.go 2014-03-26 16:26:36 +0000 |
182 | +++ client/client_test.go 2014-03-31 16:43:44 +0000 |
183 | @@ -17,10 +17,19 @@ |
184 | package client |
185 | |
186 | import ( |
187 | + "encoding/json" |
188 | "errors" |
189 | "fmt" |
190 | "io/ioutil" |
191 | + "net/http" |
192 | + "net/http/httptest" |
193 | + "path/filepath" |
194 | + "reflect" |
195 | + "testing" |
196 | + "time" |
197 | + |
198 | . "launchpad.net/gocheck" |
199 | + |
200 | "launchpad.net/ubuntu-push/bus" |
201 | "launchpad.net/ubuntu-push/bus/networkmanager" |
202 | "launchpad.net/ubuntu-push/bus/notifications" |
203 | @@ -32,11 +41,6 @@ |
204 | "launchpad.net/ubuntu-push/util" |
205 | "launchpad.net/ubuntu-push/whoopsie/identifier" |
206 | idtesting "launchpad.net/ubuntu-push/whoopsie/identifier/testing" |
207 | - "net/http" |
208 | - "net/http/httptest" |
209 | - "path/filepath" |
210 | - "testing" |
211 | - "time" |
212 | ) |
213 | |
214 | func TestClient(t *testing.T) { TestingT(t) } |
215 | @@ -83,22 +87,37 @@ |
216 | cs.timeouts = nil |
217 | } |
218 | |
219 | +func (cs *clientSuite) writeTestConfig(overrides map[string]interface{}) { |
220 | + pem_file := helpers.SourceRelative("../server/acceptance/ssl/testing.cert") |
221 | + cfgMap := map[string]interface{}{ |
222 | + "connect_timeout": "7ms", |
223 | + "exchange_timeout": "10ms", |
224 | + "hosts_cache_expiry": "1h", |
225 | + "expect_all_repaired": "30m", |
226 | + "stabilizing_timeout": "0ms", |
227 | + "connectivity_check_url": "", |
228 | + "connectivity_check_md5": "", |
229 | + "addr": ":0", |
230 | + "cert_pem_file": pem_file, |
231 | + "recheck_timeout": "3h", |
232 | + "log_level": "debug", |
233 | + } |
234 | + for k, v := range overrides { |
235 | + cfgMap[k] = v |
236 | + } |
237 | + cfgBlob, err := json.Marshal(cfgMap) |
238 | + if err != nil { |
239 | + panic(err) |
240 | + } |
241 | + ioutil.WriteFile(cs.configPath, cfgBlob, 0600) |
242 | +} |
243 | + |
244 | func (cs *clientSuite) SetUpTest(c *C) { |
245 | cs.log = helpers.NewTestLogger(c, "debug") |
246 | dir := c.MkDir() |
247 | cs.configPath = filepath.Join(dir, "config") |
248 | - cfg := fmt.Sprintf(` |
249 | -{ |
250 | - "exchange_timeout": "10ms", |
251 | - "stabilizing_timeout": "0ms", |
252 | - "connectivity_check_url": "", |
253 | - "connectivity_check_md5": "", |
254 | - "addr": ":0", |
255 | - "cert_pem_file": %#v, |
256 | - "recheck_timeout": "3h", |
257 | - "log_level": "debug" |
258 | -}`, helpers.SourceRelative("../server/acceptance/config/testing.cert")) |
259 | - ioutil.WriteFile(cs.configPath, []byte(cfg), 0600) |
260 | + |
261 | + cs.writeTestConfig(nil) |
262 | } |
263 | |
264 | type sqlientSuite struct{ clientSuite } |
265 | @@ -119,7 +138,7 @@ |
266 | err := cli.configure() |
267 | c.Assert(err, IsNil) |
268 | c.Assert(cli.config, NotNil) |
269 | - c.Check(cli.config.ExchangeTimeout.Duration, Equals, time.Duration(10*time.Millisecond)) |
270 | + c.Check(cli.config.ExchangeTimeout.TimeDuration(), Equals, time.Duration(10*time.Millisecond)) |
271 | } |
272 | |
273 | func (cs *clientSuite) TestConfigureSetsUpLog(c *C) { |
274 | @@ -179,41 +198,70 @@ |
275 | } |
276 | |
277 | func (cs *clientSuite) TestConfigureBailsOnBadPEMFilename(c *C) { |
278 | - ioutil.WriteFile(cs.configPath, []byte(` |
279 | -{ |
280 | - "exchange_timeout": "10ms", |
281 | - "stabilizing_timeout": "0ms", |
282 | - "connectivity_check_url": "", |
283 | - "connectivity_check_md5": "", |
284 | - "addr": ":0", |
285 | - "cert_pem_file": "/a/b/c", |
286 | - "log_level": "debug", |
287 | - "recheck_timeout": "3h" |
288 | -}`), 0600) |
289 | - |
290 | + cs.writeTestConfig(map[string]interface{}{ |
291 | + "cert_pem_file": "/a/b/c", |
292 | + }) |
293 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
294 | err := cli.configure() |
295 | c.Assert(err, ErrorMatches, "reading PEM file: .*") |
296 | } |
297 | |
298 | func (cs *clientSuite) TestConfigureBailsOnBadPEM(c *C) { |
299 | - ioutil.WriteFile(cs.configPath, []byte(` |
300 | -{ |
301 | - "exchange_timeout": "10ms", |
302 | - "stabilizing_timeout": "0ms", |
303 | - "connectivity_check_url": "", |
304 | - "connectivity_check_md5": "", |
305 | - "addr": ":0", |
306 | - "cert_pem_file": "/etc/passwd", |
307 | - "log_level": "debug", |
308 | - "recheck_timeout": "3h" |
309 | -}`), 0600) |
310 | - |
311 | + cs.writeTestConfig(map[string]interface{}{ |
312 | + "cert_pem_file": "/etc/passwd", |
313 | + }) |
314 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
315 | err := cli.configure() |
316 | c.Assert(err, ErrorMatches, "no PEM found.*") |
317 | } |
318 | |
319 | +func (cs *clientSuite) TestConfigureBailsOnNoHosts(c *C) { |
320 | + cs.writeTestConfig(map[string]interface{}{ |
321 | + "addr": " ", |
322 | + }) |
323 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
324 | + err := cli.configure() |
325 | + c.Assert(err, ErrorMatches, "no hosts specified") |
326 | +} |
327 | + |
328 | +func (cs *clientSuite) TestConfigureRemovesBlanksInAddr(c *C) { |
329 | + cs.writeTestConfig(map[string]interface{}{ |
330 | + "addr": " foo: 443", |
331 | + }) |
332 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
333 | + err := cli.configure() |
334 | + c.Assert(err, IsNil) |
335 | + c.Check(cli.config.Addr, Equals, "foo:443") |
336 | +} |
337 | + |
338 | +/***************************************************************** |
339 | + deriveSessionConfig tests |
340 | +******************************************************************/ |
341 | + |
342 | +func (cs *clientSuite) TestDeriveSessionConfig(c *C) { |
343 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
344 | + err := cli.configure() |
345 | + c.Assert(err, IsNil) |
346 | + expected := session.ClientSessionConfig{ |
347 | + ConnectTimeout: 7 * time.Millisecond, |
348 | + ExchangeTimeout: 10 * time.Millisecond, |
349 | + HostsCachingExpiryTime: 1 * time.Hour, |
350 | + ExpectAllRepairedTime: 30 * time.Minute, |
351 | + PEM: cli.pem, |
352 | + } |
353 | + // sanity check that we are looking at all fields |
354 | + vExpected := reflect.ValueOf(expected) |
355 | + nf := vExpected.NumField() |
356 | + for i := 0; i < nf; i++ { |
357 | + fv := vExpected.Field(i) |
358 | + // field isn't empty/zero |
359 | + c.Assert(fv.Interface(), Not(DeepEquals), reflect.Zero(fv.Type()).Interface(), Commentf("forgot about: %s", vExpected.Type().Field(i).Name)) |
360 | + } |
361 | + // finally compare |
362 | + conf := cli.deriveSessionConfig() |
363 | + c.Check(conf, DeepEquals, expected) |
364 | +} |
365 | + |
366 | /***************************************************************** |
367 | getDeviceId tests |
368 | ******************************************************************/ |
369 | @@ -308,6 +356,7 @@ |
370 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
371 | cli.log = cs.log |
372 | c.Assert(cli.initSession(), IsNil) |
373 | + cs.log.ResetCapture() |
374 | cli.hasConnectivity = true |
375 | cli.handleErr(errors.New("bananas")) |
376 | c.Check(cs.log.Captured(), Matches, ".*session exited.*bananas\n") |
377 | @@ -363,7 +412,7 @@ |
378 | func (cs *clientSuite) TestHandleConnStateC2D(c *C) { |
379 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
380 | cli.log = cs.log |
381 | - cli.session, _ = session.NewSession(string(cli.config.Addr), cli.pem, cli.config.ExchangeTimeout.Duration, cli.deviceId, levelmap.NewLevelMap, cs.log) |
382 | + cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(), cli.deviceId, levelmap.NewLevelMap, cs.log) |
383 | cli.session.Dial() |
384 | cli.hasConnectivity = true |
385 | |
386 | @@ -376,7 +425,7 @@ |
387 | func (cs *clientSuite) TestHandleConnStateC2DPending(c *C) { |
388 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
389 | cli.log = cs.log |
390 | - cli.session, _ = session.NewSession(string(cli.config.Addr), cli.pem, cli.config.ExchangeTimeout.Duration, cli.deviceId, levelmap.NewLevelMap, cs.log) |
391 | + cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(), cli.deviceId, levelmap.NewLevelMap, cs.log) |
392 | cli.hasConnectivity = true |
393 | |
394 | cli.handleConnState(false) |
395 | |
396 | === modified file 'client/gethosts/gethost_test.go' |
397 | --- client/gethosts/gethost_test.go 2014-03-24 15:32:29 +0000 |
398 | +++ client/gethosts/gethost_test.go 2014-03-31 16:43:44 +0000 |
399 | @@ -61,18 +61,16 @@ |
400 | } |
401 | |
402 | func (s *getHostsSuite) TestGetTimeout(c *C) { |
403 | - finish := make(chan bool, 1) |
404 | + started := make(chan bool, 1) |
405 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
406 | - <-finish |
407 | + started <- true |
408 | + time.Sleep(700 * time.Millisecond) |
409 | })) |
410 | defer func() { |
411 | - time.Sleep(100 * time.Millisecond) // work around -race issue |
412 | + <-started |
413 | ts.Close() |
414 | }() |
415 | - defer func() { |
416 | - finish <- true |
417 | - }() |
418 | - gh := New("foobar", ts.URL, 1*time.Second) |
419 | + gh := New("foobar", ts.URL, 500*time.Millisecond) |
420 | _, err := gh.Get() |
421 | c.Check(err, ErrorMatches, ".*closed.*") |
422 | } |
423 | |
424 | === modified file 'client/session/session.go' |
425 | --- client/session/session.go 2014-03-26 16:26:36 +0000 |
426 | +++ client/session/session.go 2014-03-31 16:43:44 +0000 |
427 | @@ -23,14 +23,18 @@ |
428 | "crypto/x509" |
429 | "errors" |
430 | "fmt" |
431 | + "math/rand" |
432 | + "net" |
433 | + "strings" |
434 | + "sync" |
435 | + "sync/atomic" |
436 | + "time" |
437 | + |
438 | + "launchpad.net/ubuntu-push/client/gethosts" |
439 | "launchpad.net/ubuntu-push/client/session/levelmap" |
440 | "launchpad.net/ubuntu-push/logger" |
441 | "launchpad.net/ubuntu-push/protocol" |
442 | "launchpad.net/ubuntu-push/util" |
443 | - "math/rand" |
444 | - "net" |
445 | - "sync/atomic" |
446 | - "time" |
447 | ) |
448 | |
449 | var wireVersionBytes = []byte{protocol.ProtocolWireVersion} |
450 | @@ -45,6 +49,15 @@ |
451 | protocol.NotificationsMsg |
452 | } |
453 | |
454 | +// parseServerAddrSpec recognizes whether spec is a HTTP URL to get |
455 | +// hosts from or a |-separated list of host:port pairs. |
456 | +func parseServerAddrSpec(spec string) (hostsEndpoint string, fallbackHosts []string) { |
457 | + if strings.HasPrefix(spec, "http") { |
458 | + return spec, nil |
459 | + } |
460 | + return "", strings.Split(spec, "|") |
461 | +} |
462 | + |
463 | // ClientSessionState is a way to broadly track the progress of the session |
464 | type ClientSessionState uint32 |
465 | |
466 | @@ -56,15 +69,38 @@ |
467 | Running |
468 | ) |
469 | |
470 | -// ClienSession holds a client<->server session and its configuration. |
471 | +type hostGetter interface { |
472 | + Get() ([]string, error) |
473 | +} |
474 | + |
475 | +// ClientSessionConfig groups the client session configuration. |
476 | +type ClientSessionConfig struct { |
477 | + ConnectTimeout time.Duration |
478 | + ExchangeTimeout time.Duration |
479 | + HostsCachingExpiryTime time.Duration |
480 | + ExpectAllRepairedTime time.Duration |
481 | + PEM []byte |
482 | +} |
483 | + |
484 | +// ClientSession holds a client<->server session and its configuration. |
485 | type ClientSession struct { |
486 | // configuration |
487 | - DeviceId string |
488 | - ServerAddr string |
489 | - ExchangeTimeout time.Duration |
490 | - Levels levelmap.LevelMap |
491 | - Protocolator func(net.Conn) protocol.Protocol |
492 | + DeviceId string |
493 | + ClientSessionConfig |
494 | + Levels levelmap.LevelMap |
495 | + Protocolator func(net.Conn) protocol.Protocol |
496 | + // hosts |
497 | + getHost hostGetter |
498 | + fallbackHosts []string |
499 | + deliveryHostsTimestamp time.Time |
500 | + deliveryHosts []string |
501 | + lastAttemptTimestamp time.Time |
502 | + leftToTry int |
503 | + tryHost int |
504 | + // hook for testing |
505 | + timeSince func(time.Time) time.Duration |
506 | // connection |
507 | + connLock sync.RWMutex |
508 | Connection net.Conn |
509 | Log logger.Logger |
510 | TLS *tls.Config |
511 | @@ -77,7 +113,7 @@ |
512 | MsgCh chan *Notification |
513 | } |
514 | |
515 | -func NewSession(serverAddr string, pem []byte, exchangeTimeout time.Duration, |
516 | +func NewSession(serverAddrSpec string, conf ClientSessionConfig, |
517 | deviceId string, levelmapFactory func() (levelmap.LevelMap, error), |
518 | log logger.Logger) (*ClientSession, error) { |
519 | state := uint32(Disconnected) |
520 | @@ -85,19 +121,27 @@ |
521 | if err != nil { |
522 | return nil, err |
523 | } |
524 | + var getHost hostGetter |
525 | + log.Infof("using addr: %v", serverAddrSpec) |
526 | + hostsEndpoint, fallbackHosts := parseServerAddrSpec(serverAddrSpec) |
527 | + if hostsEndpoint != "" { |
528 | + getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout) |
529 | + } |
530 | sess := &ClientSession{ |
531 | - ExchangeTimeout: exchangeTimeout, |
532 | - ServerAddr: serverAddr, |
533 | - DeviceId: deviceId, |
534 | - Log: log, |
535 | - Protocolator: protocol.NewProtocol0, |
536 | - Levels: levels, |
537 | - TLS: &tls.Config{InsecureSkipVerify: true}, // XXX |
538 | - stateP: &state, |
539 | + ClientSessionConfig: conf, |
540 | + getHost: getHost, |
541 | + fallbackHosts: fallbackHosts, |
542 | + DeviceId: deviceId, |
543 | + Log: log, |
544 | + Protocolator: protocol.NewProtocol0, |
545 | + Levels: levels, |
546 | + TLS: &tls.Config{InsecureSkipVerify: true}, // XXX |
547 | + stateP: &state, |
548 | + timeSince: time.Since, |
549 | } |
550 | - if pem != nil { |
551 | + if sess.PEM != nil { |
552 | cp := x509.NewCertPool() |
553 | - ok := cp.AppendCertsFromPEM(pem) |
554 | + ok := cp.AppendCertsFromPEM(sess.PEM) |
555 | if !ok { |
556 | return nil, errors.New("could not parse certificate") |
557 | } |
558 | @@ -114,15 +158,90 @@ |
559 | atomic.StoreUint32(sess.stateP, uint32(state)) |
560 | } |
561 | |
562 | +func (sess *ClientSession) setConnection(conn net.Conn) { |
563 | + sess.connLock.Lock() |
564 | + defer sess.connLock.Unlock() |
565 | + sess.Connection = conn |
566 | +} |
567 | + |
568 | +func (sess *ClientSession) getConnection() net.Conn { |
569 | + sess.connLock.RLock() |
570 | + defer sess.connLock.RUnlock() |
571 | + return sess.Connection |
572 | +} |
573 | + |
574 | +// getHosts sets deliveryHosts possibly querying a remote endpoint |
575 | +func (sess *ClientSession) getHosts() error { |
576 | + if sess.getHost != nil { |
577 | + if sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime { |
578 | + return nil |
579 | + } |
580 | + hosts, err := sess.getHost.Get() |
581 | + if err != nil { |
582 | + sess.Log.Errorf("getHosts: %v", err) |
583 | + sess.setState(Error) |
584 | + return err |
585 | + } |
586 | + sess.deliveryHostsTimestamp = time.Now() |
587 | + sess.deliveryHosts = hosts |
588 | + } else { |
589 | + sess.deliveryHosts = sess.fallbackHosts |
590 | + } |
591 | + return nil |
592 | +} |
593 | + |
594 | +// startConnectionAttempt/nextHostToTry help connect iterating over candidate hosts |
595 | + |
596 | +func (sess *ClientSession) startConnectionAttempt() { |
597 | + if sess.timeSince(sess.lastAttemptTimestamp) > sess.ExpectAllRepairedTime { |
598 | + sess.tryHost = 0 |
599 | + } |
600 | + sess.leftToTry = len(sess.deliveryHosts) |
601 | + if sess.leftToTry == 0 { |
602 | + panic("should have got hosts from config or remote at this point") |
603 | + } |
604 | + sess.lastAttemptTimestamp = time.Now() |
605 | +} |
606 | + |
607 | +func (sess *ClientSession) nextHostToTry() string { |
608 | + if sess.leftToTry == 0 { |
609 | + return "" |
610 | + } |
611 | + res := sess.deliveryHosts[sess.tryHost] |
612 | + sess.tryHost = (sess.tryHost + 1) % len(sess.deliveryHosts) |
613 | + sess.leftToTry-- |
614 | + return res |
615 | +} |
616 | + |
617 | +// we reached the Started state, we can retry with the same host if we |
618 | +// have to retry again |
619 | +func (sess *ClientSession) started() { |
620 | + sess.tryHost-- |
621 | + if sess.tryHost == -1 { |
622 | + sess.tryHost = len(sess.deliveryHosts) - 1 |
623 | + } |
624 | + sess.setState(Started) |
625 | +} |
626 | + |
627 | // connect to a server using the configuration in the ClientSession |
628 | // and set up the connection. |
629 | func (sess *ClientSession) connect() error { |
630 | - conn, err := net.DialTimeout("tcp", sess.ServerAddr, sess.ExchangeTimeout) |
631 | - if err != nil { |
632 | - sess.setState(Error) |
633 | - return fmt.Errorf("connect: %s", err) |
634 | + sess.startConnectionAttempt() |
635 | + var err error |
636 | + var conn net.Conn |
637 | + for { |
638 | + host := sess.nextHostToTry() |
639 | + if host == "" { |
640 | + sess.setState(Error) |
641 | + return fmt.Errorf("connect: %s", err) |
642 | + } |
643 | + sess.Log.Debugf("trying to connect to: %v", host) |
644 | + conn, err = net.DialTimeout("tcp", host, sess.ConnectTimeout) |
645 | + if err == nil { |
646 | + break |
647 | + } |
648 | } |
649 | - sess.Connection = tls.Client(conn, sess.TLS) |
650 | + sess.setConnection(tls.Client(conn, sess.TLS)) |
651 | sess.setState(Connected) |
652 | return nil |
653 | } |
654 | @@ -145,6 +264,8 @@ |
655 | sess.doClose() |
656 | } |
657 | func (sess *ClientSession) doClose() { |
658 | + sess.connLock.Lock() |
659 | + defer sess.connLock.Unlock() |
660 | if sess.Connection != nil { |
661 | sess.Connection.Close() |
662 | // we ignore Close errors, on purpose (the thinking being that |
663 | @@ -224,7 +345,7 @@ |
664 | |
665 | // Call this when you've connected and want to start looping. |
666 | func (sess *ClientSession) start() error { |
667 | - conn := sess.Connection |
668 | + conn := sess.getConnection() |
669 | err := conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout)) |
670 | if err != nil { |
671 | sess.setState(Error) |
672 | @@ -279,15 +400,19 @@ |
673 | sess.proto = proto |
674 | sess.pingInterval = pingInterval |
675 | sess.Log.Debugf("Connected %v.", conn.LocalAddr()) |
676 | - sess.setState(Started) |
677 | + sess.started() // deals with choosing which host to retry with as well |
678 | return nil |
679 | } |
680 | |
681 | // run calls connect, and if it works it calls start, and if it works |
682 | // it runs loop in a goroutine, and ships its return value over ErrCh. |
683 | -func (sess *ClientSession) run(closer func(), connecter, starter, looper func() error) error { |
684 | +func (sess *ClientSession) run(closer func(), hostGetter, connecter, starter, looper func() error) error { |
685 | closer() |
686 | - err := connecter() |
687 | + err := hostGetter() |
688 | + if err != nil { |
689 | + return err |
690 | + } |
691 | + err = connecter() |
692 | if err == nil { |
693 | err = starter() |
694 | if err == nil { |
695 | @@ -317,7 +442,7 @@ |
696 | // keep on trying. |
697 | panic("can't Dial() without a protocol constructor.") |
698 | } |
699 | - return sess.run(sess.doClose, sess.connect, sess.start, sess.loop) |
700 | + return sess.run(sess.doClose, sess.getHosts, sess.connect, sess.start, sess.loop) |
701 | } |
702 | |
703 | func init() { |
704 | |
705 | === modified file 'client/session/session_test.go' |
706 | --- client/session/session_test.go 2014-03-27 13:26:10 +0000 |
707 | +++ client/session/session_test.go 2014-03-31 16:43:44 +0000 |
708 | @@ -23,16 +23,21 @@ |
709 | "fmt" |
710 | "io" |
711 | "io/ioutil" |
712 | + "net" |
713 | + "net/http" |
714 | + "net/http/httptest" |
715 | + "reflect" |
716 | + "testing" |
717 | + "time" |
718 | + |
719 | . "launchpad.net/gocheck" |
720 | + |
721 | "launchpad.net/ubuntu-push/client/session/levelmap" |
722 | + //"launchpad.net/ubuntu-push/client/gethosts" |
723 | "launchpad.net/ubuntu-push/logger" |
724 | "launchpad.net/ubuntu-push/protocol" |
725 | helpers "launchpad.net/ubuntu-push/testing" |
726 | "launchpad.net/ubuntu-push/testing/condition" |
727 | - "net" |
728 | - "reflect" |
729 | - "testing" |
730 | - "time" |
731 | ) |
732 | |
733 | func TestSession(t *testing.T) { TestingT(t) } |
734 | @@ -181,23 +186,51 @@ |
735 | } |
736 | |
737 | /**************************************************************** |
738 | + parseServerAddrSpec() tests |
739 | +****************************************************************/ |
740 | + |
741 | +func (cs *clientSessionSuite) TestParseServerAddrSpec(c *C) { |
742 | + hEp, fallbackHosts := parseServerAddrSpec("http://foo/hosts") |
743 | + c.Check(hEp, Equals, "http://foo/hosts") |
744 | + c.Check(fallbackHosts, IsNil) |
745 | + |
746 | + hEp, fallbackHosts = parseServerAddrSpec("foo:443") |
747 | + c.Check(hEp, Equals, "") |
748 | + c.Check(fallbackHosts, DeepEquals, []string{"foo:443"}) |
749 | + |
750 | + hEp, fallbackHosts = parseServerAddrSpec("foo:443|bar:443") |
751 | + c.Check(hEp, Equals, "") |
752 | + c.Check(fallbackHosts, DeepEquals, []string{"foo:443", "bar:443"}) |
753 | +} |
754 | + |
755 | +/**************************************************************** |
756 | NewSession() tests |
757 | ****************************************************************/ |
758 | |
759 | +var dummyConf = ClientSessionConfig{} |
760 | + |
761 | func (cs *clientSessionSuite) TestNewSessionPlainWorks(c *C) { |
762 | - sess, err := NewSession("", nil, 0, "", cs.lvls, cs.log) |
763 | + sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log) |
764 | c.Check(sess, NotNil) |
765 | c.Check(err, IsNil) |
766 | + c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"}) |
767 | // but no root CAs set |
768 | c.Check(sess.TLS.RootCAs, IsNil) |
769 | c.Check(sess.State(), Equals, Disconnected) |
770 | } |
771 | |
772 | -var certfile string = helpers.SourceRelative("../../server/acceptance/config/testing.cert") |
773 | +func (cs *clientSessionSuite) TestNewSessionHostEndpointWorks(c *C) { |
774 | + sess, err := NewSession("http://foo/hosts", dummyConf, "wah", cs.lvls, cs.log) |
775 | + c.Assert(err, IsNil) |
776 | + c.Check(sess.getHost, NotNil) |
777 | +} |
778 | + |
779 | +var certfile string = helpers.SourceRelative("../../server/acceptance/ssl/testing.cert") |
780 | var pem, _ = ioutil.ReadFile(certfile) |
781 | |
782 | func (cs *clientSessionSuite) TestNewSessionPEMWorks(c *C) { |
783 | - sess, err := NewSession("", pem, 0, "wah", cs.lvls, cs.log) |
784 | + conf := ClientSessionConfig{PEM: pem} |
785 | + sess, err := NewSession("", conf, "wah", cs.lvls, cs.log) |
786 | c.Check(sess, NotNil) |
787 | c.Assert(err, IsNil) |
788 | c.Check(sess.TLS.RootCAs, NotNil) |
789 | @@ -205,25 +238,172 @@ |
790 | |
791 | func (cs *clientSessionSuite) TestNewSessionBadPEMFileContentFails(c *C) { |
792 | badpem := []byte("This is not the PEM you're looking for.") |
793 | - sess, err := NewSession("", badpem, 0, "wah", cs.lvls, cs.log) |
794 | + conf := ClientSessionConfig{PEM: badpem} |
795 | + sess, err := NewSession("", conf, "wah", cs.lvls, cs.log) |
796 | c.Check(sess, IsNil) |
797 | c.Check(err, NotNil) |
798 | } |
799 | |
800 | func (cs *clientSessionSuite) TestNewSessionBadLevelMapFails(c *C) { |
801 | ferr := func() (levelmap.LevelMap, error) { return nil, errors.New("Busted.") } |
802 | - sess, err := NewSession("", nil, 0, "wah", ferr, cs.log) |
803 | + sess, err := NewSession("", dummyConf, "wah", ferr, cs.log) |
804 | c.Check(sess, IsNil) |
805 | c.Assert(err, NotNil) |
806 | } |
807 | |
808 | /**************************************************************** |
809 | + getHosts() tests |
810 | +****************************************************************/ |
811 | + |
812 | +func (cs *clientSessionSuite) TestGetHostsFallback(c *C) { |
813 | + fallback := []string{"foo:443", "bar:443"} |
814 | + sess := &ClientSession{fallbackHosts: fallback} |
815 | + err := sess.getHosts() |
816 | + c.Assert(err, IsNil) |
817 | + c.Check(sess.deliveryHosts, DeepEquals, fallback) |
818 | +} |
819 | + |
820 | +type testHostGetter struct { |
821 | + hosts []string |
822 | + err error |
823 | +} |
824 | + |
825 | +func (thg *testHostGetter) Get() ([]string, error) { |
826 | + return thg.hosts, thg.err |
827 | +} |
828 | + |
829 | +func (cs *clientSessionSuite) TestGetHostsRemote(c *C) { |
830 | + hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil} |
831 | + sess := &ClientSession{getHost: hostGetter, timeSince: time.Since} |
832 | + err := sess.getHosts() |
833 | + c.Assert(err, IsNil) |
834 | + c.Check(sess.deliveryHosts, DeepEquals, []string{"foo:443", "bar:443"}) |
835 | +} |
836 | + |
837 | +func (cs *clientSessionSuite) TestGetHostsRemoteError(c *C) { |
838 | + sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log) |
839 | + c.Assert(err, IsNil) |
840 | + hostsErr := errors.New("failed") |
841 | + hostGetter := &testHostGetter{nil, hostsErr} |
842 | + sess.getHost = hostGetter |
843 | + err = sess.getHosts() |
844 | + c.Assert(err, Equals, hostsErr) |
845 | + c.Check(sess.deliveryHosts, IsNil) |
846 | + c.Check(sess.State(), Equals, Error) |
847 | +} |
848 | + |
849 | +func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) { |
850 | + hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil} |
851 | + sess := &ClientSession{ |
852 | + getHost: hostGetter, |
853 | + ClientSessionConfig: ClientSessionConfig{ |
854 | + HostsCachingExpiryTime: 2 * time.Hour, |
855 | + }, |
856 | + timeSince: time.Since, |
857 | + } |
858 | + err := sess.getHosts() |
859 | + c.Assert(err, IsNil) |
860 | + hostGetter.hosts = []string{"baz:443"} |
861 | + // cached |
862 | + err = sess.getHosts() |
863 | + c.Assert(err, IsNil) |
864 | + c.Check(sess.deliveryHosts, DeepEquals, []string{"foo:443", "bar:443"}) |
865 | + // expired |
866 | + sess.timeSince = func(ts time.Time) time.Duration { |
867 | + return 3 * time.Hour |
868 | + } |
869 | + err = sess.getHosts() |
870 | + c.Assert(err, IsNil) |
871 | + c.Check(sess.deliveryHosts, DeepEquals, []string{"baz:443"}) |
872 | +} |
873 | + |
874 | +/**************************************************************** |
875 | + startConnectionAttempt()/nextHostToTry()/started tests |
876 | +****************************************************************/ |
877 | + |
878 | +func (cs *clientSessionSuite) TestStartConnectionAttempt(c *C) { |
879 | + since := time.Since(time.Time{}) |
880 | + sess := &ClientSession{ |
881 | + ClientSessionConfig: ClientSessionConfig{ |
882 | + ExpectAllRepairedTime: 10 * time.Second, |
883 | + }, |
884 | + timeSince: func(ts time.Time) time.Duration { |
885 | + return since |
886 | + }, |
887 | + deliveryHosts: []string{"foo:443", "bar:443"}, |
888 | + } |
889 | + // start from first host |
890 | + sess.startConnectionAttempt() |
891 | + c.Check(sess.lastAttemptTimestamp, Not(Equals), 0) |
892 | + c.Check(sess.tryHost, Equals, 0) |
893 | + c.Check(sess.leftToTry, Equals, 2) |
894 | + since = 1 * time.Second |
895 | + sess.tryHost = 1 |
896 | + // just continue |
897 | + sess.startConnectionAttempt() |
898 | + c.Check(sess.tryHost, Equals, 1) |
899 | + sess.tryHost = 2 |
900 | +} |
901 | + |
902 | +func (cs *clientSessionSuite) TestStartConnectionAttemptNoHostsPanic(c *C) { |
903 | + since := time.Since(time.Time{}) |
904 | + sess := &ClientSession{ |
905 | + ClientSessionConfig: ClientSessionConfig{ |
906 | + ExpectAllRepairedTime: 10 * time.Second, |
907 | + }, |
908 | + timeSince: func(ts time.Time) time.Duration { |
909 | + return since |
910 | + }, |
911 | + } |
912 | + c.Check(sess.startConnectionAttempt, PanicMatches, "should have got hosts from config or remote at this point") |
913 | +} |
914 | + |
915 | +func (cs *clientSessionSuite) TestNextHostToTry(c *C) { |
916 | + sess := &ClientSession{ |
917 | + deliveryHosts: []string{"foo:443", "bar:443", "baz:443"}, |
918 | + tryHost: 0, |
919 | + leftToTry: 3, |
920 | + } |
921 | + c.Check(sess.nextHostToTry(), Equals, "foo:443") |
922 | + c.Check(sess.nextHostToTry(), Equals, "bar:443") |
923 | + c.Check(sess.nextHostToTry(), Equals, "baz:443") |
924 | + c.Check(sess.nextHostToTry(), Equals, "") |
925 | + c.Check(sess.nextHostToTry(), Equals, "") |
926 | + c.Check(sess.tryHost, Equals, 0) |
927 | + |
928 | + sess.leftToTry = 3 |
929 | + sess.tryHost = 1 |
930 | + c.Check(sess.nextHostToTry(), Equals, "bar:443") |
931 | + c.Check(sess.nextHostToTry(), Equals, "baz:443") |
932 | + c.Check(sess.nextHostToTry(), Equals, "foo:443") |
933 | + c.Check(sess.nextHostToTry(), Equals, "") |
934 | + c.Check(sess.nextHostToTry(), Equals, "") |
935 | + c.Check(sess.tryHost, Equals, 1) |
936 | +} |
937 | + |
938 | +func (cs *clientSessionSuite) TestStarted(c *C) { |
939 | + sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log) |
940 | + c.Assert(err, IsNil) |
941 | + |
942 | + sess.deliveryHosts = []string{"foo:443", "bar:443", "baz:443"} |
943 | + sess.tryHost = 1 |
944 | + |
945 | + sess.started() |
946 | + c.Check(sess.tryHost, Equals, 0) |
947 | + c.Check(sess.State(), Equals, Started) |
948 | + |
949 | + sess.started() |
950 | + c.Check(sess.tryHost, Equals, 2) |
951 | +} |
952 | + |
953 | +/**************************************************************** |
954 | connect() tests |
955 | ****************************************************************/ |
956 | |
957 | func (cs *clientSessionSuite) TestConnectFailsWithNoAddress(c *C) { |
958 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
959 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
960 | c.Assert(err, IsNil) |
961 | + sess.deliveryHosts = []string{"nowhere"} |
962 | err = sess.connect() |
963 | c.Check(err, ErrorMatches, ".*connect.*address.*") |
964 | c.Check(sess.State(), Equals, Error) |
965 | @@ -233,20 +413,36 @@ |
966 | srv, err := net.Listen("tcp", "localhost:0") |
967 | c.Assert(err, IsNil) |
968 | defer srv.Close() |
969 | - sess, err := NewSession(srv.Addr().String(), nil, 0, "wah", cs.lvls, cs.log) |
970 | - c.Assert(err, IsNil) |
971 | - err = sess.connect() |
972 | - c.Check(err, IsNil) |
973 | - c.Check(sess.Connection, NotNil) |
974 | - c.Check(sess.State(), Equals, Connected) |
975 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
976 | + c.Assert(err, IsNil) |
977 | + sess.deliveryHosts = []string{srv.Addr().String()} |
978 | + err = sess.connect() |
979 | + c.Check(err, IsNil) |
980 | + c.Check(sess.Connection, NotNil) |
981 | + c.Check(sess.State(), Equals, Connected) |
982 | +} |
983 | + |
984 | +func (cs *clientSessionSuite) TestConnectSecondConnects(c *C) { |
985 | + srv, err := net.Listen("tcp", "localhost:0") |
986 | + c.Assert(err, IsNil) |
987 | + defer srv.Close() |
988 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
989 | + c.Assert(err, IsNil) |
990 | + sess.deliveryHosts = []string{"nowhere", srv.Addr().String()} |
991 | + err = sess.connect() |
992 | + c.Check(err, IsNil) |
993 | + c.Check(sess.Connection, NotNil) |
994 | + c.Check(sess.State(), Equals, Connected) |
995 | + c.Check(sess.tryHost, Equals, 0) |
996 | } |
997 | |
998 | func (cs *clientSessionSuite) TestConnectConnectFail(c *C) { |
999 | srv, err := net.Listen("tcp", "localhost:0") |
1000 | c.Assert(err, IsNil) |
1001 | - sess, err := NewSession(srv.Addr().String(), nil, 0, "wah", cs.lvls, cs.log) |
1002 | + sess, err := NewSession(srv.Addr().String(), dummyConf, "wah", cs.lvls, cs.log) |
1003 | srv.Close() |
1004 | c.Assert(err, IsNil) |
1005 | + sess.deliveryHosts = []string{srv.Addr().String()} |
1006 | err = sess.connect() |
1007 | c.Check(err, ErrorMatches, ".*connection refused") |
1008 | c.Check(sess.State(), Equals, Error) |
1009 | @@ -257,7 +453,7 @@ |
1010 | ****************************************************************/ |
1011 | |
1012 | func (cs *clientSessionSuite) TestClose(c *C) { |
1013 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1014 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1015 | c.Assert(err, IsNil) |
1016 | sess.Connection = &testConn{Name: "TestClose"} |
1017 | sess.Close() |
1018 | @@ -266,7 +462,7 @@ |
1019 | } |
1020 | |
1021 | func (cs *clientSessionSuite) TestCloseTwice(c *C) { |
1022 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1023 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1024 | c.Assert(err, IsNil) |
1025 | sess.Connection = &testConn{Name: "TestCloseTwice"} |
1026 | sess.Close() |
1027 | @@ -277,7 +473,7 @@ |
1028 | } |
1029 | |
1030 | func (cs *clientSessionSuite) TestCloseFails(c *C) { |
1031 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1032 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1033 | c.Assert(err, IsNil) |
1034 | sess.Connection = &testConn{Name: "TestCloseFails", CloseCondition: condition.Work(false)} |
1035 | sess.Close() |
1036 | @@ -291,7 +487,7 @@ |
1037 | func (d *derp) Stop() { d.stopped = true } |
1038 | |
1039 | func (cs *clientSessionSuite) TestCloseStopsRetrier(c *C) { |
1040 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1041 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1042 | c.Assert(err, IsNil) |
1043 | ar := new(derp) |
1044 | sess.retrier = ar |
1045 | @@ -308,7 +504,7 @@ |
1046 | |
1047 | func (cs *clientSessionSuite) TestAutoRedialWorks(c *C) { |
1048 | // checks that AutoRedial sets up a retrier and tries redialing it |
1049 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1050 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1051 | c.Assert(err, IsNil) |
1052 | ar := new(derp) |
1053 | sess.retrier = ar |
1054 | @@ -319,7 +515,7 @@ |
1055 | |
1056 | func (cs *clientSessionSuite) TestAutoRedialStopsRetrier(c *C) { |
1057 | // checks that AutoRedial stops the previous retrier |
1058 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1059 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1060 | c.Assert(err, IsNil) |
1061 | ch := make(chan uint32) |
1062 | c.Check(sess.retrier, IsNil) |
1063 | @@ -344,7 +540,10 @@ |
1064 | |
1065 | func (s *msgSuite) SetUpTest(c *C) { |
1066 | var err error |
1067 | - s.sess, err = NewSession("", nil, time.Millisecond, "wah", levelmap.NewLevelMap, helpers.NewTestLogger(c, "debug")) |
1068 | + conf := ClientSessionConfig{ |
1069 | + ExchangeTimeout: time.Millisecond, |
1070 | + } |
1071 | + s.sess, err = NewSession("", conf, "wah", levelmap.NewLevelMap, helpers.NewTestLogger(c, "debug")) |
1072 | c.Assert(err, IsNil) |
1073 | s.sess.Connection = &testConn{Name: "TestHandle*"} |
1074 | s.errCh = make(chan error, 1) |
1075 | @@ -519,7 +718,7 @@ |
1076 | start() tests |
1077 | ****************************************************************/ |
1078 | func (cs *clientSessionSuite) TestStartFailsIfSetDeadlineFails(c *C) { |
1079 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1080 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1081 | c.Assert(err, IsNil) |
1082 | sess.Connection = &testConn{Name: "TestStartFailsIfSetDeadlineFails", |
1083 | DeadlineCondition: condition.Work(false)} // setdeadline will fail |
1084 | @@ -529,7 +728,7 @@ |
1085 | } |
1086 | |
1087 | func (cs *clientSessionSuite) TestStartFailsIfWriteFails(c *C) { |
1088 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1089 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1090 | c.Assert(err, IsNil) |
1091 | sess.Connection = &testConn{Name: "TestStartFailsIfWriteFails", |
1092 | WriteCondition: condition.Work(false)} // write will fail |
1093 | @@ -539,7 +738,7 @@ |
1094 | } |
1095 | |
1096 | func (cs *clientSessionSuite) TestStartFailsIfGetLevelsFails(c *C) { |
1097 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1098 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1099 | c.Assert(err, IsNil) |
1100 | sess.Levels = &brokenLevelMap{} |
1101 | sess.Connection = &testConn{Name: "TestStartConnectMessageFails"} |
1102 | @@ -559,7 +758,7 @@ |
1103 | } |
1104 | |
1105 | func (cs *clientSessionSuite) TestStartConnectMessageFails(c *C) { |
1106 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1107 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1108 | c.Assert(err, IsNil) |
1109 | sess.Connection = &testConn{Name: "TestStartConnectMessageFails"} |
1110 | errCh := make(chan error, 1) |
1111 | @@ -585,7 +784,7 @@ |
1112 | } |
1113 | |
1114 | func (cs *clientSessionSuite) TestStartConnackReadError(c *C) { |
1115 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1116 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1117 | c.Assert(err, IsNil) |
1118 | sess.Connection = &testConn{Name: "TestStartConnackReadError"} |
1119 | errCh := make(chan error, 1) |
1120 | @@ -609,7 +808,7 @@ |
1121 | } |
1122 | |
1123 | func (cs *clientSessionSuite) TestStartBadConnack(c *C) { |
1124 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1125 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1126 | c.Assert(err, IsNil) |
1127 | sess.Connection = &testConn{Name: "TestStartBadConnack"} |
1128 | errCh := make(chan error, 1) |
1129 | @@ -633,7 +832,7 @@ |
1130 | } |
1131 | |
1132 | func (cs *clientSessionSuite) TestStartNotConnack(c *C) { |
1133 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1134 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1135 | c.Assert(err, IsNil) |
1136 | sess.Connection = &testConn{Name: "TestStartBadConnack"} |
1137 | errCh := make(chan error, 1) |
1138 | @@ -657,7 +856,7 @@ |
1139 | } |
1140 | |
1141 | func (cs *clientSessionSuite) TestStartWorks(c *C) { |
1142 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1143 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1144 | c.Assert(err, IsNil) |
1145 | sess.Connection = &testConn{Name: "TestStartWorks"} |
1146 | errCh := make(chan error, 1) |
1147 | @@ -688,34 +887,49 @@ |
1148 | run() tests |
1149 | ****************************************************************/ |
1150 | |
1151 | -func (cs *clientSessionSuite) TestRunBailsIfConnectFails(c *C) { |
1152 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1153 | +func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) { |
1154 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1155 | c.Assert(err, IsNil) |
1156 | - failure := errors.New("TestRunBailsIfConnectFails") |
1157 | + failure := errors.New("TestRunBailsIfHostGetterFails") |
1158 | has_closed := false |
1159 | err = sess.run( |
1160 | func() { has_closed = true }, |
1161 | func() error { return failure }, |
1162 | nil, |
1163 | + nil, |
1164 | nil) |
1165 | c.Check(err, Equals, failure) |
1166 | c.Check(has_closed, Equals, true) |
1167 | } |
1168 | |
1169 | +func (cs *clientSessionSuite) TestRunBailsIfConnectFails(c *C) { |
1170 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1171 | + c.Assert(err, IsNil) |
1172 | + failure := errors.New("TestRunBailsIfConnectFails") |
1173 | + err = sess.run( |
1174 | + func() {}, |
1175 | + func() error { return nil }, |
1176 | + func() error { return failure }, |
1177 | + nil, |
1178 | + nil) |
1179 | + c.Check(err, Equals, failure) |
1180 | +} |
1181 | + |
1182 | func (cs *clientSessionSuite) TestRunBailsIfStartFails(c *C) { |
1183 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1184 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1185 | c.Assert(err, IsNil) |
1186 | failure := errors.New("TestRunBailsIfStartFails") |
1187 | err = sess.run( |
1188 | func() {}, |
1189 | func() error { return nil }, |
1190 | + func() error { return nil }, |
1191 | func() error { return failure }, |
1192 | nil) |
1193 | c.Check(err, Equals, failure) |
1194 | } |
1195 | |
1196 | func (cs *clientSessionSuite) TestRunRunsEvenIfLoopFails(c *C) { |
1197 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1198 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1199 | c.Assert(err, IsNil) |
1200 | // just to make a point: until here we haven't set ErrCh & MsgCh (no |
1201 | // biggie if this stops being true) |
1202 | @@ -727,6 +941,7 @@ |
1203 | func() {}, |
1204 | func() error { return nil }, |
1205 | func() error { return nil }, |
1206 | + func() error { return nil }, |
1207 | func() error { sess.MsgCh <- notf; return <-failureCh }) |
1208 | c.Check(err, Equals, nil) |
1209 | // if run doesn't error it sets up the channels |
1210 | @@ -744,7 +959,7 @@ |
1211 | ****************************************************************/ |
1212 | |
1213 | func (cs *clientSessionSuite) TestJitter(c *C) { |
1214 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1215 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1216 | c.Assert(err, IsNil) |
1217 | num_tries := 20 // should do the math |
1218 | spread := time.Second // |
1219 | @@ -776,12 +991,17 @@ |
1220 | |
1221 | func (cs *clientSessionSuite) TestDialPanics(c *C) { |
1222 | // one last unhappy test |
1223 | - sess, err := NewSession("", nil, 0, "wah", cs.lvls, cs.log) |
1224 | + sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) |
1225 | c.Assert(err, IsNil) |
1226 | sess.Protocolator = nil |
1227 | c.Check(sess.Dial, PanicMatches, ".*protocol constructor.") |
1228 | } |
1229 | |
1230 | +var ( |
1231 | + dialTestTimeout = 100 * time.Millisecond |
1232 | + dialTestConf = ClientSessionConfig{ExchangeTimeout: dialTestTimeout} |
1233 | +) |
1234 | + |
1235 | func (cs *clientSessionSuite) TestDialWorks(c *C) { |
1236 | // happy path thoughts |
1237 | cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock) |
1238 | @@ -791,10 +1011,22 @@ |
1239 | SessionTicketsDisabled: true, |
1240 | } |
1241 | |
1242 | - timeout := 100 * time.Millisecond |
1243 | lst, err := tls.Listen("tcp", "localhost:0", tlsCfg) |
1244 | c.Assert(err, IsNil) |
1245 | - sess, err := NewSession(lst.Addr().String(), nil, timeout, "wah", cs.lvls, cs.log) |
1246 | + // advertise |
1247 | + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
1248 | + b, err := json.Marshal(map[string]interface{}{ |
1249 | + "hosts": []string{"nowhere", lst.Addr().String()}, |
1250 | + }) |
1251 | + if err != nil { |
1252 | + panic(err) |
1253 | + } |
1254 | + w.Header().Set("Content-Type", "application/json") |
1255 | + w.Write(b) |
1256 | + })) |
1257 | + defer ts.Close() |
1258 | + |
1259 | + sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log) |
1260 | c.Assert(err, IsNil) |
1261 | tconn := &testConn{CloseCondition: condition.Fail2Work(10)} |
1262 | sess.Connection = tconn |
1263 | @@ -819,10 +1051,13 @@ |
1264 | c.Check(tconn.CloseCondition.String(), Matches, ".* 9 to go.") |
1265 | |
1266 | // now, start: 1. protocol version |
1267 | - v, err := protocol.ReadWireFormatVersion(srv, timeout) |
1268 | + v, err := protocol.ReadWireFormatVersion(srv, dialTestTimeout) |
1269 | c.Assert(err, IsNil) |
1270 | c.Assert(v, Equals, protocol.ProtocolWireVersion) |
1271 | |
1272 | + // if something goes wrong session would try the first/other host |
1273 | + c.Check(sess.tryHost, Equals, 0) |
1274 | + |
1275 | // 2. "connect" (but on the fake protcol above! woo) |
1276 | |
1277 | c.Check(takeNext(downCh), Equals, "deadline 100ms") |
1278 | @@ -843,6 +1078,9 @@ |
1279 | c.Check(takeNext(downCh), Equals, protocol.PingPongMsg{Type: "pong"}) |
1280 | upCh <- nil |
1281 | |
1282 | + // session would retry the same host |
1283 | + c.Check(sess.tryHost, Equals, 1) |
1284 | + |
1285 | // and broadcasts... |
1286 | b := &protocol.BroadcastMsg{ |
1287 | Type: "broadcast", |
1288 | @@ -870,3 +1108,30 @@ |
1289 | upCh <- failure |
1290 | c.Check(<-sess.ErrCh, Equals, failure) |
1291 | } |
1292 | + |
1293 | +func (cs *clientSessionSuite) TestDialWorksDirect(c *C) { |
1294 | + // happy path thoughts |
1295 | + cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock) |
1296 | + c.Assert(err, IsNil) |
1297 | + tlsCfg := &tls.Config{ |
1298 | + Certificates: []tls.Certificate{cert}, |
1299 | + SessionTicketsDisabled: true, |
1300 | + } |
1301 | + |
1302 | + lst, err := tls.Listen("tcp", "localhost:0", tlsCfg) |
1303 | + c.Assert(err, IsNil) |
1304 | + sess, err := NewSession(lst.Addr().String(), dialTestConf, "wah", cs.lvls, cs.log) |
1305 | + c.Assert(err, IsNil) |
1306 | + defer sess.Close() |
1307 | + |
1308 | + upCh := make(chan interface{}, 5) |
1309 | + downCh := make(chan interface{}, 5) |
1310 | + proto := &testProtocol{up: upCh, down: downCh} |
1311 | + sess.Protocolator = func(net.Conn) protocol.Protocol { return proto } |
1312 | + |
1313 | + go sess.Dial() |
1314 | + |
1315 | + _, err = lst.Accept() |
1316 | + c.Assert(err, IsNil) |
1317 | + // connect done |
1318 | +} |
1319 | |
1320 | === modified file 'debian/config.json' |
1321 | --- debian/config.json 2014-03-20 12:17:40 +0000 |
1322 | +++ debian/config.json 2014-03-31 16:43:44 +0000 |
1323 | @@ -1,5 +1,8 @@ |
1324 | { |
1325 | + "connect_timeout": "20s", |
1326 | "exchange_timeout": "30s", |
1327 | + "hosts_cache_expiry": "12h", |
1328 | + "expect_all_repaired": "40m", |
1329 | "addr": "push-delivery.ubuntu.com:443", |
1330 | "cert_pem_file": "", |
1331 | "stabilizing_timeout": "2s", |
1332 | |
1333 | === added directory 'sampleconfigs' |
1334 | === renamed file 'server/acceptance/config/config.json' => 'sampleconfigs/dev.json' |
1335 | --- server/acceptance/config/config.json 2014-01-17 17:20:34 +0000 |
1336 | +++ sampleconfigs/dev.json 2014-03-31 16:43:44 +0000 |
1337 | @@ -4,8 +4,8 @@ |
1338 | "broker_queue_size": 10000, |
1339 | "session_queue_size": 10, |
1340 | "addr": "127.0.0.1:9090", |
1341 | - "key_pem_file": "testing.key", |
1342 | - "cert_pem_file": "testing.cert", |
1343 | + "key_pem_file": "../server/acceptance/ssl/testing.key", |
1344 | + "cert_pem_file": "../server/acceptance/ssl/testing.cert", |
1345 | "http_addr": "127.0.0.1:8888", |
1346 | "http_read_timeout": "5s", |
1347 | "http_write_timeout": "5s" |
1348 | |
1349 | === renamed directory 'server/acceptance/config' => 'server/acceptance/ssl' |
1350 | === modified file 'server/acceptance/suites/helpers.go' |
1351 | --- server/acceptance/suites/helpers.go 2014-03-25 19:08:00 +0000 |
1352 | +++ server/acceptance/suites/helpers.go 2014-03-31 16:43:44 +0000 |
1353 | @@ -48,8 +48,8 @@ |
1354 | "session_queue_size": 10, |
1355 | "broker_queue_size": 100, |
1356 | "addr": addr, |
1357 | - "key_pem_file": helpers.SourceRelative("../config/testing.key"), |
1358 | - "cert_pem_file": helpers.SourceRelative("../config/testing.cert"), |
1359 | + "key_pem_file": helpers.SourceRelative("../ssl/testing.key"), |
1360 | + "cert_pem_file": helpers.SourceRelative("../ssl/testing.cert"), |
1361 | }) |
1362 | } |
1363 | |
1364 | |
1365 | === modified file 'server/acceptance/suites/suite.go' |
1366 | --- server/acceptance/suites/suite.go 2014-03-25 19:08:00 +0000 |
1367 | +++ server/acceptance/suites/suite.go 2014-03-31 16:43:44 +0000 |
1368 | @@ -128,9 +128,9 @@ |
1369 | } |
1370 | |
1371 | func testClientSession(addr string, deviceId string, reportPings bool) *acceptance.ClientSession { |
1372 | - certPEMBlock, err := ioutil.ReadFile(helpers.SourceRelative("../config/testing.cert")) |
1373 | + certPEMBlock, err := ioutil.ReadFile(helpers.SourceRelative("../ssl/testing.cert")) |
1374 | if err != nil { |
1375 | - panic(fmt.Sprintf("could not read config/testing.cert: %v", err)) |
1376 | + panic(fmt.Sprintf("could not read ssl/testing.cert: %v", err)) |
1377 | } |
1378 | return &acceptance.ClientSession{ |
1379 | ExchangeTimeout: 100 * time.Millisecond, |