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

Proposed by John Lenton
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
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_identifier_generate until it starts working. (LP: #1309237)
  * 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_identifier_generate until it starts working. (LP: #1309237)
  * 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)

To post a comment you must log in.
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'client/client.go'
--- client/client.go 2014-04-11 16:37:48 +0000
+++ client/client.go 2014-04-23 11:53:01 +0000
@@ -57,7 +57,7 @@
57 // The PEM-encoded server certificate57 // The PEM-encoded server certificate
58 CertPEMFile string `json:"cert_pem_file"`58 CertPEMFile string `json:"cert_pem_file"`
59 // The logging level (one of "debug", "info", "error")59 // The logging level (one of "debug", "info", "error")
60 LogLevel string `json:"log_level"`60 LogLevel logger.ConfigLogLevel `json:"log_level"`
61}61}
6262
63// PushClient is the Ubuntu Push Notifications client-side daemon.63// PushClient is the Ubuntu Push Notifications client-side daemon.
@@ -95,13 +95,13 @@
9595
96// configure loads its configuration, and sets it up.96// configure loads its configuration, and sets it up.
97func (client *PushClient) configure() error {97func (client *PushClient) configure() error {
98 f, err := os.Open(client.configPath)98 _, err := os.Stat(client.configPath)
99 if err != nil {99 if err != nil {
100 return fmt.Errorf("opening config: %v", err)100 return fmt.Errorf("config: %v", err)
101 }101 }
102 err = config.ReadConfig(f, &client.config)102 err = config.ReadFiles(&client.config, client.configPath, "<flags>")
103 if err != nil {103 if err != nil {
104 return fmt.Errorf("reading config: %v", err)104 return fmt.Errorf("config: %v", err)
105 }105 }
106 // ignore spaces106 // ignore spaces
107 client.config.Addr = strings.Replace(client.config.Addr, " ", "", -1)107 client.config.Addr = strings.Replace(client.config.Addr, " ", "", -1)
@@ -110,7 +110,7 @@
110 }110 }
111111
112 // later, we'll be specifying more logging options in the config file112 // later, we'll be specifying more logging options in the config file
113 client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel)113 client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel.Level())
114114
115 // overridden for testing115 // overridden for testing
116 client.idder = identifier.New()116 client.idder = identifier.New()
@@ -285,9 +285,6 @@
285 h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}}285 h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}}
286 nots := notifications.Raw(client.notificationsEndp, client.log)286 nots := notifications.Raw(client.notificationsEndp, client.log)
287 body := "Tap to open the system updater."287 body := "Tap to open the system updater."
288 if msg != nil {
289 body = fmt.Sprintf("[%d] %s", msg.TopLevel, body)
290 }
291 not_id, err := nots.Notify(288 not_id, err := nots.Notify(
292 "ubuntu-push-client", // app name289 "ubuntu-push-client", // app name
293 uint32(0), // id290 uint32(0), // id
294291
=== modified file 'client/client_test.go'
--- client/client_test.go 2014-04-11 16:21:45 +0000
+++ client/client_test.go 2014-04-23 11:53:01 +0000
@@ -19,10 +19,12 @@
19import (19import (
20 "encoding/json"20 "encoding/json"
21 "errors"21 "errors"
22 "flag"
22 "fmt"23 "fmt"
23 "io/ioutil"24 "io/ioutil"
24 "net/http"25 "net/http"
25 "net/http/httptest"26 "net/http/httptest"
27 "os"
26 "path/filepath"28 "path/filepath"
27 "reflect"29 "reflect"
28 "testing"30 "testing"
@@ -37,6 +39,7 @@
37 testibus "launchpad.net/ubuntu-push/bus/testing"39 testibus "launchpad.net/ubuntu-push/bus/testing"
38 "launchpad.net/ubuntu-push/client/session"40 "launchpad.net/ubuntu-push/client/session"
39 "launchpad.net/ubuntu-push/client/session/levelmap"41 "launchpad.net/ubuntu-push/client/session/levelmap"
42 "launchpad.net/ubuntu-push/config"
40 helpers "launchpad.net/ubuntu-push/testing"43 helpers "launchpad.net/ubuntu-push/testing"
41 "launchpad.net/ubuntu-push/testing/condition"44 "launchpad.net/ubuntu-push/testing/condition"
42 "launchpad.net/ubuntu-push/util"45 "launchpad.net/ubuntu-push/util"
@@ -79,6 +82,7 @@
79}82}
8083
81func (cs *clientSuite) SetUpSuite(c *C) {84func (cs *clientSuite) SetUpSuite(c *C) {
85 config.IgnoreParsedFlags = true // because configure() uses <flags>
82 cs.timeouts = util.SwapTimeouts([]time.Duration{0})86 cs.timeouts = util.SwapTimeouts([]time.Duration{0})
83 cs.leveldbPath = ""87 cs.leveldbPath = ""
84}88}
@@ -142,6 +146,16 @@
142 c.Check(cli.config.ExchangeTimeout.TimeDuration(), Equals, time.Duration(10*time.Millisecond))146 c.Check(cli.config.ExchangeTimeout.TimeDuration(), Equals, time.Duration(10*time.Millisecond))
143}147}
144148
149func (cs *clientSuite) TestConfigureWorksWithFlags(c *C) {
150 flag.CommandLine = flag.NewFlagSet("client", flag.ContinueOnError)
151 os.Args = []string{"client", "-addr", "foo:7777"}
152 cli := NewPushClient(cs.configPath, cs.leveldbPath)
153 err := cli.configure()
154 c.Assert(err, IsNil)
155 c.Assert(cli.config, NotNil)
156 c.Check(cli.config.Addr, Equals, "foo:7777")
157}
158
145func (cs *clientSuite) TestConfigureSetsUpLog(c *C) {159func (cs *clientSuite) TestConfigureSetsUpLog(c *C) {
146 cli := NewPushClient(cs.configPath, cs.leveldbPath)160 cli := NewPushClient(cs.configPath, cs.leveldbPath)
147 c.Check(cli.log, IsNil)161 c.Check(cli.log, IsNil)
@@ -163,7 +177,7 @@
163 c.Check(cli.idder, IsNil)177 c.Check(cli.idder, IsNil)
164 err := cli.configure()178 err := cli.configure()
165 c.Assert(err, IsNil)179 c.Assert(err, IsNil)
166 c.Assert(cli.idder, DeepEquals, identifier.New())180 c.Assert(cli.idder, FitsTypeOf, identifier.New())
167}181}
168182
169func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) {183func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) {
170184
=== modified file 'client/gethosts/gethost.go'
--- client/gethosts/gethost.go 2014-03-24 15:32:29 +0000
+++ client/gethosts/gethost.go 2014-04-23 11:53:01 +0000
@@ -49,8 +49,10 @@
49 }49 }
50}50}
5151
52type expected struct {52// Host contains the domain and hosts returned by the remote endpoint
53 Hosts []string53type Host struct {
54 Domain string
55 Hosts []string
54}56}
5557
56var (58var (
@@ -60,7 +62,7 @@
60)62)
6163
62// Get gets a list of hosts consulting the endpoint.64// Get gets a list of hosts consulting the endpoint.
63func (gh *GetHost) Get() ([]string, error) {65func (gh *GetHost) Get() (*Host, error) {
64 resp, err := gh.cli.Get(gh.endpointUrl + "?h=" + gh.hash)66 resp, err := gh.cli.Get(gh.endpointUrl + "?h=" + gh.hash)
65 if err != nil {67 if err != nil {
66 return nil, err68 return nil, err
@@ -80,7 +82,7 @@
80 if err != nil {82 if err != nil {
81 return nil, err83 return nil, err
82 }84 }
83 var parsed expected85 var parsed Host
84 err = json.Unmarshal(body, &parsed)86 err = json.Unmarshal(body, &parsed)
85 if err != nil {87 if err != nil {
86 return nil, ErrTemporary88 return nil, ErrTemporary
@@ -88,5 +90,5 @@
88 if len(parsed.Hosts) == 0 {90 if len(parsed.Hosts) == 0 {
89 return nil, ErrTemporary91 return nil, ErrTemporary
90 }92 }
91 return parsed.Hosts, nil93 return &parsed, nil
92}94}
9395
=== modified file 'client/gethosts/gethost_test.go'
--- client/gethosts/gethost_test.go 2014-03-31 14:31:07 +0000
+++ client/gethosts/gethost_test.go 2014-04-23 11:53:01 +0000
@@ -45,7 +45,8 @@
45 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {45 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
46 x := r.FormValue("h")46 x := r.FormValue("h")
47 b, err := json.Marshal(map[string]interface{}{47 b, err := json.Marshal(map[string]interface{}{
48 "hosts": []string{"http://" + x},48 "domain": "example.com",
49 "hosts": []string{"http://" + x},
49 })50 })
50 if err != nil {51 if err != nil {
51 panic(err)52 panic(err)
@@ -57,7 +58,8 @@
57 gh := New("foobar", ts.URL, 1*time.Second)58 gh := New("foobar", ts.URL, 1*time.Second)
58 res, err := gh.Get()59 res, err := gh.Get()
59 c.Assert(err, IsNil)60 c.Assert(err, IsNil)
60 c.Check(res, DeepEquals, []string{"http://c1130408a700afe0"})61 c.Check(*res, DeepEquals,
62 Host{Domain: "example.com", Hosts: []string{"http://c1130408a700afe0"}})
61}63}
6264
63func (s *getHostsSuite) TestGetTimeout(c *C) {65func (s *getHostsSuite) TestGetTimeout(c *C) {
@@ -97,4 +99,6 @@
9799
98 scenario(http.StatusOK, "{", ErrTemporary)100 scenario(http.StatusOK, "{", ErrTemporary)
99 scenario(http.StatusOK, "{}", ErrTemporary)101 scenario(http.StatusOK, "{}", ErrTemporary)
102 scenario(http.StatusOK, `{"domain": "example.com"}`, ErrTemporary)
103 scenario(http.StatusOK, `{"hosts": ["one"]}`, nil)
100}104}
101105
=== modified file 'client/session/session.go'
--- client/session/session.go 2014-04-04 13:55:00 +0000
+++ client/session/session.go 2014-04-23 11:53:01 +0000
@@ -73,7 +73,7 @@
73)73)
7474
75type hostGetter interface {75type hostGetter interface {
76 Get() ([]string, error)76 Get() (*gethosts.Host, error)
77}77}
7878
79// ClientSessionConfig groups the client session configuration.79// ClientSessionConfig groups the client session configuration.
@@ -115,6 +115,26 @@
115 stateP *uint32115 stateP *uint32
116 ErrCh chan error116 ErrCh chan error
117 MsgCh chan *Notification117 MsgCh chan *Notification
118 // autoredial knobs
119 shouldDelayP *uint32
120 lastAutoRedial time.Time
121 redialDelay func(*ClientSession) time.Duration
122 redialJitter func(time.Duration) time.Duration
123 redialDelays []time.Duration
124 redialDelaysIdx int
125}
126
127func redialDelay(sess *ClientSession) time.Duration {
128 if sess.ShouldDelay() {
129 t := sess.redialDelays[sess.redialDelaysIdx]
130 if len(sess.redialDelays) > sess.redialDelaysIdx+1 {
131 sess.redialDelaysIdx++
132 }
133 return t + sess.redialJitter(t)
134 } else {
135 sess.redialDelaysIdx = 0
136 return 0
137 }
118}138}
119139
120func NewSession(serverAddrSpec string, conf ClientSessionConfig,140func NewSession(serverAddrSpec string, conf ClientSessionConfig,
@@ -131,6 +151,7 @@
131 if hostsEndpoint != "" {151 if hostsEndpoint != "" {
132 getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout)152 getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout)
133 }153 }
154 var shouldDelay uint32 = 0
134 sess := &ClientSession{155 sess := &ClientSession{
135 ClientSessionConfig: conf,156 ClientSessionConfig: conf,
136 getHost: getHost,157 getHost: getHost,
@@ -139,10 +160,14 @@
139 Log: log,160 Log: log,
140 Protocolator: protocol.NewProtocol0,161 Protocolator: protocol.NewProtocol0,
141 Levels: levels,162 Levels: levels,
142 TLS: &tls.Config{InsecureSkipVerify: true}, // XXX163 TLS: &tls.Config{},
143 stateP: &state,164 stateP: &state,
144 timeSince: time.Since,165 timeSince: time.Since,
166 shouldDelayP: &shouldDelay,
167 redialDelay: redialDelay,
168 redialDelays: util.Timeouts(),
145 }169 }
170 sess.redialJitter = sess.Jitter
146 if sess.PEM != nil {171 if sess.PEM != nil {
147 cp := x509.NewCertPool()172 cp := x509.NewCertPool()
148 ok := cp.AppendCertsFromPEM(sess.PEM)173 ok := cp.AppendCertsFromPEM(sess.PEM)
@@ -154,6 +179,18 @@
154 return sess, nil179 return sess, nil
155}180}
156181
182func (sess *ClientSession) ShouldDelay() bool {
183 return atomic.LoadUint32(sess.shouldDelayP) != 0
184}
185
186func (sess *ClientSession) setShouldDelay() {
187 atomic.StoreUint32(sess.shouldDelayP, uint32(1))
188}
189
190func (sess *ClientSession) clearShouldDelay() {
191 atomic.StoreUint32(sess.shouldDelayP, uint32(0))
192}
193
157func (sess *ClientSession) State() ClientSessionState {194func (sess *ClientSession) State() ClientSessionState {
158 return ClientSessionState(atomic.LoadUint32(sess.stateP))195 return ClientSessionState(atomic.LoadUint32(sess.stateP))
159}196}
@@ -180,14 +217,17 @@
180 if sess.deliveryHosts != nil && sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime {217 if sess.deliveryHosts != nil && sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime {
181 return nil218 return nil
182 }219 }
183 hosts, err := sess.getHost.Get()220 host, err := sess.getHost.Get()
184 if err != nil {221 if err != nil {
185 sess.Log.Errorf("getHosts: %v", err)222 sess.Log.Errorf("getHosts: %v", err)
186 sess.setState(Error)223 sess.setState(Error)
187 return err224 return err
188 }225 }
189 sess.deliveryHostsTimestamp = time.Now()226 sess.deliveryHostsTimestamp = time.Now()
190 sess.deliveryHosts = hosts227 sess.deliveryHosts = host.Hosts
228 if sess.TLS != nil {
229 sess.TLS.ServerName = host.Domain
230 }
191 } else {231 } else {
192 sess.deliveryHosts = sess.fallbackHosts232 sess.deliveryHosts = sess.fallbackHosts
193 }233 }
@@ -234,6 +274,7 @@
234// connect to a server using the configuration in the ClientSession274// connect to a server using the configuration in the ClientSession
235// and set up the connection.275// and set up the connection.
236func (sess *ClientSession) connect() error {276func (sess *ClientSession) connect() error {
277 sess.setShouldDelay()
237 sess.startConnectionAttempt()278 sess.startConnectionAttempt()
238 var err error279 var err error
239 var conn net.Conn280 var conn net.Conn
@@ -263,7 +304,12 @@
263304
264func (sess *ClientSession) AutoRedial(doneCh chan uint32) {305func (sess *ClientSession) AutoRedial(doneCh chan uint32) {
265 sess.stopRedial()306 sess.stopRedial()
307 if time.Since(sess.lastAutoRedial) < 2*time.Second {
308 sess.setShouldDelay()
309 }
310 time.Sleep(sess.redialDelay(sess))
266 sess.retrier = util.NewAutoRedialer(sess)311 sess.retrier = util.NewAutoRedialer(sess)
312 sess.lastAutoRedial = time.Now()
267 go func() { doneCh <- sess.retrier.Redial() }()313 go func() { doneCh <- sess.retrier.Redial() }()
268}314}
269315
@@ -289,6 +335,7 @@
289 err := sess.proto.WriteMessage(protocol.PingPongMsg{Type: "pong"})335 err := sess.proto.WriteMessage(protocol.PingPongMsg{Type: "pong"})
290 if err == nil {336 if err == nil {
291 sess.Log.Debugf("ping.")337 sess.Log.Debugf("ping.")
338 sess.clearShouldDelay()
292 } else {339 } else {
293 sess.setState(Error)340 sess.setState(Error)
294 sess.Log.Errorf("unable to pong: %s", err)341 sess.Log.Errorf("unable to pong: %s", err)
@@ -330,6 +377,7 @@
330 sess.Log.Errorf("unable to ack broadcast: %s", err)377 sess.Log.Errorf("unable to ack broadcast: %s", err)
331 return err378 return err
332 }379 }
380 sess.clearShouldDelay()
333 sess.Log.Debugf("broadcast chan:%v app:%v topLevel:%d payloads:%s",381 sess.Log.Debugf("broadcast chan:%v app:%v topLevel:%d payloads:%s",
334 bcast.ChanId, bcast.AppId, bcast.TopLevel, bcast.Payloads)382 bcast.ChanId, bcast.AppId, bcast.TopLevel, bcast.Payloads)
335 if bcast.ChanId == protocol.SystemChannelId {383 if bcast.ChanId == protocol.SystemChannelId {
336384
=== modified file 'client/session/session_test.go'
--- client/session/session_test.go 2014-04-04 13:55:00 +0000
+++ client/session/session_test.go 2014-04-23 11:53:01 +0000
@@ -32,12 +32,13 @@
3232
33 . "launchpad.net/gocheck"33 . "launchpad.net/gocheck"
3434
35 "launchpad.net/ubuntu-push/client/gethosts"
35 "launchpad.net/ubuntu-push/client/session/levelmap"36 "launchpad.net/ubuntu-push/client/session/levelmap"
36 //"launchpad.net/ubuntu-push/client/gethosts"
37 "launchpad.net/ubuntu-push/logger"37 "launchpad.net/ubuntu-push/logger"
38 "launchpad.net/ubuntu-push/protocol"38 "launchpad.net/ubuntu-push/protocol"
39 helpers "launchpad.net/ubuntu-push/testing"39 helpers "launchpad.net/ubuntu-push/testing"
40 "launchpad.net/ubuntu-push/testing/condition"40 "launchpad.net/ubuntu-push/testing/condition"
41 "launchpad.net/ubuntu-push/util"
41)42)
4243
43func TestSession(t *testing.T) { TestingT(t) }44func TestSession(t *testing.T) { TestingT(t) }
@@ -214,6 +215,10 @@
214 c.Check(sess, NotNil)215 c.Check(sess, NotNil)
215 c.Check(err, IsNil)216 c.Check(err, IsNil)
216 c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"})217 c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"})
218 // the session is happy and redial delayer is default
219 c.Check(sess.ShouldDelay(), Equals, false)
220 c.Check(fmt.Sprintf("%#v", sess.redialDelay), Equals, fmt.Sprintf("%#v", redialDelay))
221 c.Check(sess.redialDelays, DeepEquals, util.Timeouts())
217 // but no root CAs set222 // but no root CAs set
218 c.Check(sess.TLS.RootCAs, IsNil)223 c.Check(sess.TLS.RootCAs, IsNil)
219 c.Check(sess.State(), Equals, Disconnected)224 c.Check(sess.State(), Equals, Disconnected)
@@ -264,16 +269,17 @@
264}269}
265270
266type testHostGetter struct {271type testHostGetter struct {
267 hosts []string272 domain string
268 err error273 hosts []string
274 err error
269}275}
270276
271func (thg *testHostGetter) Get() ([]string, error) {277func (thg *testHostGetter) Get() (*gethosts.Host, error) {
272 return thg.hosts, thg.err278 return &gethosts.Host{thg.domain, thg.hosts}, thg.err
273}279}
274280
275func (cs *clientSessionSuite) TestGetHostsRemote(c *C) {281func (cs *clientSessionSuite) TestGetHostsRemote(c *C) {
276 hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil}282 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
277 sess := &ClientSession{getHost: hostGetter, timeSince: time.Since}283 sess := &ClientSession{getHost: hostGetter, timeSince: time.Since}
278 err := sess.getHosts()284 err := sess.getHosts()
279 c.Assert(err, IsNil)285 c.Assert(err, IsNil)
@@ -284,7 +290,7 @@
284 sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log)290 sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log)
285 c.Assert(err, IsNil)291 c.Assert(err, IsNil)
286 hostsErr := errors.New("failed")292 hostsErr := errors.New("failed")
287 hostGetter := &testHostGetter{nil, hostsErr}293 hostGetter := &testHostGetter{"", nil, hostsErr}
288 sess.getHost = hostGetter294 sess.getHost = hostGetter
289 err = sess.getHosts()295 err = sess.getHosts()
290 c.Assert(err, Equals, hostsErr)296 c.Assert(err, Equals, hostsErr)
@@ -293,7 +299,7 @@
293}299}
294300
295func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) {301func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) {
296 hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil}302 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
297 sess := &ClientSession{303 sess := &ClientSession{
298 getHost: hostGetter,304 getHost: hostGetter,
299 ClientSessionConfig: ClientSessionConfig{305 ClientSessionConfig: ClientSessionConfig{
@@ -318,7 +324,7 @@
318}324}
319325
320func (cs *clientSessionSuite) TestGetHostsRemoteCachingReset(c *C) {326func (cs *clientSessionSuite) TestGetHostsRemoteCachingReset(c *C) {
321 hostGetter := &testHostGetter{[]string{"foo:443", "bar:443"}, nil}327 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
322 sess := &ClientSession{328 sess := &ClientSession{
323 getHost: hostGetter,329 getHost: hostGetter,
324 ClientSessionConfig: ClientSessionConfig{330 ClientSessionConfig: ClientSessionConfig{
@@ -427,7 +433,9 @@
427 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)433 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
428 c.Assert(err, IsNil)434 c.Assert(err, IsNil)
429 sess.deliveryHosts = []string{"nowhere"}435 sess.deliveryHosts = []string{"nowhere"}
436 sess.clearShouldDelay()
430 err = sess.connect()437 err = sess.connect()
438 c.Check(sess.ShouldDelay(), Equals, true)
431 c.Check(err, ErrorMatches, ".*connect.*address.*")439 c.Check(err, ErrorMatches, ".*connect.*address.*")
432 c.Check(sess.State(), Equals, Error)440 c.Check(sess.State(), Equals, Error)
433}441}
@@ -439,7 +447,9 @@
439 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)447 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
440 c.Assert(err, IsNil)448 c.Assert(err, IsNil)
441 sess.deliveryHosts = []string{srv.Addr().String()}449 sess.deliveryHosts = []string{srv.Addr().String()}
450 sess.clearShouldDelay()
442 err = sess.connect()451 err = sess.connect()
452 c.Check(sess.ShouldDelay(), Equals, true)
443 c.Check(err, IsNil)453 c.Check(err, IsNil)
444 c.Check(sess.Connection, NotNil)454 c.Check(sess.Connection, NotNil)
445 c.Check(sess.State(), Equals, Connected)455 c.Check(sess.State(), Equals, Connected)
@@ -452,7 +462,9 @@
452 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)462 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
453 c.Assert(err, IsNil)463 c.Assert(err, IsNil)
454 sess.deliveryHosts = []string{"nowhere", srv.Addr().String()}464 sess.deliveryHosts = []string{"nowhere", srv.Addr().String()}
465 sess.clearShouldDelay()
455 err = sess.connect()466 err = sess.connect()
467 c.Check(sess.ShouldDelay(), Equals, true)
456 c.Check(err, IsNil)468 c.Check(err, IsNil)
457 c.Check(sess.Connection, NotNil)469 c.Check(sess.Connection, NotNil)
458 c.Check(sess.State(), Equals, Connected)470 c.Check(sess.State(), Equals, Connected)
@@ -466,7 +478,9 @@
466 srv.Close()478 srv.Close()
467 c.Assert(err, IsNil)479 c.Assert(err, IsNil)
468 sess.deliveryHosts = []string{srv.Addr().String()}480 sess.deliveryHosts = []string{srv.Addr().String()}
481 sess.clearShouldDelay()
469 err = sess.connect()482 err = sess.connect()
483 c.Check(sess.ShouldDelay(), Equals, true)
470 c.Check(err, ErrorMatches, ".*connection refused")484 c.Check(err, ErrorMatches, ".*connection refused")
471 c.Check(sess.State(), Equals, Error)485 c.Check(sess.State(), Equals, Error)
472}486}
@@ -548,6 +562,27 @@
548 c.Check(<-ch, Not(Equals), 0)562 c.Check(<-ch, Not(Equals), 0)
549}563}
550564
565func (cs *clientSessionSuite) TestAutoRedialCallsRedialDelay(c *C) {
566 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
567 c.Assert(err, IsNil)
568 flag := false
569 sess.redialDelay = func(sess *ClientSession) time.Duration { flag = true; return 0 }
570 sess.AutoRedial(nil)
571 c.Check(flag, Equals, true)
572}
573
574func (cs *clientSessionSuite) TestAutoRedialSetsRedialDelayIfTooQuick(c *C) {
575 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
576 c.Assert(err, IsNil)
577 sess.redialDelay = func(sess *ClientSession) time.Duration { return 0 }
578 sess.AutoRedial(nil)
579 c.Check(sess.ShouldDelay(), Equals, false)
580 sess.stopRedial()
581 sess.clearShouldDelay()
582 sess.AutoRedial(nil)
583 c.Check(sess.ShouldDelay(), Equals, true)
584}
585
551/****************************************************************586/****************************************************************
552 handlePing() tests587 handlePing() tests
553****************************************************************/588****************************************************************/
@@ -594,6 +629,24 @@
594 c.Check(s.sess.State(), Equals, Error)629 c.Check(s.sess.State(), Equals, Error)
595}630}
596631
632func (s *msgSuite) TestHandlePingClearsDelay(c *C) {
633 s.sess.setShouldDelay()
634 s.upCh <- nil // no error
635 c.Check(s.sess.handlePing(), IsNil)
636 c.Assert(len(s.downCh), Equals, 1)
637 c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"})
638 c.Check(s.sess.ShouldDelay(), Equals, false)
639}
640
641func (s *msgSuite) TestHandlePingDoesNotClearsDelayOnError(c *C) {
642 s.sess.setShouldDelay()
643 s.upCh <- errors.New("Pong")
644 c.Check(s.sess.handlePing(), NotNil)
645 c.Assert(len(s.downCh), Equals, 1)
646 c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"})
647 c.Check(s.sess.ShouldDelay(), Equals, true)
648}
649
597/****************************************************************650/****************************************************************
598 handleBroadcast() tests651 handleBroadcast() tests
599****************************************************************/652****************************************************************/
@@ -687,6 +740,32 @@
687 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"nak"})740 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"nak"})
688}741}
689742
743func (s *msgSuite) TestHandleBroadcastClearsDelay(c *C) {
744 s.sess.setShouldDelay()
745
746 msg := serverMsg{"broadcast", protocol.BroadcastMsg{},
747 protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}}
748 go func() { s.errCh <- s.sess.handleBroadcast(&msg) }()
749 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
750 s.upCh <- nil // ack ok
751 c.Check(<-s.errCh, IsNil)
752
753 c.Check(s.sess.ShouldDelay(), Equals, false)
754}
755
756func (s *msgSuite) TestHandleBroadcastDoesNotClearDelayOnError(c *C) {
757 s.sess.setShouldDelay()
758
759 msg := serverMsg{"broadcast", protocol.BroadcastMsg{},
760 protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}}
761 go func() { s.errCh <- s.sess.handleBroadcast(&msg) }()
762 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
763 s.upCh <- errors.New("bcast")
764 c.Check(<-s.errCh, NotNil)
765
766 c.Check(s.sess.ShouldDelay(), Equals, true)
767}
768
690/****************************************************************769/****************************************************************
691 handleConnBroken() tests770 handleConnBroken() tests
692****************************************************************/771****************************************************************/
@@ -1087,9 +1166,64 @@
10871166
1088var (1167var (
1089 dialTestTimeout = 100 * time.Millisecond1168 dialTestTimeout = 100 * time.Millisecond
1090 dialTestConf = ClientSessionConfig{ExchangeTimeout: dialTestTimeout}1169 dialTestConf = ClientSessionConfig{
1170 ExchangeTimeout: dialTestTimeout,
1171 PEM: helpers.TestCertPEMBlock,
1172 }
1091)1173)
10921174
1175func (cs *clientSessionSuite) TestDialBadServerName(c *C) {
1176 // a borked server name
1177 cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock)
1178 c.Assert(err, IsNil)
1179 tlsCfg := &tls.Config{
1180 Certificates: []tls.Certificate{cert},
1181 SessionTicketsDisabled: true,
1182 }
1183
1184 lst, err := tls.Listen("tcp", "localhost:0", tlsCfg)
1185 c.Assert(err, IsNil)
1186 // advertise
1187 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1188 b, err := json.Marshal(map[string]interface{}{
1189 "domain": "xyzzy", // <-- *** THIS *** is the bit that'll break it
1190 "hosts": []string{"nowhere", lst.Addr().String()},
1191 })
1192 if err != nil {
1193 panic(err)
1194 }
1195 w.Header().Set("Content-Type", "application/json")
1196 w.Write(b)
1197 }))
1198 defer ts.Close()
1199
1200 sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log)
1201 c.Assert(err, IsNil)
1202 tconn := &testConn{}
1203 sess.Connection = tconn
1204
1205 upCh := make(chan interface{}, 5)
1206 downCh := make(chan interface{}, 5)
1207 errCh := make(chan error, 1)
1208 proto := &testProtocol{up: upCh, down: downCh}
1209 sess.Protocolator = func(net.Conn) protocol.Protocol { return proto }
1210
1211 go func() {
1212 errCh <- sess.Dial()
1213 }()
1214
1215 srv, err := lst.Accept()
1216 c.Assert(err, IsNil)
1217
1218 // connect done
1219
1220 _, err = protocol.ReadWireFormatVersion(srv, dialTestTimeout)
1221 c.Check(err, NotNil)
1222
1223 c.Check(<-errCh, NotNil)
1224 c.Check(sess.State(), Equals, Error)
1225}
1226
1093func (cs *clientSessionSuite) TestDialWorks(c *C) {1227func (cs *clientSessionSuite) TestDialWorks(c *C) {
1094 // happy path thoughts1228 // happy path thoughts
1095 cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock)1229 cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock)
@@ -1104,7 +1238,8 @@
1104 // advertise1238 // advertise
1105 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {1239 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1106 b, err := json.Marshal(map[string]interface{}{1240 b, err := json.Marshal(map[string]interface{}{
1107 "hosts": []string{"nowhere", lst.Addr().String()},1241 "domain": "localhost",
1242 "hosts": []string{"nowhere", lst.Addr().String()},
1108 })1243 })
1109 if err != nil {1244 if err != nil {
1110 panic(err)1245 panic(err)
@@ -1223,3 +1358,38 @@
1223 c.Assert(err, IsNil)1358 c.Assert(err, IsNil)
1224 // connect done1359 // connect done
1225}1360}
1361
1362/****************************************************************
1363 redialDelay() tests
1364****************************************************************/
1365
1366func (cs *clientSessionSuite) TestShouldDelay(c *C) {
1367 sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
1368 c.Assert(err, IsNil)
1369 c.Check(sess.ShouldDelay(), Equals, false)
1370 sess.setShouldDelay()
1371 c.Check(sess.ShouldDelay(), Equals, true)
1372 sess.clearShouldDelay()
1373 c.Check(sess.ShouldDelay(), Equals, false)
1374}
1375
1376func (cs *clientSessionSuite) TestRedialDelay(c *C) {
1377 sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)
1378 c.Assert(err, IsNil)
1379 sess.redialDelays = []time.Duration{17, 42}
1380 n := 0
1381 sess.redialJitter = func(time.Duration) time.Duration { n++; return 0 }
1382 // we get increasing delays while we're unhappy
1383 sess.setShouldDelay()
1384 c.Check(redialDelay(sess), Equals, time.Duration(17))
1385 c.Check(redialDelay(sess), Equals, time.Duration(42))
1386 c.Check(redialDelay(sess), Equals, time.Duration(42))
1387 // once we're happy, delays drop to 0
1388 sess.clearShouldDelay()
1389 c.Check(redialDelay(sess), Equals, time.Duration(0))
1390 // and start again from the top if we become unhappy again
1391 sess.setShouldDelay()
1392 c.Check(redialDelay(sess), Equals, time.Duration(17))
1393 // and redialJitter got called every time shouldDelay was true
1394 c.Check(n, Equals, 4)
1395}
12261396
=== modified file 'config/config.go'
--- config/config.go 2014-03-25 18:49:18 +0000
+++ config/config.go 2014-04-23 11:53:01 +0000
@@ -20,6 +20,7 @@
20import (20import (
21 "encoding/json"21 "encoding/json"
22 "errors"22 "errors"
23 "flag"
23 "fmt"24 "fmt"
24 "io"25 "io"
25 "io/ioutil"26 "io/ioutil"
@@ -27,6 +28,7 @@
27 "os"28 "os"
28 "path/filepath"29 "path/filepath"
29 "reflect"30 "reflect"
31 "strconv"
30 "strings"32 "strings"
31 "time"33 "time"
32)34)
@@ -118,6 +120,22 @@
118 return fillDestConfig(destValue, p1)120 return fillDestConfig(destValue, p1)
119}121}
120122
123// FromString are config holders that can be set by parsing a string.
124type FromString interface {
125 SetFromString(enc string) error
126}
127
128// UnmarshalJSONViaString helps unmarshalling from JSON for FromString
129// supporting config holders.
130func UnmarshalJSONViaString(dest FromString, b []byte) error {
131 var enc string
132 err := json.Unmarshal(b, &enc)
133 if err != nil {
134 return err
135 }
136 return dest.SetFromString(enc)
137}
138
121// ConfigTimeDuration can hold a time.Duration in a configuration struct,139// ConfigTimeDuration can hold a time.Duration in a configuration struct,
122// that is parsed from a string as supported by time.ParseDuration.140// that is parsed from a string as supported by time.ParseDuration.
123type ConfigTimeDuration struct {141type ConfigTimeDuration struct {
@@ -125,13 +143,11 @@
125}143}
126144
127func (ctd *ConfigTimeDuration) UnmarshalJSON(b []byte) error {145func (ctd *ConfigTimeDuration) UnmarshalJSON(b []byte) error {
128 var enc string146 return UnmarshalJSONViaString(ctd, b)
129 var v time.Duration147}
130 err := json.Unmarshal(b, &enc)148
131 if err != nil {149func (ctd *ConfigTimeDuration) SetFromString(enc string) error {
132 return err150 v, err := time.ParseDuration(enc)
133 }
134 v, err = time.ParseDuration(enc)
135 if err != nil {151 if err != nil {
136 return err152 return err
137 }153 }
@@ -148,12 +164,11 @@
148type ConfigHostPort string164type ConfigHostPort string
149165
150func (chp *ConfigHostPort) UnmarshalJSON(b []byte) error {166func (chp *ConfigHostPort) UnmarshalJSON(b []byte) error {
151 var enc string167 return UnmarshalJSONViaString(chp, b)
152 err := json.Unmarshal(b, &enc)168}
153 if err != nil {169
154 return err170func (chp *ConfigHostPort) SetFromString(enc string) error {
155 }171 _, _, err := net.SplitHostPort(enc)
156 _, _, err = net.SplitHostPort(enc)
157 if err != nil {172 if err != nil {
158 return err173 return err
159 }174 }
@@ -198,23 +213,117 @@
198 return ioutil.ReadFile(p)213 return ioutil.ReadFile(p)
199}214}
200215
201// ReadFiles reads configuration from a set of files. Uses ReadConfig internally.216// used to implement getting config values with flag.Parse()
217type val struct {
218 destField destField
219 accu map[string]json.RawMessage
220}
221
222func (v *val) String() string { // used to show default
223 return string(v.accu[v.destField.configName()])
224}
225
226func (v *val) IsBoolFlag() bool {
227 return v.destField.fld.Type.Kind() == reflect.Bool
228}
229
230func (v *val) marshalAsNeeded(s string) (json.RawMessage, error) {
231 var toMarshal interface{}
232 switch v.destField.dest.(type) {
233 case *string, FromString:
234 toMarshal = s
235 case *bool:
236 bit, err := strconv.ParseBool(s)
237 if err != nil {
238 return nil, err
239 }
240 toMarshal = bit
241 default:
242 return json.RawMessage(s), nil
243 }
244 return json.Marshal(toMarshal)
245}
246
247func (v *val) Set(s string) error {
248 marshalled, err := v.marshalAsNeeded(s)
249 if err != nil {
250 return err
251 }
252 v.accu[v.destField.configName()] = marshalled
253 return nil
254}
255
256func readOneConfig(accu map[string]json.RawMessage, cfgPath string) error {
257 r, err := os.Open(cfgPath)
258 if err != nil {
259 return err
260 }
261 defer r.Close()
262 err = json.NewDecoder(r).Decode(&accu)
263 if err != nil {
264 return err
265 }
266 return nil
267}
268
269// used to implement -cfg@=
270type readConfigAtVal struct {
271 accu map[string]json.RawMessage
272}
273
274func (v *readConfigAtVal) String() string {
275 return "<config.json>"
276}
277
278func (v *readConfigAtVal) Set(path string) error {
279 return readOneConfig(v.accu, path)
280}
281
282// readUsingFlags gets config values from command line flags.
283func readUsingFlags(accu map[string]json.RawMessage, destValue reflect.Value) error {
284 if flag.Parsed() {
285 if IgnoreParsedFlags {
286 return nil
287 }
288 return fmt.Errorf("too late, flags already parsed")
289 }
290 destStruct := destValue.Elem()
291 for destField := range traverseStruct(destStruct) {
292 help := destField.fld.Tag.Get("help")
293 flag.Var(&val{destField, accu}, destField.configName(), help)
294 }
295 flag.Var(&readConfigAtVal{accu}, "cfg@", "get config values from file")
296 flag.Parse()
297 return nil
298}
299
300// IgnoreParsedFlags will just have ReadFiles ignore <flags> if the
301// command line was already parsed.
302var IgnoreParsedFlags = false
303
304// ReadFiles reads configuration from a set of files. The string
305// "<flags>" can be used as a pseudo file-path, it will consider
306// command line flags, invoking flag.Parse(). Among those the flag
307// -cfg@=FILE can be used to get further config values from FILE.
202func ReadFiles(destConfig interface{}, cfgFpaths ...string) error {308func ReadFiles(destConfig interface{}, cfgFpaths ...string) error {
203 destValue, err := checkDestConfig("destConfig", destConfig)309 destValue, err := checkDestConfig("destConfig", destConfig)
204 if err != nil {310 if err != nil {
205 return err311 return err
206 }312 }
207 // do the parsing in two phases for better error handling313 // do the parsing in two phases for better error handling
208 var p1 map[string]json.RawMessage314 p1 := make(map[string]json.RawMessage)
209 readOne := false315 readOne := false
210 for _, cfgPath := range cfgFpaths {316 for _, cfgPath := range cfgFpaths {
317 if cfgPath == "<flags>" {
318 err := readUsingFlags(p1, destValue)
319 if err != nil {
320 return err
321 }
322 readOne = true
323 continue
324 }
211 if _, err := os.Stat(cfgPath); err == nil {325 if _, err := os.Stat(cfgPath); err == nil {
212 r, err := os.Open(cfgPath)326 err := readOneConfig(p1, cfgPath)
213 if err != nil {
214 return err
215 }
216 defer r.Close()
217 err = json.NewDecoder(r).Decode(&p1)
218 if err != nil {327 if err != nil {
219 return err328 return err
220 }329 }
221330
=== modified file 'config/config_test.go'
--- config/config_test.go 2014-03-25 18:49:18 +0000
+++ config/config_test.go 2014-04-23 11:53:01 +0000
@@ -18,6 +18,9 @@
1818
19import (19import (
20 "bytes"20 "bytes"
21 "encoding/json"
22 "flag"
23 "fmt"
21 "io/ioutil"24 "io/ioutil"
22 "os"25 "os"
23 "path/filepath"26 "path/filepath"
@@ -230,3 +233,105 @@
230 c.Check(res, DeepEquals, []string{"b", "c_list", "d"})233 c.Check(res, DeepEquals, []string{"b", "c_list", "d"})
231234
232}235}
236
237type testConfig3 struct {
238 A bool
239 B string
240 C []string `json:"c_list"`
241 D ConfigTimeDuration `help:"duration"`
242 E ConfigHostPort
243 F string
244}
245
246type configFlagsSuite struct{}
247
248var _ = Suite(&configFlagsSuite{})
249
250func (s *configFlagsSuite) SetUpTest(c *C) {
251 flag.CommandLine = flag.NewFlagSet("cmd", flag.PanicOnError)
252 // supress outputs
253 flag.Usage = func() { flag.PrintDefaults() }
254 flag.CommandLine.SetOutput(ioutil.Discard)
255}
256
257func (s *configFlagsSuite) TestReadUsingFlags(c *C) {
258 os.Args = []string{"cmd", "-a=1", "-b=foo", "-c_list", `["x","y"]`, "-d", "10s", "-e=localhost:80"}
259 var cfg testConfig3
260 p := make(map[string]json.RawMessage)
261 err := readUsingFlags(p, reflect.ValueOf(&cfg))
262 c.Assert(err, IsNil)
263 c.Check(p, DeepEquals, map[string]json.RawMessage{
264 "a": json.RawMessage("true"),
265 "b": json.RawMessage(`"foo"`),
266 "c_list": json.RawMessage(`["x","y"]`),
267 "d": json.RawMessage(`"10s"`),
268 "e": json.RawMessage(`"localhost:80"`),
269 })
270}
271
272func (s *configFlagsSuite) TestReadUsingFlagsBoolError(c *C) {
273 os.Args = []string{"cmd", "-a=zoo"}
274 var cfg testConfig3
275 p := make(map[string]json.RawMessage)
276 c.Check(func() { readUsingFlags(p, reflect.ValueOf(&cfg)) }, PanicMatches, ".*invalid boolean.*-a.*")
277}
278
279func (s *configFlagsSuite) TestReadFilesAndFlags(c *C) {
280 // test <flags> pseudo file
281 os.Args = []string{"cmd", "-b=x"}
282 tmpDir := c.MkDir()
283 cfgPath := filepath.Join(tmpDir, "cfg.json")
284 err := ioutil.WriteFile(cfgPath, []byte(`{"a": 42, "c_list": ["y", "z"]}`), os.ModePerm)
285 c.Assert(err, IsNil)
286 var cfg testConfig1
287 err = ReadFiles(&cfg, cfgPath, "<flags>")
288 c.Assert(err, IsNil)
289 c.Check(cfg.A, Equals, 42)
290 c.Check(cfg.B, Equals, "x")
291 c.Check(cfg.C, DeepEquals, []string{"y", "z"})
292}
293
294func (s *configFlagsSuite) TestReadFilesAndFlagsConfigAtSupport(c *C) {
295 // test <flags> pseudo file
296 tmpDir := c.MkDir()
297 cfgPath := filepath.Join(tmpDir, "cfg.json")
298 os.Args = []string{"cmd", "-a=42", fmt.Sprintf("-cfg@=%s", cfgPath)}
299 err := ioutil.WriteFile(cfgPath, []byte(`{"b": "x", "c_list": ["y", "z"]}`), os.ModePerm)
300 c.Assert(err, IsNil)
301 var cfg testConfig1
302 err = ReadFiles(&cfg, "<flags>")
303 c.Assert(err, IsNil)
304 c.Check(cfg.A, Equals, 42)
305 c.Check(cfg.B, Equals, "x")
306 c.Check(cfg.C, DeepEquals, []string{"y", "z"})
307}
308
309func (s *configFlagsSuite) TestReadUsingFlagsHelp(c *C) {
310 os.Args = []string{"cmd", "-h"}
311 buf := bytes.NewBufferString("")
312 flag.CommandLine.Init("cmd", flag.ContinueOnError)
313 flag.CommandLine.SetOutput(buf)
314 var cfg testConfig3
315 p := map[string]json.RawMessage{
316 "d": json.RawMessage(`"2s"`),
317 }
318 readUsingFlags(p, reflect.ValueOf(&cfg))
319 c.Check(buf.String(), Matches, `(?s).*-cfg@=<config.json>: get config values from file\n.*-d="2s": duration.*`)
320}
321
322func (s *configFlagsSuite) TestReadUsingFlagsAlreadyParsed(c *C) {
323 os.Args = []string{"cmd"}
324 flag.Parse()
325 var cfg struct{}
326 p := make(map[string]json.RawMessage)
327 err := readUsingFlags(p, reflect.ValueOf(&cfg))
328 c.Assert(err, ErrorMatches, "too late, flags already parsed")
329 err = ReadFiles(&cfg, "<flags>")
330 c.Assert(err, ErrorMatches, "too late, flags already parsed")
331 IgnoreParsedFlags = true
332 defer func() {
333 IgnoreParsedFlags = false
334 }()
335 err = ReadFiles(&cfg, "<flags>")
336 c.Assert(err, IsNil)
337}
233338
=== modified file 'debian/changelog'
--- debian/changelog 2014-04-11 18:31:57 +0000
+++ debian/changelog 2014-04-23 11:53:01 +0000
@@ -1,3 +1,22 @@
1ubuntu-push (0.2.1-0ubuntu1) UNRELEASED; urgency=high
2
3 [ Samuele Pedroni ]
4 * gave the client the ability to get config from commandline
5 ( => easier automated testing) (LP: #1311600)
6
7 [ John Lenton ]
8 * Ensure ubuntu-push-client is the only one running in the session.
9 (LP: #1309432)
10 * Remove supurious numbers in brackets in notifications. (LP: #1308145)
11 * Check the server certificate and server name. (LP: #1297969)
12 * Loop whoopsie_identifier_generate until it starts working. (LP: #1309237)
13 * In the session: set a flag on connect, clear it on successfully
14 replying to ping or broadcast messages, check it at the top of
15 autoredial. Also track the last autoredial, and set the delay flag if
16 autoredial is re-called too quickly. (LP: #1309231)
17
18 -- John Lenton <john.lenton@canonical.com> Fri, 18 Apr 2014 10:42:31 +0100
19
1ubuntu-push (0.2+14.04.20140411-0ubuntu1) trusty; urgency=medium20ubuntu-push (0.2+14.04.20140411-0ubuntu1) trusty; urgency=medium
221
3 [ John Lenton ]22 [ John Lenton ]
423
=== modified file 'logger/logger.go'
--- logger/logger.go 2014-02-24 10:27:38 +0000
+++ logger/logger.go 2014-04-23 11:53:01 +0000
@@ -23,6 +23,8 @@
23 "log"23 "log"
24 "os"24 "os"
25 "runtime"25 "runtime"
26
27 "launchpad.net/ubuntu-push/config"
26)28)
2729
28// Logger is a simple logger interface with logging at levels.30// Logger is a simple logger interface with logging at levels.
@@ -119,3 +121,28 @@
119 lg.outputFunc(2, fmt.Sprintf("DEBUG "+format, v...))121 lg.outputFunc(2, fmt.Sprintf("DEBUG "+format, v...))
120 }122 }
121}123}
124
125// config bits
126
127// ConfigLogLevel can hold a log level in a configuration struct.
128type ConfigLogLevel string
129
130func (cll *ConfigLogLevel) ConfigFromJSONString() {}
131
132func (cll *ConfigLogLevel) UnmarshalJSON(b []byte) error {
133 return config.UnmarshalJSONViaString(cll, b)
134}
135
136func (cll *ConfigLogLevel) SetFromString(enc string) error {
137 _, ok := levelToNLevel[enc]
138 if !ok {
139 return fmt.Errorf("not a log level: %s", enc)
140 }
141 *cll = ConfigLogLevel(enc)
142 return nil
143}
144
145// Level returns the log level string held in cll.
146func (cll ConfigLogLevel) Level() string {
147 return string(cll)
148}
122149
=== modified file 'logger/logger_test.go'
--- logger/logger_test.go 2014-02-10 22:51:43 +0000
+++ logger/logger_test.go 2014-04-23 11:53:01 +0000
@@ -25,6 +25,8 @@
25 "testing"25 "testing"
2626
27 . "launchpad.net/gocheck"27 . "launchpad.net/gocheck"
28
29 "launchpad.net/ubuntu-push/config"
28)30)
2931
30func TestLogger(t *testing.T) { TestingT(t) }32func TestLogger(t *testing.T) { TestingT(t) }
@@ -138,3 +140,26 @@
138 logger.Output(1, "foobaz")140 logger.Output(1, "foobaz")
139 c.Check(buf.String(), Matches, "logger_test.go:[0-9]+: foobar\nlogger_test.go:[0-9]+: foobaz\n")141 c.Check(buf.String(), Matches, "logger_test.go:[0-9]+: foobar\nlogger_test.go:[0-9]+: foobaz\n")
140}142}
143
144type testLogLevelConfig struct {
145 Lvl ConfigLogLevel
146}
147
148func (s *loggerSuite) TestReadConfigLogLevel(c *C) {
149 buf := bytes.NewBufferString(`{"lvl": "debug"}`)
150 var cfg testLogLevelConfig
151 err := config.ReadConfig(buf, &cfg)
152 c.Assert(err, IsNil)
153 c.Check(cfg.Lvl.Level(), Equals, "debug")
154}
155
156func (s *loggerSuite) TestReadConfigLogLevelErrors(c *C) {
157 var cfg testLogLevelConfig
158 checkError := func(jsonCfg string, expectedError string) {
159 buf := bytes.NewBufferString(jsonCfg)
160 err := config.ReadConfig(buf, &cfg)
161 c.Check(err, ErrorMatches, expectedError)
162 }
163 checkError(`{"lvl": 1}`, "lvl:.*type string")
164 checkError(`{"lvl": "foo"}`, "lvl: not a log level: foo")
165}
141166
=== modified file 'server/acceptance/cmd/acceptanceclient.go'
--- server/acceptance/cmd/acceptanceclient.go 2014-04-10 13:52:31 +0000
+++ server/acceptance/cmd/acceptanceclient.go 2014-04-23 11:53:01 +0000
@@ -48,13 +48,18 @@
48 fmt.Fprintf(os.Stderr, "Usage: acceptancclient [options] <config.json> <device id>\n")48 fmt.Fprintf(os.Stderr, "Usage: acceptancclient [options] <config.json> <device id>\n")
49 flag.PrintDefaults()49 flag.PrintDefaults()
50 }50 }
51 missingArg := func(what string) {
52 fmt.Fprintf(os.Stderr, "missing %s\n", what)
53 flag.Usage()
54 os.Exit(2)
55 }
51 flag.Parse()56 flag.Parse()
52 narg := flag.NArg()57 narg := flag.NArg()
53 switch {58 switch {
54 case narg < 1:59 case narg < 1:
55 log.Fatal("missing config file")60 missingArg("config file")
56 case narg < 2:61 case narg < 2:
57 log.Fatal("missing device-id")62 missingArg("device-id")
58 }63 }
59 configFName := flag.Arg(0)64 configFName := flag.Arg(0)
60 f, err := os.Open(configFName)65 f, err := os.Open(configFName)
6166
=== modified file 'ubuntu-push-client.go'
--- ubuntu-push-client.go 2014-03-12 13:25:20 +0000
+++ ubuntu-push-client.go 2014-04-23 11:53:01 +0000
@@ -19,12 +19,37 @@
19import (19import (
20 "log"20 "log"
2121
22 "launchpad.net/go-dbus/v1"
22 "launchpad.net/go-xdg/v0"23 "launchpad.net/go-xdg/v0"
2324
24 "launchpad.net/ubuntu-push/client"25 "launchpad.net/ubuntu-push/client"
25)26)
2627
28const NAME = "com.ubuntu.PushNotifications"
29
30// grabName grabs ownership of the dbus name, and bails the client as
31// soon as somebody else grabs it.
32func grabName() {
33 conn, err := dbus.Connect(dbus.SessionBus)
34 if err != nil {
35 log.Fatalf("bus: %v", err)
36 }
37
38 flags := dbus.NameFlagAllowReplacement | dbus.NameFlagReplaceExisting
39 n := conn.RequestName(NAME, flags)
40 go func() {
41 for err := range n.C {
42 if err != nil {
43 log.Fatalf("FATAL: name channel got: %v", err)
44 }
45 }
46 }()
47}
48
27func main() {49func main() {
50 // XXX: this is a quick hack to ensure unicity
51 grabName()
52
28 cfgFname, err := xdg.Config.Find("ubuntu-push-client/config.json")53 cfgFname, err := xdg.Config.Find("ubuntu-push-client/config.json")
29 if err != nil {54 if err != nil {
30 log.Fatalf("unable to find a configuration file: %v", err)55 log.Fatalf("unable to find a configuration file: %v", err)
3156
=== modified file 'whoopsie/identifier/identifier.go'
--- whoopsie/identifier/identifier.go 2014-02-21 16:17:28 +0000
+++ whoopsie/identifier/identifier.go 2014-04-23 11:53:01 +0000
@@ -27,6 +27,7 @@
27import "C"27import "C"
28import "unsafe"28import "unsafe"
29import "errors"29import "errors"
30import "time"
3031
31// an Id knows how to generate itself, and how to stringify itself.32// an Id knows how to generate itself, and how to stringify itself.
32type Id interface {33type Id interface {
@@ -36,12 +37,17 @@
3637
37// Identifier is the default Id implementation.38// Identifier is the default Id implementation.
38type Identifier struct {39type Identifier struct {
39 value string40 value string
41 generator func(**C.char, **C.GError)
42}
43
44func generator(csp **C.char, errp **C.GError) {
45 C.whoopsie_identifier_generate(csp, errp)
40}46}
4147
42// New creates an Identifier, but does not call Generate() on it.48// New creates an Identifier, but does not call Generate() on it.
43func New() Id {49func New() Id {
44 return &Identifier{}50 return &Identifier{generator: generator}
45}51}
4652
47// Generate makes the Identifier create the identifier itself.53// Generate makes the Identifier create the identifier itself.
@@ -49,8 +55,18 @@
49 var gerr *C.GError55 var gerr *C.GError
50 var cs *C.char56 var cs *C.char
51 defer C.g_free((C.gpointer)(unsafe.Pointer(cs)))57 defer C.g_free((C.gpointer)(unsafe.Pointer(cs)))
52 C.whoopsie_identifier_generate(&cs, &gerr)58
5359 for i := 0; i < 200; i++ {
60 id.generator(&cs, &gerr)
61
62 if cs != nil || gerr != nil {
63 goto SuccessMaybe
64 }
65 time.Sleep(600 * time.Millisecond)
66 }
67 return errors.New("whoopsie_identifier_generate still bad after 2m; giving up")
68
69SuccessMaybe:
54 if gerr != nil {70 if gerr != nil {
55 return errors.New(C.GoString((*C.char)(gerr.message)))71 return errors.New(C.GoString((*C.char)(gerr.message)))
56 } else {72 } else {
5773
=== modified file 'whoopsie/identifier/identifier_test.go'
--- whoopsie/identifier/identifier_test.go 2014-01-15 15:51:50 +0000
+++ whoopsie/identifier/identifier_test.go 2014-04-23 11:53:01 +0000
@@ -41,3 +41,18 @@
41func (s *IdentifierSuite) TestIdentifierInterface(c *C) {41func (s *IdentifierSuite) TestIdentifierInterface(c *C) {
42 _ = []Id{New()}42 _ = []Id{New()}
43}43}
44
45// TestFailure checks that Identifier survives whoopsie shenanigans
46func (s *IdentifierSuite) TestIdentifierSurvivesShenanigans(c *C) {
47 count := 0
48 // using _Ctype* as a workaround for gocheck also having a C
49 gen := func(csp **_Ctype_char, errp **_Ctype_GError) {
50 count++
51 if count > 3 {
52 generator(csp, errp)
53 }
54 }
55 id := &Identifier{generator: gen}
56 id.Generate()
57 c.Check(id.String(), HasLen, 128)
58}

Subscribers

People subscribed via source and target branches