Merge lp:~rogpeppe/juju-core/391-1.14-avoid-allFatal into lp:juju-core/1.14

Proposed by Roger Peppe
Status: Merged
Approved by: Roger Peppe
Approved revision: no longer in the source branch.
Merged at revision: 1741
Proposed branch: lp:~rogpeppe/juju-core/391-1.14-avoid-allFatal
Merge into: lp:juju-core/1.14
Diff against target: 761 lines (+313/-65)
13 files modified
cmd/jujud/agent.go (+21/-0)
cmd/jujud/agent_test.go (+66/-0)
cmd/jujud/machine.go (+14/-8)
cmd/jujud/machine_test.go (+6/-6)
cmd/jujud/unit.go (+2/-2)
juju/testing/conn.go (+48/-36)
state/api/apiclient.go (+5/-4)
state/apiserver/pinger_test.go (+12/-0)
state/state.go (+6/-0)
state/state_test.go (+6/-0)
testing/checkers/set.go (+35/-0)
testing/checkers/set_test.go (+54/-0)
testing/mgo.go (+38/-9)
To merge this branch: bzr merge lp:~rogpeppe/juju-core/391-1.14-avoid-allFatal
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+185445@code.launchpad.net

Commit message

cmd/jujud: avoid using allFatal

This is a backport of the https://code.launchpad.net/~rogpeppe/juju-core/384-avoid-allfatal/+merge/184743
now proposed for 1.14, with selected other patches from other revisions
to make it compile.

https://codereview.appspot.com/13696043/

Description of the change

cmd/jujud: avoid using allFatal

This is a backport of the https://code.launchpad.net/~rogpeppe/juju-core/384-avoid-allfatal/+merge/184743
now proposed for 1.14, with selected other patches from other revisions
to make it compile.

https://codereview.appspot.com/13696043/

To post a comment you must log in.
Revision history for this message
Roger Peppe (rogpeppe) wrote :

Reviewers: mp+185445_code.launchpad.net,

Message:
Please take a look.

Description:
cmd/jujud: avoid using allFatal

This is a backport of the
https://code.launchpad.net/~rogpeppe/juju-core/384-avoid-allfatal/+merge/184743
now proposed for 1.14, with selected other patches from other revisions
to make it compile.

https://code.launchpad.net/~rogpeppe/juju-core/391-1.14-avoid-allFatal/+merge/185445

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/13696043/

Affected files (+315, -65 lines):
   A [revision details]
   M cmd/jujud/agent.go
   M cmd/jujud/agent_test.go
   M cmd/jujud/machine.go
   M cmd/jujud/machine_test.go
   M cmd/jujud/unit.go
   M juju/testing/conn.go
   M state/api/apiclient.go
   M state/apiserver/pinger_test.go
   M state/state.go
   M state/state_test.go
   A testing/checkers/set.go
   A testing/checkers/set_test.go
   M testing/mgo.go

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Revision history for this message
William Reade (fwereade) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/jujud/agent.go'
2--- cmd/jujud/agent.go 2013-09-11 06:27:05 +0000
3+++ cmd/jujud/agent.go 2013-09-13 09:26:01 +0000
4@@ -111,6 +111,27 @@
5 return ok
6 }
7
8+type pinger interface {
9+ Ping() error
10+}
11+
12+// connectionIsFatal returns a function suitable for passing
13+// as the isFatal argument to worker.NewRunner,
14+// that diagnoses an error as fatal if the connection
15+// has failed or if the error is otherwise fatal.
16+func connectionIsFatal(conn pinger) func(err error) bool {
17+ return func(err error) bool {
18+ if isFatal(err) {
19+ return true
20+ }
21+ if err := conn.Ping(); err != nil {
22+ log.Infof("error pinging %T: %v", conn, err)
23+ return true
24+ }
25+ return false
26+ }
27+}
28+
29 // isleep waits for the given duration or until it receives a value on
30 // stop. It returns whether the full duration was slept without being
31 // stopped.
32
33=== modified file 'cmd/jujud/agent_test.go'
34--- cmd/jujud/agent_test.go 2013-08-24 13:50:05 +0000
35+++ cmd/jujud/agent_test.go 2013-09-13 09:26:01 +0000
36@@ -20,7 +20,9 @@
37 envtools "launchpad.net/juju-core/environs/tools"
38 "launchpad.net/juju-core/juju/testing"
39 "launchpad.net/juju-core/state"
40+ "launchpad.net/juju-core/state/api/params"
41 coretesting "launchpad.net/juju-core/testing"
42+ jc "launchpad.net/juju-core/testing/checkers"
43 coretools "launchpad.net/juju-core/tools"
44 "launchpad.net/juju-core/version"
45 "launchpad.net/juju-core/worker"
46@@ -48,6 +50,70 @@
47 }
48 }
49
50+var isFatalTests = []struct {
51+ err error
52+ isFatal bool
53+}{{
54+ err: worker.ErrTerminateAgent,
55+ isFatal: true,
56+}, {
57+ err: &upgrader.UpgradeReadyError{},
58+ isFatal: true,
59+}, {
60+ err: &params.Error{
61+ Message: "blah",
62+ Code: params.CodeNotProvisioned,
63+ },
64+ isFatal: true,
65+}, {
66+ err: &fatalError{"some fatal error"},
67+ isFatal: true,
68+}, {
69+ err: stderrors.New("foo"),
70+ isFatal: false,
71+}, {
72+ err: &params.Error{
73+ Message: "blah",
74+ Code: params.CodeNotFound,
75+ },
76+ isFatal: false,
77+}}
78+
79+func (*toolSuite) TestIsFatal(c *gc.C) {
80+ for i, test := range isFatalTests {
81+ c.Logf("test %d: %s", i, test.err)
82+ c.Assert(isFatal(test.err), gc.Equals, test.isFatal)
83+ }
84+}
85+
86+type testPinger func() error
87+
88+func (f testPinger) Ping() error {
89+ return f()
90+}
91+
92+func (s *toolSuite) TestConnectionIsFatal(c *gc.C) {
93+ var (
94+ errPinger testPinger = func() error {
95+ return stderrors.New("ping error")
96+ }
97+ okPinger testPinger = func() error {
98+ return nil
99+ }
100+ )
101+ for i, pinger := range []testPinger{errPinger, okPinger} {
102+ for j, test := range isFatalTests {
103+ c.Logf("test %d.%d: %s", i, j, test.err)
104+ fatal := connectionIsFatal(pinger)(test.err)
105+ if test.isFatal {
106+ c.Check(fatal, jc.IsTrue)
107+ } else {
108+ c.Check(fatal, gc.Equals, i == 0)
109+ }
110+ }
111+ }
112+}
113+
114 func mkTools(s string) *coretools.Tools {
115 return &coretools.Tools{
116 Version: version.MustParseBinary(s + "-foo-bar"),
117
118=== modified file 'cmd/jujud/machine.go'
119--- cmd/jujud/machine.go 2013-09-11 06:27:05 +0000
120+++ cmd/jujud/machine.go 2013-09-13 09:26:01 +0000
121@@ -34,6 +34,16 @@
122 "launchpad.net/juju-core/worker/upgrader"
123 )
124
125+type workerRunner interface {
126+ worker.Worker
127+ StartWorker(id string, startFunc func() (worker.Worker, error)) error
128+ StopWorker(id string) error
129+}
130+
131+var newRunner = func(isFatal func(error) bool, moreImportant func(e0, e1 error) bool) workerRunner {
132+ return worker.NewRunner(isFatal, moreImportant)
133+}
134+
135 const bootstrapMachineId = "0"
136
137 var retryDelay = 3 * time.Second
138@@ -44,7 +54,7 @@
139 tomb tomb.Tomb
140 Conf AgentConf
141 MachineId string
142- runner *worker.Runner
143+ runner workerRunner
144 }
145
146 // Info returns usage information for the command.
147@@ -68,7 +78,7 @@
148 if err := a.Conf.checkArgs(args); err != nil {
149 return err
150 }
151- a.runner = worker.NewRunner(isFatal, moreImportant)
152+ a.runner = newRunner(isFatal, moreImportant)
153 return nil
154 }
155
156@@ -127,10 +137,6 @@
157 return err
158 }
159
160-func allFatal(error) bool {
161- return true
162-}
163-
164 var stateJobs = map[params.MachineJob]bool{
165 params.JobHostUnits: true,
166 params.JobManageEnviron: true,
167@@ -164,7 +170,7 @@
168 if needsStateWorker {
169 ensureStateWorker()
170 }
171- runner := worker.NewRunner(allFatal, moreImportant)
172+ runner := newRunner(connectionIsFatal(st), moreImportant)
173 // Only the machiner currently connects to the API.
174 // Add other workers here as they are ready.
175 runner.StartWorker("machiner", func() (worker.Worker, error) {
176@@ -208,7 +214,7 @@
177 // TODO(rog) use more discriminating test for errors
178 // rather than taking everything down indiscriminately.
179 dataDir := a.Conf.dataDir
180- runner := worker.NewRunner(allFatal, moreImportant)
181+ runner := newRunner(connectionIsFatal(st), moreImportant)
182 // At this stage, since we don't embed lxc containers, just start an lxc
183 // provisioner task for non-lxc containers. Since we have only LXC
184 // containers and normal machines, this effectively means that we only
185
186=== modified file 'cmd/jujud/machine_test.go'
187--- cmd/jujud/machine_test.go 2013-09-11 06:27:05 +0000
188+++ cmd/jujud/machine_test.go 2013-09-13 09:26:01 +0000
189@@ -24,7 +24,7 @@
190 "launchpad.net/juju-core/state/api/params"
191 "launchpad.net/juju-core/state/watcher"
192 "launchpad.net/juju-core/testing"
193- "launchpad.net/juju-core/testing/checkers"
194+ jc "launchpad.net/juju-core/testing/checkers"
195 "launchpad.net/juju-core/tools"
196 "launchpad.net/juju-core/version"
197 "launchpad.net/juju-core/worker/deployer"
198@@ -33,7 +33,7 @@
199 type MachineSuite struct {
200 agentSuite
201 lxc.TestSuite
202- oldCacheDir string
203+ restoreCacheDir jc.Restorer
204 }
205
206 var _ = gc.Suite(&MachineSuite{})
207@@ -41,11 +41,11 @@
208 func (s *MachineSuite) SetUpSuite(c *gc.C) {
209 s.agentSuite.SetUpSuite(c)
210 s.TestSuite.SetUpSuite(c)
211- s.oldCacheDir = charm.CacheDir
212+ s.restoreCacheDir = jc.Set(&charm.CacheDir, c.MkDir())
213 }
214
215 func (s *MachineSuite) TearDownSuite(c *gc.C) {
216- charm.CacheDir = s.oldCacheDir
217+ s.restoreCacheDir()
218 s.TestSuite.TearDownSuite(c)
219 s.agentSuite.TearDownSuite(c)
220 }
221@@ -231,7 +231,7 @@
222 if err == nil && attempt.HasNext() {
223 continue
224 }
225- c.Assert(err, checkers.Satisfies, errors.IsNotFoundError)
226+ c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
227 }
228
229 // short-circuit-remove u1 after it's been deployed; check it's recalled
230@@ -239,7 +239,7 @@
231 err = u1.Destroy()
232 c.Assert(err, gc.IsNil)
233 err = u1.Refresh()
234- c.Assert(err, checkers.Satisfies, errors.IsNotFoundError)
235+ c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
236 ctx.waitDeployed(c)
237 }
238
239
240=== modified file 'cmd/jujud/unit.go'
241--- cmd/jujud/unit.go 2013-09-11 06:27:05 +0000
242+++ cmd/jujud/unit.go 2013-09-13 09:26:01 +0000
243@@ -85,7 +85,7 @@
244 }
245 unit := entity.(*state.Unit)
246 dataDir := a.Conf.dataDir
247- runner := worker.NewRunner(allFatal, moreImportant)
248+ runner := worker.NewRunner(connectionIsFatal(st), moreImportant)
249 runner.StartWorker("uniter", func() (worker.Worker, error) {
250 return uniter.NewUniter(st, unit.Name(), dataDir), nil
251 })
252@@ -98,7 +98,7 @@
253 return nil, err
254 }
255 dataDir := a.Conf.dataDir
256- runner := worker.NewRunner(allFatal, moreImportant)
257+ runner := worker.NewRunner(connectionIsFatal(st), moreImportant)
258 runner.StartWorker("upgrader", func() (worker.Worker, error) {
259 return upgrader.New(st.Upgrader(), entity.Tag(), dataDir), nil
260 })
261
262=== modified file 'juju/testing/conn.go'
263--- juju/testing/conn.go 2013-09-11 06:27:05 +0000
264+++ juju/testing/conn.go 2013-09-13 09:26:01 +0000
265@@ -9,7 +9,7 @@
266 "os"
267 "path/filepath"
268
269- . "launchpad.net/gocheck"
270+ gc "launchpad.net/gocheck"
271
272 "launchpad.net/juju-core/charm"
273 "launchpad.net/juju-core/constraints"
274@@ -85,13 +85,13 @@
275
276 // StartInstance is a test helper function that starts an instance on the
277 // environment using the current series and invalid info states.
278-func StartInstance(c *C, env environs.Environ, machineId string) (instance.Instance, *instance.HardwareCharacteristics) {
279+func StartInstance(c *gc.C, env environs.Environ, machineId string) (instance.Instance, *instance.HardwareCharacteristics) {
280 return StartInstanceWithConstraints(c, env, machineId, constraints.Value{})
281 }
282
283 // StartInstanceWithConstraints is a test helper function that starts an instance on the
284 // environment with the specified constraints, using the current series and invalid info states.
285-func StartInstanceWithConstraints(c *C, env environs.Environ, machineId string,
286+func StartInstanceWithConstraints(c *gc.C, env environs.Environ, machineId string,
287 cons constraints.Value) (instance.Instance, *instance.HardwareCharacteristics) {
288 series := config.DefaultSeries
289 inst, metadata, err := provider.StartInstance(
290@@ -103,7 +103,7 @@
291 FakeStateInfo(machineId),
292 FakeAPIInfo(machineId),
293 )
294- c.Assert(err, IsNil)
295+ c.Assert(err, gc.IsNil)
296 return inst, metadata
297 }
298
299@@ -119,24 +119,24 @@
300 agent-version: %s
301 `
302
303-func (s *JujuConnSuite) SetUpSuite(c *C) {
304+func (s *JujuConnSuite) SetUpSuite(c *gc.C) {
305 s.LoggingSuite.SetUpSuite(c)
306 s.MgoSuite.SetUpSuite(c)
307 }
308
309-func (s *JujuConnSuite) TearDownSuite(c *C) {
310+func (s *JujuConnSuite) TearDownSuite(c *gc.C) {
311 s.MgoSuite.TearDownSuite(c)
312 s.LoggingSuite.TearDownSuite(c)
313 }
314
315-func (s *JujuConnSuite) SetUpTest(c *C) {
316+func (s *JujuConnSuite) SetUpTest(c *gc.C) {
317 s.oldJujuHome = config.SetJujuHome(c.MkDir())
318 s.LoggingSuite.SetUpTest(c)
319 s.MgoSuite.SetUpTest(c)
320 s.setUpConn(c)
321 }
322
323-func (s *JujuConnSuite) TearDownTest(c *C) {
324+func (s *JujuConnSuite) TearDownTest(c *gc.C) {
325 s.tearDownConn(c)
326 s.MgoSuite.TearDownTest(c)
327 s.LoggingSuite.TearDownTest(c)
328@@ -145,51 +145,63 @@
329
330 // Reset returns environment state to that which existed at the start of
331 // the test.
332-func (s *JujuConnSuite) Reset(c *C) {
333+func (s *JujuConnSuite) Reset(c *gc.C) {
334 s.tearDownConn(c)
335 s.setUpConn(c)
336 }
337
338-func (s *JujuConnSuite) StateInfo(c *C) *state.Info {
339+func (s *JujuConnSuite) StateInfo(c *gc.C) *state.Info {
340 info, _, err := s.Conn.Environ.StateInfo()
341- c.Assert(err, IsNil)
342+ c.Assert(err, gc.IsNil)
343 info.Password = "dummy-secret"
344 return info
345 }
346
347-func (s *JujuConnSuite) APIInfo(c *C) *api.Info {
348+func (s *JujuConnSuite) APIInfo(c *gc.C) *api.Info {
349 _, apiInfo, err := s.APIConn.Environ.StateInfo()
350- c.Assert(err, IsNil)
351+ c.Assert(err, gc.IsNil)
352 apiInfo.Tag = "user-admin"
353 apiInfo.Password = "dummy-secret"
354 return apiInfo
355 }
356
357-func (s *JujuConnSuite) openAPIAs(c *C, tag, password, nonce string) *api.State {
358+func (s *JujuConnSuite) openAPIAs(c *gc.C, tag, password, nonce string) *api.State {
359 _, info, err := s.APIConn.Environ.StateInfo()
360- c.Assert(err, IsNil)
361+ c.Assert(err, gc.IsNil)
362 info.Tag = tag
363 info.Password = password
364 info.Nonce = nonce
365 st, err := api.Open(info, api.DialOpts{})
366- c.Assert(err, IsNil)
367- c.Assert(st, NotNil)
368+ c.Assert(err, gc.IsNil)
369+ c.Assert(st, gc.NotNil)
370 return st
371 }
372
373 // OpenAPIAs opens the API using the given identity tag
374 // and password for authentication.
375-func (s *JujuConnSuite) OpenAPIAs(c *C, tag, password string) *api.State {
376+func (s *JujuConnSuite) OpenAPIAs(c *gc.C, tag, password string) *api.State {
377 return s.openAPIAs(c, tag, password, "")
378 }
379
380 // OpenAPIAsMachine opens the API using the given machine tag,
381 // password and nonce for authentication.
382-func (s *JujuConnSuite) OpenAPIAsMachine(c *C, tag, password, nonce string) *api.State {
383+func (s *JujuConnSuite) OpenAPIAsMachine(c *gc.C, tag, password, nonce string) *api.State {
384 return s.openAPIAs(c, tag, password, nonce)
385 }
386
387-func (s *JujuConnSuite) setUpConn(c *C) {
388+// OpenAPIAsNewMachine creates a new machine entry that lives in system state, and
389+// then uses that to open the API.
390+func (s *JujuConnSuite) OpenAPIAsNewMachine(c *gc.C) (*api.State, *state.Machine) {
391+ machine, err := s.State.AddMachine("series", state.JobHostUnits)
392+ c.Assert(err, gc.IsNil)
393+ err = machine.SetPassword("test-password")
394+ c.Assert(err, gc.IsNil)
395+ err = machine.SetProvisioned("foo", "fake_nonce", nil)
396+ c.Assert(err, gc.IsNil)
397+ return s.openAPIAs(c, machine.Tag(), "test-password", "fake_nonce"), machine
398+}
399+
400+func (s *JujuConnSuite) setUpConn(c *gc.C) {
401 if s.RootDir != "" {
402 panic("JujuConnSuite.setUpConn without teardown")
403 }
404@@ -197,38 +209,38 @@
405 s.oldHome = os.Getenv("HOME")
406 home := filepath.Join(s.RootDir, "/home/ubuntu")
407 err := os.MkdirAll(home, 0777)
408- c.Assert(err, IsNil)
409+ c.Assert(err, gc.IsNil)
410 os.Setenv("HOME", home)
411
412 dataDir := filepath.Join(s.RootDir, "/var/lib/juju")
413 err = os.MkdirAll(dataDir, 0777)
414- c.Assert(err, IsNil)
415+ c.Assert(err, gc.IsNil)
416
417 yaml := []byte(fmt.Sprintf(envConfig, version.Current.Number))
418 err = ioutil.WriteFile(config.JujuHomePath("environments.yaml"), yaml, 0600)
419- c.Assert(err, IsNil)
420+ c.Assert(err, gc.IsNil)
421
422 err = ioutil.WriteFile(config.JujuHomePath("dummyenv-cert.pem"), []byte(testing.CACert), 0666)
423- c.Assert(err, IsNil)
424+ c.Assert(err, gc.IsNil)
425
426 err = ioutil.WriteFile(config.JujuHomePath("dummyenv-private-key.pem"), []byte(testing.CAKey), 0600)
427- c.Assert(err, IsNil)
428+ c.Assert(err, gc.IsNil)
429
430 environ, err := environs.PrepareFromName("dummyenv")
431- c.Assert(err, IsNil)
432+ c.Assert(err, gc.IsNil)
433 // sanity check we've got the correct environment.
434- c.Assert(environ.Name(), Equals, "dummyenv")
435- c.Assert(bootstrap.Bootstrap(environ, constraints.Value{}), IsNil)
436+ c.Assert(environ.Name(), gc.Equals, "dummyenv")
437+ c.Assert(bootstrap.Bootstrap(environ, constraints.Value{}), gc.IsNil)
438
439 s.BackingState = environ.(GetStater).GetStateInAPIServer()
440
441 conn, err := juju.NewConn(environ)
442- c.Assert(err, IsNil)
443+ c.Assert(err, gc.IsNil)
444 s.Conn = conn
445 s.State = conn.State
446
447 apiConn, err := juju.NewAPIConn(environ, api.DialOpts{})
448- c.Assert(err, IsNil)
449+ c.Assert(err, gc.IsNil)
450 s.APIConn = apiConn
451 s.APIState = apiConn.State
452 s.environ = environ
453@@ -238,7 +250,7 @@
454 GetStateInAPIServer() *state.State
455 }
456
457-func (s *JujuConnSuite) tearDownConn(c *C) {
458+func (s *JujuConnSuite) tearDownConn(c *gc.C) {
459 // Bootstrap will set the admin password, and render non-authorized use
460 // impossible. s.State may still hold the right password, so try to reset
461 // the password so that the MgoSuite soft-resetting works. If that fails,
462@@ -247,8 +259,8 @@
463 if err := s.State.SetAdminMongoPassword(""); err != nil {
464 c.Logf("cannot reset admin password: %v", err)
465 }
466- c.Assert(s.Conn.Close(), IsNil)
467- c.Assert(s.APIConn.Close(), IsNil)
468+ c.Assert(s.Conn.Close(), gc.IsNil)
469+ c.Assert(s.APIConn.Close(), gc.IsNil)
470 dummy.Reset()
471 s.Conn = nil
472 s.State = nil
473@@ -276,13 +288,13 @@
474 }
475 }
476
477-func (s *JujuConnSuite) AddTestingCharm(c *C, name string) *state.Charm {
478+func (s *JujuConnSuite) AddTestingCharm(c *gc.C, name string) *state.Charm {
479 ch := testing.Charms.Dir(name)
480 ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision())
481 curl := charm.MustParseURL("local:series/" + ident)
482 repo, err := charm.InferRepository(curl, testing.Charms.Path)
483- c.Assert(err, IsNil)
484+ c.Assert(err, gc.IsNil)
485 sch, err := s.Conn.PutCharm(curl, repo, false)
486- c.Assert(err, IsNil)
487+ c.Assert(err, gc.IsNil)
488 return sch
489 }
490
491=== modified file 'state/api/apiclient.go'
492--- state/api/apiclient.go 2013-09-11 06:27:05 +0000
493+++ state/api/apiclient.go 2013-09-13 09:26:01 +0000
494@@ -133,11 +133,8 @@
495 }
496
497 func (s *State) heartbeatMonitor() {
498- ping := func() error {
499- return s.Call("Pinger", "", "Ping", nil, nil)
500- }
501 for {
502- if err := ping(); err != nil {
503+ if err := s.Ping(); err != nil {
504 close(s.broken)
505 return
506 }
507@@ -145,6 +142,10 @@
508 }
509 }
510
511+func (s *State) Ping() error {
512+ return s.Call("Pinger", "", "Ping", nil, nil)
513+}
514+
515 // Call invokes a low-level RPC method of the given objType, id, and
516 // request, passing the given parameters and filling in the response
517 // results. This should not be used directly by clients.
518
519=== modified file 'state/apiserver/pinger_test.go'
520--- state/apiserver/pinger_test.go 2013-09-11 06:27:05 +0000
521+++ state/apiserver/pinger_test.go 2013-09-13 09:26:01 +0000
522@@ -6,6 +6,7 @@
523 import (
524 gc "launchpad.net/gocheck"
525 "launchpad.net/juju-core/juju/testing"
526+ "launchpad.net/juju-core/rpc"
527 "launchpad.net/juju-core/state"
528 "launchpad.net/juju-core/state/api"
529 "time"
530@@ -54,3 +55,14 @@
531 return
532 }
533 }
534+
535+func (s *stateSuite) TestPing(c *gc.C) {
536+ st, _ := s.OpenAPIAsNewMachine(c)
537+ defer st.Close()
538+ err := st.Ping()
539+ c.Assert(err, gc.IsNil)
540+ err = st.Close()
541+ c.Assert(err, gc.IsNil)
542+ err = st.Ping()
543+ c.Assert(err, gc.Equals, rpc.ErrShutdown)
544+}
545
546=== modified file 'state/state.go'
547--- state/state.go 2013-09-11 06:27:05 +0000
548+++ state/state.go 2013-09-13 09:26:01 +0000
549@@ -102,6 +102,12 @@
550 return st.runner.Run(ops, "", nil)
551 }
552
553+// Ping probes the state's database connection to ensure
554+// that it is still alive.
555+func (st *State) Ping() error {
556+ return st.db.Session.Ping()
557+}
558+
559 func (st *State) Watch() *multiwatcher.Watcher {
560 st.mu.Lock()
561 if st.allManager == nil {
562
563=== modified file 'state/state_test.go'
564--- state/state_test.go 2013-09-11 06:27:05 +0000
565+++ state/state_test.go 2013-09-13 09:26:01 +0000
566@@ -63,6 +63,12 @@
567 c.Assert(s.State.CACert(), gc.DeepEquals, info.CACert)
568 }
569
570+func (s *StateSuite) TestPing(c *gc.C) {
571+ c.Assert(s.State.Ping(), gc.IsNil)
572+ testing.MgoRestart()
573+ c.Assert(s.State.Ping(), gc.NotNil)
574+}
575+
576 func (s *StateSuite) TestAPIAddresses(c *gc.C) {
577 config, err := s.State.EnvironConfig()
578 c.Assert(err, gc.IsNil)
579
580=== added file 'testing/checkers/set.go'
581--- testing/checkers/set.go 1970-01-01 00:00:00 +0000
582+++ testing/checkers/set.go 2013-09-13 09:26:01 +0000
583@@ -0,0 +1,35 @@
584+package checkers
585+
586+import (
587+ "reflect"
588+)
589+
590+// Restorer holds a function that can be used
591+// to restore some previous state.
592+type Restorer func()
593+
594+// Restore restores some previous state.
595+func (r Restorer) Restore() {
596+ r()
597+}
598+
599+// Set sets the value pointed to by the given
600+// destination to the given value, and returns
601+// a function to restore it to its original value.
602+// The value must be assignable to the element
603+// type of the destination.
604+func Set(dest, value interface{}) Restorer {
605+ destv := reflect.ValueOf(dest).Elem()
606+ oldv := reflect.New(destv.Type()).Elem()
607+ oldv.Set(destv)
608+ valuev := reflect.ValueOf(value)
609+ if !valuev.IsValid() {
610+ // This isn't quite right when the destination type is not
611+ // nilable, but it's better than the complex alternative.
612+ valuev = reflect.Zero(destv.Type())
613+ }
614+ destv.Set(valuev)
615+ return func() {
616+ destv.Set(oldv)
617+ }
618+}
619
620=== added file 'testing/checkers/set_test.go'
621--- testing/checkers/set_test.go 1970-01-01 00:00:00 +0000
622+++ testing/checkers/set_test.go 2013-09-13 09:26:01 +0000
623@@ -0,0 +1,54 @@
624+package checkers_test
625+
626+import (
627+ "errors"
628+
629+ gc "launchpad.net/gocheck"
630+ jc "launchpad.net/juju-core/testing/checkers"
631+)
632+
633+type SetSuite struct{}
634+
635+var _ = gc.Suite(&SetSuite{})
636+
637+func (*SetSuite) TestSetInt(c *gc.C) {
638+ i := 99
639+ restore := jc.Set(&i, 88)
640+ c.Assert(i, gc.Equals, 88)
641+ restore()
642+ c.Assert(i, gc.Equals, 99)
643+}
644+
645+func (*SetSuite) TestSetError(c *gc.C) {
646+ oldErr := errors.New("foo")
647+ newErr := errors.New("bar")
648+ err := oldErr
649+ restore := jc.Set(&err, newErr)
650+ c.Assert(err, gc.Equals, newErr)
651+ restore()
652+ c.Assert(err, gc.Equals, oldErr)
653+}
654+
655+func (*SetSuite) TestSetErrorToNil(c *gc.C) {
656+ oldErr := errors.New("foo")
657+ err := oldErr
658+ restore := jc.Set(&err, nil)
659+ c.Assert(err, gc.Equals, nil)
660+ restore()
661+ c.Assert(err, gc.Equals, oldErr)
662+}
663+
664+func (*SetSuite) TestSetMapToNil(c *gc.C) {
665+ oldMap := map[string]int{"foo": 1234}
666+ m := oldMap
667+ restore := jc.Set(&m, nil)
668+ c.Assert(m, gc.IsNil)
669+ restore()
670+ c.Assert(m, gc.DeepEquals, oldMap)
671+}
672+
673+func (*SetSuite) TestSetPanicsWhenNotAssignable(c *gc.C) {
674+ i := 99
675+ type otherInt int
676+ c.Assert(func() { jc.Set(&i, otherInt(88)) }, gc.PanicMatches, `reflect\.Set: value of type checkers_test\.otherInt is not assignable to type int`)
677+}
678
679=== modified file 'testing/mgo.go'
680--- testing/mgo.go 2013-08-27 07:28:22 +0000
681+++ testing/mgo.go 2013-09-13 09:26:01 +0000
682@@ -57,7 +57,6 @@
683 }
684
685 // startMgoServer starts a MongoDB server in a temporary directory.
686-// It panics if it encounters an error.
687 func startMgoServer() error {
688 dbdir, err := ioutil.TempDir("", "test-mgo")
689 if err != nil {
690@@ -69,12 +68,30 @@
691 return fmt.Errorf("cannot write cert/key PEM: %v", err)
692 }
693 MgoPort = FindTCPPort()
694+ MgoAddr = fmt.Sprintf("localhost:%d", MgoPort)
695+ mgoDir = dbdir
696+ if err := runMgoServer(); err != nil {
697+ MgoAddr = ""
698+ MgoPort = 0
699+ os.RemoveAll(mgoDir)
700+ mgoDir = ""
701+ return err
702+ }
703+ return nil
704+}
705+
706+// runMgoServer runs the MongoDB server at the
707+// address and directory already configured.
708+func runMgoServer() error {
709+ if mgoServer != nil {
710+ panic("mongo server is already running")
711+ }
712 mgoport := strconv.Itoa(MgoPort)
713 mgoargs := []string{
714 "--auth",
715- "--dbpath", dbdir,
716+ "--dbpath", mgoDir,
717 "--sslOnNormalPorts",
718- "--sslPEMKeyFile", pemPath,
719+ "--sslPEMKeyFile", filepath.Join(mgoDir, "server.pem"),
720 "--sslPEMKeyPassword", "ignored",
721 "--bind_ip", "localhost",
722 "--port", mgoport,
723@@ -106,21 +123,33 @@
724 }()
725 mgoExited = exited
726 if err := server.Start(); err != nil {
727- os.RemoveAll(dbdir)
728 return err
729 }
730- MgoAddr = "localhost:" + mgoport
731 mgoServer = server
732- mgoDir = dbdir
733 return nil
734 }
735
736+func mgoKill() {
737+ mgoServer.Process.Kill()
738+ <-mgoExited
739+ mgoServer = nil
740+ mgoExited = nil
741+}
742+
743 func destroyMgoServer() {
744 if mgoServer != nil {
745- mgoServer.Process.Kill()
746- <-mgoExited
747+ mgoKill()
748 os.RemoveAll(mgoDir)
749- MgoAddr, mgoServer, mgoExited, mgoDir = "", nil, nil, ""
750+ MgoAddr, mgoDir = "", ""
751+ }
752+}
753+
754+// MgoRestart restarts the mongo server, useful for
755+// testing what happens when a state server goes down.
756+func MgoRestart() {
757+ mgoKill()
758+ if err := startMgoServer(); err != nil {
759+ panic(err)
760 }
761 }
762

Subscribers

People subscribed via source and target branches

to all changes: