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
1=== modified file 'cmd/jujud/machine.go'
2--- cmd/jujud/machine.go 2013-07-08 18:40:36 +0000
3+++ cmd/jujud/machine.go 2013-07-11 12:18:26 +0000
4@@ -16,9 +16,11 @@
5 "launchpad.net/juju-core/state/api/params"
6 "launchpad.net/juju-core/state/apiserver"
7 "launchpad.net/juju-core/worker"
8+ "launchpad.net/juju-core/worker/cleaner"
9 "launchpad.net/juju-core/worker/firewaller"
10 "launchpad.net/juju-core/worker/machiner"
11 "launchpad.net/juju-core/worker/provisioner"
12+ "launchpad.net/juju-core/worker/resumer"
13 "launchpad.net/tomb"
14 "path/filepath"
15 "time"
16@@ -154,6 +156,7 @@
17 if err != nil {
18 return nil, err
19 }
20+ reportOpenedState(st)
21 m := entity.(*state.Machine)
22 // TODO(rog) use more discriminating test for errors
23 // rather than taking everything down indiscriminately.
24@@ -206,6 +209,15 @@
25 }
26 return apiserver.NewServer(st, fmt.Sprintf(":%d", a.Conf.APIPort), a.Conf.StateServerCert, a.Conf.StateServerKey)
27 })
28+ runner.StartWorker("cleaner", func() (worker.Worker, error) {
29+ return cleaner.NewCleaner(st), nil
30+ })
31+ runner.StartWorker("resumer", func() (worker.Worker, error) {
32+ // The action of resumer is so subtle that it is not tested,
33+ // because we can't figure out how to do so without brutalising
34+ // the transaction log.
35+ return resumer.NewResumer(st), nil
36+ })
37 default:
38 log.Warningf("ignoring unknown job %q", job)
39 }
40@@ -241,3 +253,22 @@
41 func (a *MachineAgent) Tag() string {
42 return state.MachineTag(a.MachineId)
43 }
44+
45+// Below pieces are used for testing,to give us access to the *State opened
46+// by the agent, and allow us to trigger syncs without waiting 5s for them
47+// to happen automatically.
48+
49+var stateReporter chan<- *state.State
50+
51+func reportOpenedState(st *state.State) {
52+ select {
53+ case stateReporter <- st:
54+ default:
55+ }
56+}
57+
58+func sendOpenedStates(dst chan<- *state.State) (undo func()) {
59+ var original chan<- *state.State
60+ original, stateReporter = stateReporter, dst
61+ return func() { stateReporter = original }
62+}
63
64=== modified file 'cmd/jujud/machine_test.go'
65--- cmd/jujud/machine_test.go 2013-07-09 11:31:00 +0000
66+++ cmd/jujud/machine_test.go 2013-07-11 12:18:26 +0000
67@@ -307,35 +307,44 @@
68 RetryDelay: 10 * time.Millisecond,
69 }
70
71-func (s *MachineSuite) TestServeAPI(c *C) {
72- stm, conf, _ := s.primeAgent(c, state.JobManageState)
73+func (s *MachineSuite) assertJobWithState(c *C, job state.MachineJob, test func(*agent.Conf, *state.State)) {
74+ stm, conf, _ := s.primeAgent(c, job)
75 addAPIInfo(conf, stm)
76 err := conf.Write()
77 c.Assert(err, IsNil)
78 a := s.newAgent(c, stm)
79+ defer a.Stop()
80+
81+ agentStates := make(chan *state.State, 1000)
82+ undo := sendOpenedStates(agentStates)
83+ defer undo()
84+
85 done := make(chan error)
86 go func() {
87 done <- a.Run(nil)
88 }()
89
90- st, err := api.Open(conf.APIInfo, fastDialOpts)
91- c.Assert(err, IsNil)
92- defer st.Close()
93-
94- // This just verifies we can log in successfully.
95- m, err := st.Machiner().Machine(stm.Tag())
96- c.Assert(err, IsNil)
97- c.Assert(m.Life(), Equals, params.Alive)
98+ select {
99+ case agentState := <-agentStates:
100+ c.Assert(agentState, NotNil)
101+ test(conf, agentState)
102+ case <-time.After(testing.LongWait):
103+ c.Fatalf("state not opened")
104+ }
105
106 err = a.Stop()
107- // When shutting down, the API server can be shut down before
108- // the other workers that connect to it, so they get an error so
109- // they then die, causing Stop to return an error. It's not
110- // easy to control the actual error that's received in this
111- // circumstance so we just log it rather than asserting that it
112- // is not nil.
113- if err != nil {
114- c.Logf("error shutting down: %v", err)
115+ if job == state.JobManageState {
116+ // When shutting down, the API server can be shut down before
117+ // the other workers that connect to it, so they get an error so
118+ // they then die, causing Stop to return an error. It's not
119+ // easy to control the actual error that's received in this
120+ // circumstance so we just log it rather than asserting that it
121+ // is not nil.
122+ if err != nil {
123+ c.Logf("error shutting down state manager: %v", err)
124+ }
125+ } else {
126+ c.Assert(err, IsNil)
127 }
128
129 select {
130@@ -346,6 +355,55 @@
131 }
132 }
133
134+func (s *MachineSuite) TestManageStateServesAPI(c *C) {
135+ s.assertJobWithState(c, state.JobManageState, func(conf *agent.Conf, agentState *state.State) {
136+ st, err := api.Open(conf.APIInfo, fastDialOpts)
137+ c.Assert(err, IsNil)
138+ defer st.Close()
139+ m, err := st.Machiner().Machine(conf.APIInfo.Tag)
140+ c.Assert(err, IsNil)
141+ c.Assert(m.Life(), Equals, params.Alive)
142+ })
143+}
144+
145+func (s *MachineSuite) TestManageStateRunsCleaner(c *C) {
146+ s.assertJobWithState(c, state.JobManageState, func(conf *agent.Conf, agentState *state.State) {
147+ // Create a service and unit, and destroy the service.
148+ service, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress"))
149+ c.Assert(err, IsNil)
150+ unit, err := service.AddUnit()
151+ c.Assert(err, IsNil)
152+ err = service.Destroy()
153+ c.Assert(err, IsNil)
154+
155+ // Check the unit was not yet removed.
156+ err = unit.Refresh()
157+ c.Assert(err, IsNil)
158+ w := unit.Watch()
159+ defer w.Stop()
160+
161+ // Trigger a sync on the state used by the agent, and wait
162+ // for the unit to be removed.
163+ agentState.Sync()
164+ timeout := time.After(testing.LongWait)
165+ for done := false; !done; {
166+ select {
167+ case <-timeout:
168+ c.Fatalf("unit not cleaned up")
169+ case <-time.After(testing.ShortWait):
170+ s.State.StartSync()
171+ case <-w.Changes():
172+ err := unit.Refresh()
173+ if errors.IsNotFoundError(err) {
174+ done = true
175+ } else {
176+ c.Assert(err, IsNil)
177+ }
178+ }
179+ }
180+ })
181+}
182+
183 var serveAPIWithBadConfTests = []struct {
184 change func(c *agent.Conf)
185 err string

Subscribers

People subscribed via source and target branches

to status/vote changes: