Merge lp:~themue/pyjuju/go-state-unit-resolved-watcher into lp:pyjuju/go
- go-state-unit-resolved-watcher
- Merge into go
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+102497@code.launchpad.net |
Commit message
Description of the change
Added ResolvedWatcher for Units.
It reads the resolved mode out of the node content if
it's created or changed.
Gustavo Niemeyer (niemeyer) wrote : | # |
William Reade (fwereade) wrote : | # |
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:/
File state/state.go (right):
https:/
state/state.go:292: // A non-existing node is treaten as an empty node.
s/non-existing/
s/treaten/treated/
(I actually rather like "treaten", but... ;))
https:/
File state/unit.go (left):
https:/
state/unit.go:453:
This file is getting a bit unwieldy IMO. Could we move the Watcher types
into their own file?
https:/
File state/unit.go (right):
https:/
state/unit.go:287: if err := validResolvedMo
{
I *think* acceptNone should be true here, shouldn't it?
https:/
state/unit.go:469: // parseResolveMode parses a given YAML for the
resolve mode
s/parseResolveM
https:/
state/unit.go:472: setting := &struct{ Retry ResolvedMode }{}
I have a slight preference for a non-anonymous struct
(ResolvedNodeCo
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:/
state/unit.go:538: func (w *NeedsUpgradeWa
Maybe "run" rather than "loop"?
https:/
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:/
state/unit.go:600: func (w *ResolvedWatcher) loop() {
As above, I prefer "run"; ymmv.
https:/
File state/watch_test.go (right):
https:/
state/watch_
How can we hit this path?
https:/
state/watch_
Why 200ms here and 100ms above?
https:/
state/watch_
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:/
state/watch_
As above.
Roger Peppe (rogpeppe) wrote : | # |
https:/
File state/unit.go (right):
https:/
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
(ResolvedNodeCo
> 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.
https:/
state/unit.go:538: func (w *NeedsUpgradeWa
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:/
File state/watch_test.go (right):
https:/
state/watch_
On 2012/04/19 08:29:50, fwereade wrote:
> How can we hit this path?
+1.
panic("not reached")
https:/
state/watch_
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.
Gustavo Niemeyer (niemeyer) wrote : | # |
Still waiting for the pre-req to be sorted out, but a few
comments-
https:/
File state/unit.go (right):
https:/
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
(ResolvedNodeCo
> 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:/
state/unit.go:538: func (w *NeedsUpgradeWa
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.
William Reade (fwereade) wrote : | # |
https:/
File state/unit.go (right):
https:/
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
(ResolvedNodeCo
> > 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
"resolvedNodeCo
benefits of static typing to node content...
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:/
File state/state.go (right):
https:/
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/
The correct form is actually non-existent, AFAIK:
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.
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.
Gustavo Niemeyer (niemeyer) wrote : | # |
I believe this became https:/
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
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 | +} |
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/