Merge lp:~themue/pyjuju/go-state-unit-resolved-watcher into lp:pyjuju/go

Proposed by Frank Mueller
Status: Rejected
Rejected by: Gustavo Niemeyer
Proposed branch: lp:~themue/pyjuju/go-state-unit-resolved-watcher
Merge into: lp:pyjuju/go
Diff against target: 453 lines (+256/-52)
3 files modified
state/state_test.go (+37/-19)
state/unit.go (+165/-26)
state/watch_test.go (+54/-7)
To merge this branch: bzr merge lp:~themue/pyjuju/go-state-unit-resolved-watcher
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+102497@code.launchpad.net

Description of the change

Added ResolvedWatcher for Units.

It reads the resolved mode out of the node content if
it's created or changed.

https://codereview.appspot.com/6059047/

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

This needs a pre-req branch when you're proposing. Look at the diffs.
It's piling up every single branch you've sent before.

https://codereview.appspot.com/6059047/

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

Looks generally sensible, only showstopper is upgrade node content.

This isn't your fault -- I don't remember seeing this change discussed
on the list -- but it needs to be done all the same.

https://codereview.appspot.com/6059047/diff/1/state/state.go
File state/state.go (right):

https://codereview.appspot.com/6059047/diff/1/state/state.go#newcode292
state/state.go:292: // A non-existing node is treaten as an empty node.
s/non-existing/nonexistent/
s/treaten/treated/

(I actually rather like "treaten", but... ;))

https://codereview.appspot.com/6059047/diff/1/state/unit.go
File state/unit.go (left):

https://codereview.appspot.com/6059047/diff/1/state/unit.go#oldcode453
state/unit.go:453:
This file is getting a bit unwieldy IMO. Could we move the Watcher types
into their own file?

https://codereview.appspot.com/6059047/diff/1/state/unit.go
File state/unit.go (right):

https://codereview.appspot.com/6059047/diff/1/state/unit.go#newcode287
state/unit.go:287: if err := validResolvedMode(mode, false); err != nil
{
I *think* acceptNone should be true here, shouldn't it?

https://codereview.appspot.com/6059047/diff/1/state/unit.go#newcode469
state/unit.go:469: // parseResolveMode parses a given YAML for the
resolve mode
s/parseResolveMode/parseResolvedMode/

https://codereview.appspot.com/6059047/diff/1/state/unit.go#newcode472
state/unit.go:472: setting := &struct{ Retry ResolvedMode }{}
I have a slight preference for a non-anonymous struct
(ResolvedNodeContent, or something) here... we need to set the content
in the tests, and we will also need to do so from the CLI, and I'd
rather have a reasonable certainty that everything hitting this node is
using the same mechanism to do so.

https://codereview.appspot.com/6059047/diff/1/state/unit.go#newcode538
state/unit.go:538: func (w *NeedsUpgradeWatcher) loop() {
Maybe "run" rather than "loop"?

https://codereview.appspot.com/6059047/diff/1/state/unit.go#newcode552
state/unit.go:552: case w.changeChan <- change.Exists:
Existence isn't enough; there's a "force" upgrade mode now as well
(meaning "upgrade even if not actually running"), and we'll need to
handle that for feature parity.

https://codereview.appspot.com/6059047/diff/1/state/unit.go#newcode600
state/unit.go:600: func (w *ResolvedWatcher) loop() {
As above, I prefer "run"; ymmv.

https://codereview.appspot.com/6059047/diff/1/state/watch_test.go
File state/watch_test.go (right):

https://codereview.appspot.com/6059047/diff/1/state/watch_test.go#newcode17
state/watch_test.go:17: return nil, false, false
How can we hit this path?

https://codereview.appspot.com/6059047/diff/1/state/watch_test.go#newcode77
state/watch_test.go:77: case <-time.After(200 * time.Millisecond):
Why 200ms here and 100ms above?

https://codereview.appspot.com/6059047/diff/1/state/watch_test.go#newcode119
state/watch_test.go:119: go func() {
Given that the events *can* coalesce, even if it's not very likely in
this instance, I think it would be better to interleave this stuff with
the tests.

https://codereview.appspot.com/6059047/diff/1/state/watch_test.go#newcode164
state/watch_test.go:164: go func() {
As above.

https://codereview.appspot.com/6059047/diff/1/state/...

Read more...

Revision history for this message
Roger Peppe (rogpeppe) wrote :

https://codereview.appspot.com/6059047/diff/1/state/unit.go
File state/unit.go (right):

https://codereview.appspot.com/6059047/diff/1/state/unit.go#newcode472
state/unit.go:472: setting := &struct{ Retry ResolvedMode }{}
On 2012/04/19 08:29:50, fwereade wrote:
> I have a slight preference for a non-anonymous struct
(ResolvedNodeContent, or
> something) here... we need to set the content in the tests, and we
will also
> need to do so from the CLI, and I'd rather have a reasonable certainty
that
> everything hitting this node is using the same mechanism to do so.

orthogonal to this, declaring as a bare variable and using & in the
Unmarshal would read better IMHO,
var setting struct {
     Retry ResolvedMode
}
if err := goyaml.Unmarshal([]byte(yaml), &setting); err != nil {

https://codereview.appspot.com/6059047/diff/1/state/unit.go#newcode538
state/unit.go:538: func (w *NeedsUpgradeWatcher) loop() {
On 2012/04/19 08:29:50, fwereade wrote:
> Maybe "run" rather than "loop"?

+1 ("loop" implies a particular implementation strategy which may not be
used).

https://codereview.appspot.com/6059047/diff/1/state/watch_test.go
File state/watch_test.go (right):

https://codereview.appspot.com/6059047/diff/1/state/watch_test.go#newcode17
state/watch_test.go:17: return nil, false, false
On 2012/04/19 08:29:50, fwereade wrote:
> How can we hit this path?

+1.
panic("not reached")

https://codereview.appspot.com/6059047/diff/1/state/watch_test.go#newcode119
state/watch_test.go:119: go func() {
On 2012/04/19 08:29:50, fwereade wrote:
> Given that the events *can* coalesce, even if it's not very likely in
this
> instance, I think it would be better to interleave this stuff with the
tests.

+1. especially as it makes the tests run slightly faster.

https://codereview.appspot.com/6059047/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Still waiting for the pre-req to be sorted out, but a few
comments-on-comments.

https://codereview.appspot.com/6059047/diff/1/state/unit.go
File state/unit.go (right):

https://codereview.appspot.com/6059047/diff/1/state/unit.go#newcode472
state/unit.go:472: setting := &struct{ Retry ResolvedMode }{}
On 2012/04/19 08:29:50, fwereade wrote:
> I have a slight preference for a non-anonymous struct
(ResolvedNodeContent, or
> something) here... we need to set the content in the tests, and we
will also
> need to do so from the CLI, and I'd rather have a reasonable certainty
that
> everything hitting this node is using the same mechanism to do so.

-1. The API from this package isn't about ZooKeeper nodes. It's about
services and machines.

https://codereview.appspot.com/6059047/diff/1/state/unit.go#newcode538
state/unit.go:538: func (w *NeedsUpgradeWatcher) loop() {
On 2012/04/19 08:58:49, rog wrote:
> On 2012/04/19 08:29:50, fwereade wrote:
> > Maybe "run" rather than "loop"?

> +1 ("loop" implies a particular implementation strategy which may not
be used).

-1. This *is* a loop, and this isn't part of the API, which means it
*is* the implementation as well. I don't see the point.

https://codereview.appspot.com/6059047/

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

https://codereview.appspot.com/6059047/diff/1/state/unit.go
File state/unit.go (right):

https://codereview.appspot.com/6059047/diff/1/state/unit.go#newcode472
state/unit.go:472: setting := &struct{ Retry ResolvedMode }{}
On 2012/04/19 09:11:55, niemeyer wrote:
> On 2012/04/19 08:29:50, fwereade wrote:
> > I have a slight preference for a non-anonymous struct
(ResolvedNodeContent, or
> > something) here... we need to set the content in the tests, and we
will also
> > need to do so from the CLI, and I'd rather have a reasonable
certainty that
> > everything hitting this node is using the same mechanism to do so.

> -1. The API from this package isn't about ZooKeeper nodes. It's about
services
> and machines.

It doesn't have to be public; how would you feel about
"resolvedNodeContent" instead? I'm really just trying to extend the
benefits of static typing to node content...

https://codereview.appspot.com/6059047/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Frank, please have a look at the CL. It *still* contains unrelated
changes that are not supposed to be reviewed here.

https://codereview.appspot.com/6059047/diff/1/state/state.go
File state/state.go (right):

https://codereview.appspot.com/6059047/diff/1/state/state.go#newcode292
state/state.go:292: // A non-existing node is treaten as an empty node.
On 2012/04/19 08:29:50, fwereade wrote:
> s/non-existing/nonexistent/

The correct form is actually non-existent, AFAIK:

http://oxforddictionaries.com/definition/non-existent

https://codereview.appspot.com/6059047/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

On 2012/04/24 10:01:51, TheMue wrote:
> A review comment of William for this proposal has been that the
handling of
> "needs upgrade" is outdated due to a change of the Python code. Do I
understand
> you right that you now want me to roll the changes back to the old
> implementation and create an extra change for it?

Oh, so an unrelated change to the needs-upgrade functionality was merged
in purposefully?

This is still the same topic we talked about back at the Rally in
Budapest: changes should be self-contained. Fixing the NeedsUpgrades
behavior is definitely not part of what this CL is supposed to be
covering. From its summary:

"""
Added ResolvedWatcher for Units.

It reads the resolved mode out of the node content if
it's created or changed.
"""

If you get a review comment from someone suggesting unrelated changes
and you agree with them, just put that in another branch and move on.

https://codereview.appspot.com/6059047/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

On 2012/04/24 12:10:21, TheMue wrote:
> Understood, I will try to be more thoughtful in future. So far to me
the whole
> watcher topic has been one task.

> How about this one here? I would like you to review this time both
changes at
> once as a final exception.

Sorry, but that was the same thing you said back in Budapest in that
same circumstance. It should be trivial to revert this, given that you
just added revisions on top of the original branch. Just follow
something like this:

bzr log # find the revision you started adding unrelated changes
cd ..
mv original-branch new-work
bzr branch -r <revision> new-work original-branch
cd original-branch
lbox propose

If done right, the above procedure should revert the changes to this CL
in a painless way.

https://codereview.appspot.com/6059047/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Unmerged revisions

118. By Frank Mueller

Merged missing parent changes.

117. By Frank Mueller

Done review changes after rollback and concentrate on the resolve watcher.

116. By Frank Mueller

Added ResolvedWatcher for Units.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'state/state_test.go'
2--- state/state_test.go 2012-03-26 12:47:37 +0000
3+++ state/state_test.go 2012-04-24 14:35:46 +0000
4@@ -778,7 +778,7 @@
5 c.Assert(err, ErrorMatches, "no unused machine found")
6 }
7
8-func (s *StateSuite) TestGetSetClearUnitUpgrate(c *C) {
9+func (s *StateSuite) TestGetSetClearUnitUpgrade(c *C) {
10 // Check that setting and clearing an upgrade flag on a unit works.
11 dummy, _ := addDummyCharm(c, s.st)
12 wordpress, err := s.st.AddService("wordpress", dummy)
13@@ -786,38 +786,56 @@
14 unit, err := wordpress.AddUnit()
15 c.Assert(err, IsNil)
16
17- // Defaults to false.
18- upgrade, err := unit.NeedsUpgrade()
19+ // Defaults to false and false.
20+ needsUpgrade, err := unit.NeedsUpgrade()
21 c.Assert(err, IsNil)
22- c.Assert(upgrade, Equals, false)
23+ c.Assert(needsUpgrade, DeepEquals, &state.NeedsUpgrade{false, false})
24
25 // Can be set.
26- err = unit.SetNeedsUpgrade()
27- c.Assert(err, IsNil)
28- upgrade, err = unit.NeedsUpgrade()
29- c.Assert(err, IsNil)
30- c.Assert(upgrade, Equals, true)
31+ err = unit.SetNeedsUpgrade(false)
32+ c.Assert(err, IsNil)
33+ needsUpgrade, err = unit.NeedsUpgrade()
34+ c.Assert(err, IsNil)
35+ c.Assert(needsUpgrade, DeepEquals, &state.NeedsUpgrade{true, false})
36
37 // Can be set multiple times.
38- err = unit.SetNeedsUpgrade()
39- c.Assert(err, IsNil)
40- upgrade, err = unit.NeedsUpgrade()
41- c.Assert(err, IsNil)
42- c.Assert(upgrade, Equals, true)
43+ err = unit.SetNeedsUpgrade(false)
44+ c.Assert(err, IsNil)
45+ needsUpgrade, err = unit.NeedsUpgrade()
46+ c.Assert(err, IsNil)
47+ c.Assert(needsUpgrade, DeepEquals, &state.NeedsUpgrade{true, false})
48
49 // Can be cleared.
50 err = unit.ClearNeedsUpgrade()
51 c.Assert(err, IsNil)
52- upgrade, err = unit.NeedsUpgrade()
53+ needsUpgrade, err = unit.NeedsUpgrade()
54 c.Assert(err, IsNil)
55- c.Assert(upgrade, Equals, false)
56+ c.Assert(needsUpgrade, DeepEquals, &state.NeedsUpgrade{false, false})
57
58 // Can be cleared multiple times
59 err = unit.ClearNeedsUpgrade()
60 c.Assert(err, IsNil)
61- upgrade, err = unit.NeedsUpgrade()
62- c.Assert(err, IsNil)
63- c.Assert(upgrade, Equals, false)
64+ needsUpgrade, err = unit.NeedsUpgrade()
65+ c.Assert(err, IsNil)
66+ c.Assert(needsUpgrade, DeepEquals, &state.NeedsUpgrade{false, false})
67+
68+ // Can be set forced.
69+ err = unit.SetNeedsUpgrade(true)
70+ c.Assert(err, IsNil)
71+ needsUpgrade, err = unit.NeedsUpgrade()
72+ c.Assert(err, IsNil)
73+ c.Assert(needsUpgrade, DeepEquals, &state.NeedsUpgrade{true, true})
74+
75+ // Can be set forced multiple times.
76+ err = unit.SetNeedsUpgrade(true)
77+ c.Assert(err, IsNil)
78+ needsUpgrade, err = unit.NeedsUpgrade()
79+ c.Assert(err, IsNil)
80+ c.Assert(needsUpgrade, DeepEquals, &state.NeedsUpgrade{true, true})
81+
82+ // Can't be set multipe with different force flag. (TODO)
83+ err = unit.SetNeedsUpgrade(false)
84+ c.Assert(err, ErrorMatches, `unit upgrade already enabled: "wordpress/0"`)
85 }
86
87 func (s *StateSuite) TestGetSetClearResolved(c *C) {
88
89=== modified file 'state/unit.go'
90--- state/unit.go 2012-04-20 09:39:38 +0000
91+++ state/unit.go 2012-04-24 14:35:46 +0000
92@@ -28,6 +28,13 @@
93 ResolvedNoHooks ResolvedMode = 1001
94 )
95
96+// NeedsUpgrade describes if a unit needs an
97+// upgrade and if this is forced.
98+type NeedsUpgrade struct {
99+ Upgrade bool
100+ Force bool
101+}
102+
103 // agentPingerPeriod defines the period of pinging the
104 // ZooKeeper to signal that a unit agent is alive. It's
105 // also used by machine.
106@@ -233,23 +240,49 @@
107 return retryTopologyChange(u.st.zk, unassignUnit)
108 }
109
110-// NeedsUpgrade returns whether the unit needs an upgrade.
111-func (u *Unit) NeedsUpgrade() (bool, error) {
112- stat, err := u.st.zk.Exists(u.zkNeedsUpgradePath())
113+// NeedsUpgrade returns whether the unit needs an upgrade
114+// and if it is forced.
115+func (u *Unit) NeedsUpgrade() (*NeedsUpgrade, error) {
116+ yaml, _, err := u.st.zk.Get(u.zkNeedsUpgradePath())
117+ if zookeeper.IsError(err, zookeeper.ZNONODE) {
118+ return &NeedsUpgrade{false, false}, nil
119+ }
120 if err != nil {
121- return false, err
122- }
123- return stat != nil, nil
124+ return &NeedsUpgrade{false, false}, err
125+ }
126+ var setting struct {
127+ Force bool
128+ }
129+ if err = goyaml.Unmarshal([]byte(yaml), &setting); err != nil {
130+ return &NeedsUpgrade{false, false}, err
131+ }
132+ return &NeedsUpgrade{true, setting.Force}, nil
133 }
134
135-// SetNeedsUpgrade informs the unit that it should perform an upgrade.
136-func (u *Unit) SetNeedsUpgrade() error {
137- _, err := u.st.zk.Create(u.zkNeedsUpgradePath(), "", 0, zkPermAll)
138- if zookeeper.IsError(err, zookeeper.ZNODEEXISTS) {
139- // Node already exists, so same state.
140- return nil
141+// SetNeedsUpgrade informs the unit that it should perform
142+// a regular or forced upgrade.
143+func (u *Unit) SetNeedsUpgrade(force bool) error {
144+ setNeedsUpgrade := func(oldYaml string, stat *zookeeper.Stat) (string, error) {
145+ var setting struct {
146+ Force bool
147+ }
148+ if oldYaml == "" {
149+ setting.Force = force
150+ newYaml, err := goyaml.Marshal(setting)
151+ if err != nil {
152+ return "", err
153+ }
154+ return string(newYaml), nil
155+ }
156+ if err := goyaml.Unmarshal([]byte(oldYaml), &setting); err != nil {
157+ return "", err
158+ }
159+ if setting.Force != force {
160+ return "", fmt.Errorf("unit upgrade already enabled: %q", u.Name())
161+ }
162+ return oldYaml, nil
163 }
164- return err
165+ return u.st.zk.RetryChange(u.zkNeedsUpgradePath(), 0, zkPermAll, setNeedsUpgrade)
166 }
167
168 // ClearNeedsUpgrade resets the upgrade notification. It is typically
169@@ -284,7 +317,7 @@
170 return ResolvedNone, err
171 }
172 mode := setting.Retry
173- if err := validResolvedMode(mode); err != nil {
174+ if err := validResolvedMode(mode, false); err != nil {
175 return ResolvedNone, err
176 }
177 return mode, nil
178@@ -297,7 +330,7 @@
179 // reexecute previous failed hooks or to continue as if they had
180 // succeeded before.
181 func (u *Unit) SetResolved(mode ResolvedMode) error {
182- if err := validResolvedMode(mode); err != nil {
183+ if err := validResolvedMode(mode, false); err != nil {
184 return err
185 }
186 setting := &struct{ Retry ResolvedMode }{mode}
187@@ -322,6 +355,13 @@
188 return err
189 }
190
191+// WatchResolved returns a watcher that fires when the unit
192+// is marked as having had its problems resolved. See
193+// SetResolved for details.
194+func (u *Unit) WatchResolved() *ResolvedWatcher {
195+ return newResolvedWatcher(u.st, u.zkResolvedPath())
196+}
197+
198 // OpenPort sets the policy of the port with protocol and number to be opened.
199 func (u *Unit) OpenPort(protocol string, number int) error {
200 openPort := func(oldYaml string, stat *zookeeper.Stat) (string, error) {
201@@ -459,9 +499,29 @@
202 return parts[0], int(sequenceNo), nil
203 }
204
205+// parseResolvedMode parses a given YAML for the resolve mode
206+// and checks if it's valid.
207+func parseResolvedMode(yaml string) (ResolvedMode, error) {
208+ var setting struct {
209+ Retry ResolvedMode
210+ }
211+ if err := goyaml.Unmarshal([]byte(yaml), &setting); err != nil {
212+ return ResolvedNone, err
213+ }
214+ mode := setting.Retry
215+ if err := validResolvedMode(mode, true); err != nil {
216+ return ResolvedNone, err
217+ }
218+ return mode, nil
219+}
220+
221 // validResolvedMode ensures that only valid values for the
222-// resolved mode are used.
223-func validResolvedMode(mode ResolvedMode) error {
224+// resolved mode are used. If acceptNone is true also the
225+// mode ResolvedNone is valid.
226+func validResolvedMode(mode ResolvedMode, acceptNone bool) error {
227+ if acceptNone && mode == ResolvedNone {
228+ return nil
229+ }
230 if mode != ResolvedRetryHooks && mode != ResolvedNoHooks {
231 return fmt.Errorf("invalid error resolution mode: %d", mode)
232 }
233@@ -474,7 +534,7 @@
234 path string
235 tomb tomb.Tomb
236 watcher *watcher.ContentWatcher
237- changeChan chan bool
238+ changeChan chan NeedsUpgrade
239 }
240
241 // newNeedsUpgradeWatcher creates and starts a new resolved flag node
242@@ -483,7 +543,7 @@
243 w := &NeedsUpgradeWatcher{
244 st: st,
245 path: path,
246- changeChan: make(chan bool),
247+ changeChan: make(chan NeedsUpgrade),
248 watcher: watcher.NewContentWatcher(st.zk, path),
249 }
250 go w.loop()
251@@ -493,7 +553,7 @@
252 // Changes returns a channel that will receive notifications
253 // about upgrades for the unit. Note that multiple changes
254 // may be observed as a single event in the channel.
255-func (w *NeedsUpgradeWatcher) Changes() <-chan bool {
256+func (w *NeedsUpgradeWatcher) Changes() <-chan NeedsUpgrade {
257 return w.changeChan
258 }
259
260@@ -523,12 +583,91 @@
261 w.tomb.Kill(nil)
262 return
263 }
264- select {
265- case <-w.watcher.Dying():
266- return
267- case <-w.tomb.Dying():
268- return
269- case w.changeChan <- change.Exists:
270+ var needsUpgrade NeedsUpgrade
271+ if change.Exists {
272+ needsUpgrade.Upgrade = true
273+ var setting struct {
274+ Force bool
275+ }
276+ if err := goyaml.Unmarshal([]byte(change.Content), &setting); err != nil {
277+ w.tomb.Kill(err)
278+ return
279+ }
280+ needsUpgrade.Force = setting.Force
281+ }
282+ select {
283+ case <-w.watcher.Dying():
284+ return
285+ case <-w.tomb.Dying():
286+ return
287+ case w.changeChan <- needsUpgrade:
288+ }
289+ }
290+ }
291+}
292+
293+// ResolvedWatcher observes changes to a resolved flag node.
294+type ResolvedWatcher struct {
295+ st *State
296+ path string
297+ tomb tomb.Tomb
298+ watcher *watcher.ContentWatcher
299+ changeChan chan ResolvedMode
300+}
301+
302+// newResolvedWatcher creates and starts a new resolved flag node
303+// watcher for the given path.
304+func newResolvedWatcher(st *State, path string) *ResolvedWatcher {
305+ w := &ResolvedWatcher{
306+ st: st,
307+ path: path,
308+ changeChan: make(chan ResolvedMode),
309+ watcher: watcher.NewContentWatcher(st.zk, path),
310+ }
311+ go w.loop()
312+ return w
313+}
314+
315+// Changes returns a channel that will receive the new
316+// resolved mode when a change is detected. Note that multiple
317+// changes may be observed as a single event in the channel.
318+func (w *ResolvedWatcher) Changes() <-chan ResolvedMode {
319+ return w.changeChan
320+}
321+
322+// Stop stops the watch and returns any error encountered
323+// while watching. This method should always be called
324+// before discarding the watcher.
325+func (w *ResolvedWatcher) Stop() error {
326+ w.tomb.Kill(nil)
327+ if err := w.watcher.Stop(); err != nil {
328+ w.tomb.Wait()
329+ return err
330+ }
331+ return w.tomb.Wait()
332+}
333+
334+// loop is the backend for watching the resolved flag node.
335+func (w *ResolvedWatcher) loop() {
336+ defer w.tomb.Done()
337+ defer close(w.changeChan)
338+
339+ for {
340+ select {
341+ case <-w.tomb.Dying():
342+ return
343+ case change := <-w.watcher.Changes():
344+ mode, err := parseResolvedMode(change.Content)
345+ if err != nil {
346+ w.tomb.Kill(err)
347+ return
348+ }
349+ select {
350+ case <-w.watcher.Dying():
351+ return
352+ case <-w.tomb.Dying():
353+ return
354+ case w.changeChan <- mode:
355 }
356 }
357 }
358
359=== modified file 'state/watch_test.go'
360--- state/watch_test.go 2012-04-19 15:23:59 +0000
361+++ state/watch_test.go 2012-04-24 14:35:46 +0000
362@@ -2,6 +2,7 @@
363
364 import (
365 . "launchpad.net/gocheck"
366+ "launchpad.net/juju/go/state"
367 "time"
368 )
369
370@@ -92,26 +93,26 @@
371
372 go func() {
373 time.Sleep(50 * time.Millisecond)
374- err = unit.SetNeedsUpgrade()
375+ err = unit.SetNeedsUpgrade(false)
376 c.Assert(err, IsNil)
377 time.Sleep(50 * time.Millisecond)
378 err = unit.ClearNeedsUpgrade()
379 c.Assert(err, IsNil)
380 time.Sleep(50 * time.Millisecond)
381- err = unit.SetNeedsUpgrade()
382+ err = unit.SetNeedsUpgrade(true)
383 c.Assert(err, IsNil)
384 }()
385
386- var expectedChanges = []bool{
387- true,
388- false,
389- true,
390+ var expectedChanges = []state.NeedsUpgrade{
391+ {true, false},
392+ {false, false},
393+ {true, true},
394 }
395 for _, want := range expectedChanges {
396 select {
397 case got, ok := <-needsUpgradeWatcher.Changes():
398 c.Assert(ok, Equals, true)
399- c.Assert(got, Equals, want)
400+ c.Assert(got, DeepEquals, want)
401 case <-time.After(200 * time.Millisecond):
402 c.Fatalf("didn't get change: %#v", want)
403 }
404@@ -126,3 +127,49 @@
405 err = needsUpgradeWatcher.Stop()
406 c.Assert(err, IsNil)
407 }
408+
409+func (s *StateSuite) TestUnitWatchResolved(c *C) {
410+ dummy, _ := addDummyCharm(c, s.st)
411+ wordpress, err := s.st.AddService("wordpress", dummy)
412+ c.Assert(err, IsNil)
413+ c.Assert(wordpress.Name(), Equals, "wordpress")
414+ unit, err := wordpress.AddUnit()
415+ c.Assert(err, IsNil)
416+ resolvedWatcher := unit.WatchResolved()
417+
418+ go func() {
419+ time.Sleep(50 * time.Millisecond)
420+ err = unit.SetResolved(state.ResolvedRetryHooks)
421+ c.Assert(err, IsNil)
422+ time.Sleep(50 * time.Millisecond)
423+ err = unit.ClearResolved()
424+ c.Assert(err, IsNil)
425+ time.Sleep(50 * time.Millisecond)
426+ err = unit.SetResolved(state.ResolvedNoHooks)
427+ c.Assert(err, IsNil)
428+ }()
429+
430+ var expectedChanges = []state.ResolvedMode{
431+ state.ResolvedRetryHooks,
432+ state.ResolvedNone,
433+ state.ResolvedNoHooks,
434+ }
435+ for _, want := range expectedChanges {
436+ select {
437+ case got, ok := <-resolvedWatcher.Changes():
438+ c.Assert(ok, Equals, true)
439+ c.Assert(got, Equals, want)
440+ case <-time.After(200 * time.Millisecond):
441+ c.Fatalf("didn't get change: %#v", want)
442+ }
443+ }
444+
445+ select {
446+ case got, _ := <-resolvedWatcher.Changes():
447+ c.Fatalf("got unexpected change: %#v", got)
448+ case <-time.After(100 * time.Millisecond):
449+ }
450+
451+ err = resolvedWatcher.Stop()
452+ c.Assert(err, IsNil)
453+}

Subscribers

People subscribed via source and target branches