Merge lp:~fwereade/juju-core/cleaner-resumer-integrate into lp:~go-bot/juju-core/trunk

Proposed by William Reade
Status: Merged
Approved by: William Reade
Approved revision: no longer in the source branch.
Merged at revision: 1434
Proposed branch: lp:~fwereade/juju-core/cleaner-resumer-integrate
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 185 lines (+107/-18)
2 files modified
cmd/jujud/machine.go (+31/-0)
cmd/jujud/machine_test.go (+76/-18)
To merge this branch: bzr merge lp:~fwereade/juju-core/cleaner-resumer-integrate
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+174013@code.launchpad.net

Commit message

jujud: integrate cleaner, resumer

the stateReporter chan may be useful elsewhere, but I haven't trawled for
further simplifications. Main point is integrating these tasks and testing wat
we can.

https://codereview.appspot.com/10825044/

Description of the change

jujud: integrate cleaner, resumer

the stateReporter chan may be useful elsewhere, but I haven't trawled for
further simplifications. Main point is integrating these tasks and testing wat
we can.

https://codereview.appspot.com/10825044/

To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

Reviewers: mp+174013_code.launchpad.net,

Message:
Please take a look.

Description:
jujud: integrate cleaner, resumer

the stateReporter chan may be useful elsewhere, but I haven't trawled
for
further simplifications. Main point is integrating these tasks and
testing wat
we can.

https://code.launchpad.net/~fwereade/juju-core/cleaner-resumer-integrate/+merge/174013

(do not edit description out of merge proposal)

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

Affected files:
   A [revision details]
   M cmd/jujud/machine.go
   M cmd/jujud/machine_test.go

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

LGTM, only a couple of clarification requests.

https://codereview.appspot.com/10825044/diff/1/cmd/jujud/machine_test.go
File cmd/jujud/machine_test.go (right):

https://codereview.appspot.com/10825044/diff/1/cmd/jujud/machine_test.go#newcode318
cmd/jujud/machine_test.go:318: agentStates := make(chan *state.State,
1000)
why 1000? why not unbuffered?

https://codereview.appspot.com/10825044/diff/1/cmd/jujud/machine_test.go#newcode319
cmd/jujud/machine_test.go:319: defer
sendOpenedStates(sendOpenedStates(agentStates))
this a but confusing - perhaps a comment before this whole block
explaining what's being done will help

https://codereview.appspot.com/10825044/

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (4.8 KiB)

LGTM

I have some comments, but I can live with the code as it is written. So
if having a helper is too much distraction, just land it.

https://codereview.appspot.com/10825044/diff/1/cmd/jujud/machine_test.go
File cmd/jujud/machine_test.go (right):

https://codereview.appspot.com/10825044/diff/1/cmd/jujud/machine_test.go#newcode318
cmd/jujud/machine_test.go:318: agentStates := make(chan *state.State,
1000)
On 2013/07/10 18:28:46, dimitern wrote:
> why 1000? why not unbuffered?

I don't think we want unbuffered, because then when the other loop
starts up it will block until we do our select (I think).
The trick is that once you call 'sendOpenedStates' *all* things that
start up are going to be trying to send their state to this channel.

The general idea is: "I'm going to start something up, and I want a
backdoor into the State object it is using, so send it onto this channel
so I can pull it out in this code."

However, once you set the channel, more things are going to be sent on
it than you really need. We could just create a local goroutine that
consumes them all and puts it somewhere we can use it.

https://codereview.appspot.com/10825044/diff/1/cmd/jujud/machine_test.go#newcode319
cmd/jujud/machine_test.go:319: defer
sendOpenedStates(sendOpenedStates(agentStates))
On 2013/07/10 18:28:46, dimitern wrote:
> this a but confusing - perhaps a comment before this whole block
explaining
> what's being done will help

A slightly better pattern is probably:

oldChan := sendOpenedStates(agentStates)
defer sendOpenedStates(oldChan)

For things like this, I usually prefer to have the sendOpenedStates type
function actually return a func() that does the cleanup. So the code
becomes:

func sendOpenedStates(ch <-chan *state.State) {
   old, agentStates := agentStates, ch
   return func() { agentStates := old) }
}

As then the code in the test case is:

cleanup := sendOpenedStates(agentStates)
defer cleanup()

And that is a common pattern regardless of what internal poking a given
test helper has to do.

Nested function calls and defer are hard to parse for mere mortals,
which is why I don't spell it as:

defer sendOpenedStates(agentStates)()
     ^------------------------------^
The indicator of what you are deferring is very far from 'defer' vs

cleanup := ...
defer cleanup()

It is also why I'm not 100% sold on the:

go func() {
...
...
...
...
...
..
...
.
.
.
.
..
}()

Syntax. At least it is "go func() {" which makes it clear you are
thinking about calling func at the end of this. But when you leave off
those last two parenthesis it gets pretty confusing. :)

https://codereview.appspot.com/10825044/diff/1/cmd/jujud/machine_test.go#newcode329
cmd/jujud/machine_test.go:329: case <-time.After(500 *
time.Millisecond):
Please use the testing.LongWait constants. It will help signal *why* you
are picking a given timeout, and lets us tweak them across the codebase
if we find they are too long/short.

https://codereview.appspot.com/10825044/diff/1/cmd/jujud/machine_test.go#newcode386
cmd/jujud/machine_test.go:386: timeout := time.After(500 *
time.Millisecond)
Same here on the timeout.

This loop has been used elsewhere, but I'm not quite sure why we hav...

Read more...

Revision history for this message
William Reade (fwereade) wrote :
Download full text (5.9 KiB)

https://codereview.appspot.com/10825044/diff/1/cmd/jujud/machine_test.go
File cmd/jujud/machine_test.go (right):

https://codereview.appspot.com/10825044/diff/1/cmd/jujud/machine_test.go#newcode318
cmd/jujud/machine_test.go:318: agentStates := make(chan *state.State,
1000)
On 2013/07/11 05:50:16, jameinel wrote:
> On 2013/07/10 18:28:46, dimitern wrote:
> > why 1000? why not unbuffered?

> I don't think we want unbuffered, because then when the other loop
starts up it
> will block until we do our select (I think).
> The trick is that once you call 'sendOpenedStates' *all* things that
start up
> are going to be trying to send their state to this channel.

> The general idea is: "I'm going to start something up, and I want a
backdoor
> into the State object it is using, so send it onto this channel so I
can pull it
> out in this code."

> However, once you set the channel, more things are going to be sent on
it than
> you really need. We could just create a local goroutine that consumes
them all
> and puts it somewhere we can use it.

In practice, only one state should be sent, because we're not expecting
errors and restarts. But if we get them, that buffer should be big
enough to ensure nothing blocks. That's all :). I don't *think* it's
worth the extra infrastructure to check that no more come in; if things
are broken I think we'll detect it in other ways.

https://codereview.appspot.com/10825044/diff/1/cmd/jujud/machine_test.go#newcode319
cmd/jujud/machine_test.go:319: defer
sendOpenedStates(sendOpenedStates(agentStates))
On 2013/07/11 05:50:16, jameinel wrote:
> On 2013/07/10 18:28:46, dimitern wrote:
> > this a but confusing - perhaps a comment before this whole block
explaining
> > what's being done will help

> A slightly better pattern is probably:

> oldChan := sendOpenedStates(agentStates)
> defer sendOpenedStates(oldChan)

> For things like this, I usually prefer to have the sendOpenedStates
type
> function actually return a func() that does the cleanup. So the code
becomes:

> func sendOpenedStates(ch <-chan *state.State) {
> old, agentStates := agentStates, ch
> return func() { agentStates := old) }
> }

> As then the code in the test case is:

> cleanup := sendOpenedStates(agentStates)
> defer cleanup()

> And that is a common pattern regardless of what internal poking a
given test
> helper has to do.

> Nested function calls and defer are hard to parse for mere mortals,
which is why
> I don't spell it as:

> defer sendOpenedStates(agentStates)()
> ^------------------------------^
> The indicator of what you are deferring is very far from 'defer' vs

> cleanup := ...
> defer cleanup()

> It is also why I'm not 100% sold on the:

> go func() {
> ...
> ...
> ...
> ...
> ...
> ..
> ...
> .
> .
> .
> .
> ..
> }()

> Syntax. At least it is "go func() {" which makes it clear you are
thinking about
> calling func at the end of this. But when you leave off those last two
> parenthesis it gets pretty confusing. :)

Done as undo func.

https://codereview.appspot.com/10825044/diff/1/cmd/jujud/machine_test.go#newcode329
cmd/jujud/machine_test.go:329: case <-time.After(500 *
time.Millisecond):
On 2013/07/11 05:50:16, jameinel wrot...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cmd/jujud/machine.go'
--- cmd/jujud/machine.go 2013-07-08 18:40:36 +0000
+++ cmd/jujud/machine.go 2013-07-11 12:18:26 +0000
@@ -16,9 +16,11 @@
16 "launchpad.net/juju-core/state/api/params"16 "launchpad.net/juju-core/state/api/params"
17 "launchpad.net/juju-core/state/apiserver"17 "launchpad.net/juju-core/state/apiserver"
18 "launchpad.net/juju-core/worker"18 "launchpad.net/juju-core/worker"
19 "launchpad.net/juju-core/worker/cleaner"
19 "launchpad.net/juju-core/worker/firewaller"20 "launchpad.net/juju-core/worker/firewaller"
20 "launchpad.net/juju-core/worker/machiner"21 "launchpad.net/juju-core/worker/machiner"
21 "launchpad.net/juju-core/worker/provisioner"22 "launchpad.net/juju-core/worker/provisioner"
23 "launchpad.net/juju-core/worker/resumer"
22 "launchpad.net/tomb"24 "launchpad.net/tomb"
23 "path/filepath"25 "path/filepath"
24 "time"26 "time"
@@ -154,6 +156,7 @@
154 if err != nil {156 if err != nil {
155 return nil, err157 return nil, err
156 }158 }
159 reportOpenedState(st)
157 m := entity.(*state.Machine)160 m := entity.(*state.Machine)
158 // TODO(rog) use more discriminating test for errors161 // TODO(rog) use more discriminating test for errors
159 // rather than taking everything down indiscriminately.162 // rather than taking everything down indiscriminately.
@@ -206,6 +209,15 @@
206 }209 }
207 return apiserver.NewServer(st, fmt.Sprintf(":%d", a.Conf.APIPort), a.Conf.StateServerCert, a.Conf.StateServerKey)210 return apiserver.NewServer(st, fmt.Sprintf(":%d", a.Conf.APIPort), a.Conf.StateServerCert, a.Conf.StateServerKey)
208 })211 })
212 runner.StartWorker("cleaner", func() (worker.Worker, error) {
213 return cleaner.NewCleaner(st), nil
214 })
215 runner.StartWorker("resumer", func() (worker.Worker, error) {
216 // The action of resumer is so subtle that it is not tested,
217 // because we can't figure out how to do so without brutalising
218 // the transaction log.
219 return resumer.NewResumer(st), nil
220 })
209 default:221 default:
210 log.Warningf("ignoring unknown job %q", job)222 log.Warningf("ignoring unknown job %q", job)
211 }223 }
@@ -241,3 +253,22 @@
241func (a *MachineAgent) Tag() string {253func (a *MachineAgent) Tag() string {
242 return state.MachineTag(a.MachineId)254 return state.MachineTag(a.MachineId)
243}255}
256
257// Below pieces are used for testing,to give us access to the *State opened
258// by the agent, and allow us to trigger syncs without waiting 5s for them
259// to happen automatically.
260
261var stateReporter chan<- *state.State
262
263func reportOpenedState(st *state.State) {
264 select {
265 case stateReporter <- st:
266 default:
267 }
268}
269
270func sendOpenedStates(dst chan<- *state.State) (undo func()) {
271 var original chan<- *state.State
272 original, stateReporter = stateReporter, dst
273 return func() { stateReporter = original }
274}
244275
=== modified file 'cmd/jujud/machine_test.go'
--- cmd/jujud/machine_test.go 2013-07-09 11:31:00 +0000
+++ cmd/jujud/machine_test.go 2013-07-11 12:18:26 +0000
@@ -307,35 +307,44 @@
307 RetryDelay: 10 * time.Millisecond,307 RetryDelay: 10 * time.Millisecond,
308}308}
309309
310func (s *MachineSuite) TestServeAPI(c *C) {310func (s *MachineSuite) assertJobWithState(c *C, job state.MachineJob, test func(*agent.Conf, *state.State)) {
311 stm, conf, _ := s.primeAgent(c, state.JobManageState)311 stm, conf, _ := s.primeAgent(c, job)
312 addAPIInfo(conf, stm)312 addAPIInfo(conf, stm)
313 err := conf.Write()313 err := conf.Write()
314 c.Assert(err, IsNil)314 c.Assert(err, IsNil)
315 a := s.newAgent(c, stm)315 a := s.newAgent(c, stm)
316 defer a.Stop()
317
318 agentStates := make(chan *state.State, 1000)
319 undo := sendOpenedStates(agentStates)
320 defer undo()
321
316 done := make(chan error)322 done := make(chan error)
317 go func() {323 go func() {
318 done <- a.Run(nil)324 done <- a.Run(nil)
319 }()325 }()
320326
321 st, err := api.Open(conf.APIInfo, fastDialOpts)327 select {
322 c.Assert(err, IsNil)328 case agentState := <-agentStates:
323 defer st.Close()329 c.Assert(agentState, NotNil)
324330 test(conf, agentState)
325 // This just verifies we can log in successfully.331 case <-time.After(testing.LongWait):
326 m, err := st.Machiner().Machine(stm.Tag())332 c.Fatalf("state not opened")
327 c.Assert(err, IsNil)333 }
328 c.Assert(m.Life(), Equals, params.Alive)
329334
330 err = a.Stop()335 err = a.Stop()
331 // When shutting down, the API server can be shut down before336 if job == state.JobManageState {
332 // the other workers that connect to it, so they get an error so337 // When shutting down, the API server can be shut down before
333 // they then die, causing Stop to return an error. It's not338 // the other workers that connect to it, so they get an error so
334 // easy to control the actual error that's received in this339 // they then die, causing Stop to return an error. It's not
335 // circumstance so we just log it rather than asserting that it340 // easy to control the actual error that's received in this
336 // is not nil.341 // circumstance so we just log it rather than asserting that it
337 if err != nil {342 // is not nil.
338 c.Logf("error shutting down: %v", err)343 if err != nil {
344 c.Logf("error shutting down state manager: %v", err)
345 }
346 } else {
347 c.Assert(err, IsNil)
339 }348 }
340349
341 select {350 select {
@@ -346,6 +355,55 @@
346 }355 }
347}356}
348357
358func (s *MachineSuite) TestManageStateServesAPI(c *C) {
359 s.assertJobWithState(c, state.JobManageState, func(conf *agent.Conf, agentState *state.State) {
360 st, err := api.Open(conf.APIInfo, fastDialOpts)
361 c.Assert(err, IsNil)
362 defer st.Close()
363 m, err := st.Machiner().Machine(conf.APIInfo.Tag)
364 c.Assert(err, IsNil)
365 c.Assert(m.Life(), Equals, params.Alive)
366 })
367}
368
369func (s *MachineSuite) TestManageStateRunsCleaner(c *C) {
370 s.assertJobWithState(c, state.JobManageState, func(conf *agent.Conf, agentState *state.State) {
371 // Create a service and unit, and destroy the service.
372 service, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress"))
373 c.Assert(err, IsNil)
374 unit, err := service.AddUnit()
375 c.Assert(err, IsNil)
376 err = service.Destroy()
377 c.Assert(err, IsNil)
378
379 // Check the unit was not yet removed.
380 err = unit.Refresh()
381 c.Assert(err, IsNil)
382 w := unit.Watch()
383 defer w.Stop()
384
385 // Trigger a sync on the state used by the agent, and wait
386 // for the unit to be removed.
387 agentState.Sync()
388 timeout := time.After(testing.LongWait)
389 for done := false; !done; {
390 select {
391 case <-timeout:
392 c.Fatalf("unit not cleaned up")
393 case <-time.After(testing.ShortWait):
394 s.State.StartSync()
395 case <-w.Changes():
396 err := unit.Refresh()
397 if errors.IsNotFoundError(err) {
398 done = true
399 } else {
400 c.Assert(err, IsNil)
401 }
402 }
403 }
404 })
405}
406
349var serveAPIWithBadConfTests = []struct {407var serveAPIWithBadConfTests = []struct {
350 change func(c *agent.Conf)408 change func(c *agent.Conf)
351 err string409 err string

Subscribers

People subscribed via source and target branches

to status/vote changes: