Merge lp:~fwereade/juju-core/use-prepare-leave-scope into lp:~go-bot/juju-core/trunk
- use-prepare-leave-scope
- Merge into trunk
Proposed by
William Reade
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | William Reade | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 2772 | ||||
Proposed branch: | lp:~fwereade/juju-core/use-prepare-leave-scope | ||||
Merge into: | lp:~go-bot/juju-core/trunk | ||||
Prerequisite: | lp:~fwereade/juju-core/prepare-leave-scope | ||||
Diff against target: |
968 lines (+295/-105) 13 files modified
state/apiserver/uniter/uniter.go (+6/-4) state/apiserver/uniter/uniter_test.go (+11/-4) state/cleanup.go (+65/-22) state/cleanup_test.go (+82/-11) state/environ.go (+1/-1) state/machine.go (+1/-1) state/relation.go (+1/-1) state/relationunit.go (+15/-2) state/relationunit_test.go (+52/-37) state/service.go (+1/-1) state/service_test.go (+7/-0) state/unit.go (+27/-8) state/unit_test.go (+26/-13) |
||||
To merge this branch: | bzr merge lp:~fwereade/juju-core/use-prepare-leave-scope | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+219758@code.launchpad.net |
Commit message
state: set departed on dying units' relations
This ended up including:
* stopping using magic strings in cleanup
* drawing a clear distinction between "joined" and "in scope" (which
accounts for most of the lines of changes)
...but did *not* include:
* fixing the name of the Uniter.
wait for API versioning
Description of the change
state: set departed on dying units' relations
This ended up including:
* stopping using magic strings in cleanup
* drawing a clear distinction between "joined" and "in scope" (which
accounts for most of the lines of changes)
...but did *not* include:
* fixing the name of the Uniter.
wait for API versioning
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'state/apiserver/uniter/uniter.go' |
2 | --- state/apiserver/uniter/uniter.go 2014-05-13 04:30:48 +0000 |
3 | +++ state/apiserver/uniter/uniter.go 2014-05-22 09:18:59 +0000 |
4 | @@ -694,8 +694,8 @@ |
5 | return result, nil |
6 | } |
7 | |
8 | -func joinedRelationTags(unit *state.Unit) ([]string, error) { |
9 | - relations, err := unit.JoinedRelations() |
10 | +func relationsInScopeTags(unit *state.Unit) ([]string, error) { |
11 | + relations, err := unit.RelationsInScope() |
12 | if err != nil { |
13 | return nil, err |
14 | } |
15 | @@ -706,7 +706,9 @@ |
16 | return tags, nil |
17 | } |
18 | |
19 | -// JoinedRelations returns the tags of all relations each supplied unit has joined. |
20 | +// JoinedRelations returns the tags of all relations for which each supplied unit |
21 | +// has entered scope. It should be called RelationsInScope, but it's not convenient |
22 | +// to make that change until we have versioned APIs. |
23 | func (u *UniterAPI) JoinedRelations(args params.Entities) (params.StringsResults, error) { |
24 | result := params.StringsResults{ |
25 | Results: make([]params.StringsResult, len(args.Entities)), |
26 | @@ -724,7 +726,7 @@ |
27 | var unit *state.Unit |
28 | unit, err = u.getUnit(entity.Tag) |
29 | if err == nil { |
30 | - result.Results[i].Result, err = joinedRelationTags(unit) |
31 | + result.Results[i].Result, err = relationsInScopeTags(unit) |
32 | } |
33 | } |
34 | result.Results[i].Error = common.ServerError(err) |
35 | |
36 | === modified file 'state/apiserver/uniter/uniter_test.go' |
37 | --- state/apiserver/uniter/uniter_test.go 2014-05-13 04:30:48 +0000 |
38 | +++ state/apiserver/uniter/uniter_test.go 2014-05-22 09:18:59 +0000 |
39 | @@ -1065,9 +1065,7 @@ |
40 | {rel.Tag()}, |
41 | }, |
42 | } |
43 | - result, err := s.uniter.JoinedRelations(args) |
44 | - c.Assert(err, gc.IsNil) |
45 | - c.Assert(result, gc.DeepEquals, params.StringsResults{ |
46 | + expect := params.StringsResults{ |
47 | Results: []params.StringsResult{ |
48 | {Result: []string{rel.Tag()}}, |
49 | {Error: apiservertesting.ErrUnauthorized}, |
50 | @@ -1076,7 +1074,16 @@ |
51 | {Error: apiservertesting.ErrUnauthorized}, |
52 | {Error: apiservertesting.ErrUnauthorized}, |
53 | }, |
54 | - }) |
55 | + } |
56 | + check := func() { |
57 | + result, err := s.uniter.JoinedRelations(args) |
58 | + c.Assert(err, gc.IsNil) |
59 | + c.Assert(result, gc.DeepEquals, expect) |
60 | + } |
61 | + check() |
62 | + err = relUnit.PrepareLeaveScope() |
63 | + c.Assert(err, gc.IsNil) |
64 | + check() |
65 | } |
66 | |
67 | func (s *uniterSuite) TestReadSettings(c *gc.C) { |
68 | |
69 | === modified file 'state/cleanup.go' |
70 | --- state/cleanup.go 2014-05-13 04:30:48 +0000 |
71 | +++ state/cleanup.go 2014-05-22 09:18:59 +0000 |
72 | @@ -8,17 +8,28 @@ |
73 | "labix.org/v2/mgo/txn" |
74 | ) |
75 | |
76 | +type cleanupKind string |
77 | + |
78 | +const ( |
79 | + // SCHEMACHANGE: the names are expressive, the values not so much. |
80 | + cleanupRelationSettings cleanupKind = "settings" |
81 | + cleanupUnitsForDyingService cleanupKind = "units" |
82 | + cleanupDyingUnit cleanupKind = "dyingUnit" |
83 | + cleanupServicesForDyingEnvironment cleanupKind = "services" |
84 | + cleanupForceDestroyedMachine cleanupKind = "machine" |
85 | +) |
86 | + |
87 | // cleanupDoc represents a potentially large set of documents that should be |
88 | // removed. |
89 | type cleanupDoc struct { |
90 | Id bson.ObjectId `bson:"_id"` |
91 | - Kind string |
92 | + Kind cleanupKind |
93 | Prefix string |
94 | } |
95 | |
96 | // newCleanupOp returns a txn.Op that creates a cleanup document with a unique |
97 | // id and the supplied kind and prefix. |
98 | -func (st *State) newCleanupOp(kind, prefix string) txn.Op { |
99 | +func (st *State) newCleanupOp(kind cleanupKind, prefix string) txn.Op { |
100 | doc := &cleanupDoc{ |
101 | Id: bson.NewObjectId(), |
102 | Kind: kind, |
103 | @@ -50,14 +61,16 @@ |
104 | var err error |
105 | logger.Debugf("running %q cleanup: %q", doc.Kind, doc.Prefix) |
106 | switch doc.Kind { |
107 | - case "settings": |
108 | - err = st.cleanupSettings(doc.Prefix) |
109 | - case "units": |
110 | - err = st.cleanupUnits(doc.Prefix) |
111 | - case "services": |
112 | - err = st.cleanupServices() |
113 | - case "machine": |
114 | - err = st.cleanupMachine(doc.Prefix) |
115 | + case cleanupRelationSettings: |
116 | + err = st.cleanupRelationSettings(doc.Prefix) |
117 | + case cleanupUnitsForDyingService: |
118 | + err = st.cleanupUnitsForDyingService(doc.Prefix) |
119 | + case cleanupDyingUnit: |
120 | + err = st.cleanupDyingUnit(doc.Prefix) |
121 | + case cleanupServicesForDyingEnvironment: |
122 | + err = st.cleanupServicesForDyingEnvironment() |
123 | + case cleanupForceDestroyedMachine: |
124 | + err = st.cleanupForceDestroyedMachine(doc.Prefix) |
125 | default: |
126 | err = fmt.Errorf("unknown cleanup kind %q", doc.Kind) |
127 | } |
128 | @@ -80,7 +93,7 @@ |
129 | return nil |
130 | } |
131 | |
132 | -func (st *State) cleanupSettings(prefix string) error { |
133 | +func (st *State) cleanupRelationSettings(prefix string) error { |
134 | // Documents marked for cleanup are not otherwise referenced in the |
135 | // system, and will not be under watch, and are therefore safe to |
136 | // delete directly. |
137 | @@ -95,9 +108,10 @@ |
138 | return nil |
139 | } |
140 | |
141 | -// cleanupServices sets all services to Dying, if they are not already Dying |
142 | -// or Dead. It's expected to be used when an environment is destroyed. |
143 | -func (st *State) cleanupServices() error { |
144 | +// cleanupServicesForDyingEnvironment sets all services to Dying, if they are |
145 | +// not already Dying or Dead. It's expected to be used when an environment is |
146 | +// destroyed. |
147 | +func (st *State) cleanupServicesForDyingEnvironment() error { |
148 | // This won't miss services, because a Dying environment cannot have |
149 | // services added to it. But we do have to remove the services themselves |
150 | // via individual transactions, because they could be in any state at all. |
151 | @@ -115,9 +129,10 @@ |
152 | return nil |
153 | } |
154 | |
155 | -// cleanupUnits sets all units with the given prefix to Dying, if they are not |
156 | -// already Dying or Dead. It's expected to be used when a service is destroyed. |
157 | -func (st *State) cleanupUnits(prefix string) error { |
158 | +// cleanupUnitsForDyingService sets all units with the given prefix to Dying, |
159 | +// if they are not already Dying or Dead. It's expected to be used when a |
160 | +// service is destroyed. |
161 | +func (st *State) cleanupUnitsForDyingService(prefix string) error { |
162 | // This won't miss units, because a Dying service cannot have units added |
163 | // to it. But we do have to remove the units themselves via individual |
164 | // transactions, because they could be in any state at all. |
165 | @@ -135,10 +150,38 @@ |
166 | return nil |
167 | } |
168 | |
169 | -// cleanupMachine systematically destroys and removes all entities that |
170 | -// depend upon the supplied machine, and removes the machine from state. It's |
171 | +// cleanupDyingUnit marks the unit as departing from all its joined relations, |
172 | +// allowing related units to start converging to a state in which that unit is |
173 | +// gone as quickly as possible. |
174 | +func (st *State) cleanupDyingUnit(name string) error { |
175 | + unit, err := st.Unit(name) |
176 | + if errors.IsNotFound(err) { |
177 | + return nil |
178 | + } else if err != nil { |
179 | + return err |
180 | + } |
181 | + relations, err := unit.RelationsJoined() |
182 | + if err != nil { |
183 | + return err |
184 | + } |
185 | + for _, relation := range relations { |
186 | + relationUnit, err := relation.Unit(unit) |
187 | + if errors.IsNotFound(err) { |
188 | + continue |
189 | + } else if err != nil { |
190 | + return err |
191 | + } |
192 | + if err := relationUnit.PrepareLeaveScope(); err != nil { |
193 | + return err |
194 | + } |
195 | + } |
196 | + return nil |
197 | +} |
198 | + |
199 | +// cleanupForceDestroyedMachine systematically destroys and removes all entities |
200 | +// that depend upon the supplied machine, and removes the machine from state. It's |
201 | // expected to be used in response to destroy-machine --force. |
202 | -func (st *State) cleanupMachine(machineId string) error { |
203 | +func (st *State) cleanupForceDestroyedMachine(machineId string) error { |
204 | machine, err := st.Machine(machineId) |
205 | if errors.IsNotFound(err) { |
206 | return nil |
207 | @@ -179,7 +222,7 @@ |
208 | // instance that would otherwise be ignored when in provisioner-safe-mode. |
209 | } |
210 | |
211 | -// cleanupContainers recursively calls cleanupMachine on the supplied |
212 | +// cleanupContainers recursively calls cleanupForceDestroyedMachine on the supplied |
213 | // machine's containers, and removes them from state entirely. |
214 | func (st *State) cleanupContainers(machine *Machine) error { |
215 | containerIds, err := machine.Containers() |
216 | @@ -189,7 +232,7 @@ |
217 | return err |
218 | } |
219 | for _, containerId := range containerIds { |
220 | - if err := st.cleanupMachine(containerId); err != nil { |
221 | + if err := st.cleanupForceDestroyedMachine(containerId); err != nil { |
222 | return err |
223 | } |
224 | container, err := st.Machine(containerId) |
225 | |
226 | === modified file 'state/cleanup_test.go' |
227 | --- state/cleanup_test.go 2014-05-13 04:30:48 +0000 |
228 | +++ state/cleanup_test.go 2014-05-22 09:18:59 +0000 |
229 | @@ -44,7 +44,6 @@ |
230 | |
231 | // Run the cleanup, and check that units are all destroyed as appropriate. |
232 | s.assertCleanupRuns(c) |
233 | - s.assertDoesNotNeedCleanup(c) |
234 | err = units[0].Refresh() |
235 | c.Assert(err, gc.IsNil) |
236 | c.Assert(units[0].Life(), gc.Equals, state.Dying) |
237 | @@ -52,6 +51,10 @@ |
238 | c.Assert(err, jc.Satisfies, errors.IsNotFound) |
239 | err = units[2].Refresh() |
240 | c.Assert(err, jc.Satisfies, errors.IsNotFound) |
241 | + |
242 | + // Run a final cleanup to clear the cleanup scheduled for the unit that |
243 | + // became dying. |
244 | + s.assertCleanupCount(c, 1) |
245 | } |
246 | |
247 | func (s *CleanupSuite) TestCleanupEnvironmentServices(c *gc.C) { |
248 | @@ -108,12 +111,10 @@ |
249 | // Destroy the service, check the relation's still around. |
250 | err = pr.svc.Destroy() |
251 | c.Assert(err, gc.IsNil) |
252 | - s.assertNeedsCleanup(c) |
253 | - s.assertCleanupRuns(c) |
254 | + s.assertCleanupCount(c, 2) |
255 | err = rel.Refresh() |
256 | c.Assert(err, gc.IsNil) |
257 | c.Assert(rel.Life(), gc.Equals, state.Dying) |
258 | - s.assertDoesNotNeedCleanup(c) |
259 | |
260 | // The unit leaves scope, triggering relation removal. |
261 | err = pr.ru0.LeaveScope() |
262 | @@ -126,8 +127,7 @@ |
263 | c.Assert(settings, gc.DeepEquals, map[string]interface{}{"some": "settings"}) |
264 | |
265 | // ...but they are on cleanup. |
266 | - s.assertCleanupRuns(c) |
267 | - s.assertDoesNotNeedCleanup(c) |
268 | + s.assertCleanupCount(c, 1) |
269 | _, err = pr.ru1.ReadSettings("riak/0") |
270 | c.Assert(err, gc.ErrorMatches, `cannot read settings for unit "riak/0" in relation "riak:ring": settings not found`) |
271 | } |
272 | @@ -164,12 +164,11 @@ |
273 | s.assertNeedsCleanup(c) |
274 | |
275 | // Clean up, and check that the unit has been removed... |
276 | - s.assertCleanupRuns(c) |
277 | - s.assertDoesNotNeedCleanup(c) |
278 | + s.assertCleanupCount(c, 2) |
279 | assertRemoved(c, pr.u0) |
280 | |
281 | // ...and the unit has departed relation scope... |
282 | - assertNotInScope(c, pr.ru0) |
283 | + assertNotJoined(c, pr.ru0) |
284 | |
285 | // ...but that the machine remains, and is Dead, ready for removal by the |
286 | // provisioner. |
287 | @@ -218,8 +217,7 @@ |
288 | s.assertNeedsCleanup(c) |
289 | |
290 | // Clean up, and check that the container has been removed... |
291 | - s.assertCleanupRuns(c) |
292 | - s.assertDoesNotNeedCleanup(c) |
293 | + s.assertCleanupCount(c, 2) |
294 | err = container.Refresh() |
295 | c.Assert(err, jc.Satisfies, errors.IsNotFound) |
296 | |
297 | @@ -240,6 +238,67 @@ |
298 | assertLife(c, machine, state.Dead) |
299 | } |
300 | |
301 | +func (s *CleanupSuite) TestCleanupDyingUnit(c *gc.C) { |
302 | + // Create active unit, in a relation. |
303 | + prr := NewProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal) |
304 | + err := prr.pru0.EnterScope(nil) |
305 | + c.Assert(err, gc.IsNil) |
306 | + |
307 | + // Destroy provider unit 0; check it's Dying, and a cleanup has been scheduled. |
308 | + err = prr.pu0.Destroy() |
309 | + c.Assert(err, gc.IsNil) |
310 | + err = prr.pu0.Refresh() |
311 | + c.Assert(err, gc.IsNil) |
312 | + assertLife(c, prr.pu0, state.Dying) |
313 | + s.assertNeedsCleanup(c) |
314 | + |
315 | + // Check it's reported in scope until cleaned up. |
316 | + assertJoined(c, prr.pru0) |
317 | + s.assertCleanupCount(c, 1) |
318 | + assertInScope(c, prr.pru0) |
319 | + assertNotJoined(c, prr.pru0) |
320 | + |
321 | + // Destroy the relation, and check it sticks around... |
322 | + err = prr.rel.Destroy() |
323 | + c.Assert(err, gc.IsNil) |
324 | + assertLife(c, prr.rel, state.Dying) |
325 | + |
326 | + // ...until the unit is removed, and really leaves scope. |
327 | + err = prr.pu0.EnsureDead() |
328 | + c.Assert(err, gc.IsNil) |
329 | + err = prr.pu0.Remove() |
330 | + c.Assert(err, gc.IsNil) |
331 | + assertNotInScope(c, prr.pru0) |
332 | + assertRemoved(c, prr.rel) |
333 | +} |
334 | + |
335 | +func (s *CleanupSuite) TestCleanupDyingUnitAlreadyRemoved(c *gc.C) { |
336 | + // Create active unit, in a relation. |
337 | + prr := NewProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal) |
338 | + err := prr.pru0.EnterScope(nil) |
339 | + c.Assert(err, gc.IsNil) |
340 | + |
341 | + // Destroy provider unit 0; check it's Dying, and a cleanup has been scheduled. |
342 | + err = prr.pu0.Destroy() |
343 | + c.Assert(err, gc.IsNil) |
344 | + err = prr.pu0.Refresh() |
345 | + c.Assert(err, gc.IsNil) |
346 | + assertLife(c, prr.pu0, state.Dying) |
347 | + s.assertNeedsCleanup(c) |
348 | + |
349 | + // Remove the unit, and the relation. |
350 | + err = prr.pu0.EnsureDead() |
351 | + c.Assert(err, gc.IsNil) |
352 | + err = prr.pu0.Remove() |
353 | + c.Assert(err, gc.IsNil) |
354 | + err = prr.rel.Destroy() |
355 | + c.Assert(err, gc.IsNil) |
356 | + assertRemoved(c, prr.rel) |
357 | + |
358 | + // Check the cleanup still runs happily. |
359 | + s.assertCleanupCount(c, 1) |
360 | +} |
361 | + |
362 | func (s *CleanupSuite) TestNothingToCleanup(c *gc.C) { |
363 | s.assertDoesNotNeedCleanup(c) |
364 | s.assertCleanupRuns(c) |
365 | @@ -262,3 +321,15 @@ |
366 | c.Assert(err, gc.IsNil) |
367 | c.Assert(actual, jc.IsFalse) |
368 | } |
369 | + |
370 | +// assertCleanupCount is useful because certain cleanups cause other cleanups |
371 | +// to be queued; it makes more sense to just run cleanup again than to unpick |
372 | +// object destruction so that we run the cleanups inline while running cleanups. |
373 | +func (s *CleanupSuite) assertCleanupCount(c *gc.C, count int) { |
374 | + for i := 0; i < count; i++ { |
375 | + c.Logf("checking cleanups %d", i) |
376 | + s.assertNeedsCleanup(c) |
377 | + s.assertCleanupRuns(c) |
378 | + } |
379 | + s.assertDoesNotNeedCleanup(c) |
380 | +} |
381 | |
382 | === modified file 'state/environ.go' |
383 | --- state/environ.go 2014-05-13 04:30:48 +0000 |
384 | +++ state/environ.go 2014-05-22 09:18:59 +0000 |
385 | @@ -105,7 +105,7 @@ |
386 | Id: e.doc.UUID, |
387 | Update: bson.D{{"$set", bson.D{{"life", Dying}}}}, |
388 | Assert: isEnvAliveDoc, |
389 | - }, e.st.newCleanupOp("services", "")} |
390 | + }, e.st.newCleanupOp(cleanupServicesForDyingEnvironment, "")} |
391 | err := e.st.runTransaction(ops) |
392 | switch err { |
393 | case nil, txn.ErrAborted: |
394 | |
395 | === modified file 'state/machine.go' |
396 | --- state/machine.go 2014-05-13 04:30:48 +0000 |
397 | +++ state/machine.go 2014-05-22 09:18:59 +0000 |
398 | @@ -398,7 +398,7 @@ |
399 | C: m.st.machines.Name, |
400 | Id: m.doc.Id, |
401 | Assert: bson.D{{"jobs", bson.D{{"$nin", []MachineJob{JobManageEnviron}}}}}, |
402 | - }, m.st.newCleanupOp("machine", m.doc.Id)} |
403 | + }, m.st.newCleanupOp(cleanupForceDestroyedMachine, m.doc.Id)} |
404 | if err := m.st.runTransaction(ops); err != txn.ErrAborted { |
405 | return err |
406 | } |
407 | |
408 | === modified file 'state/relation.go' |
409 | --- state/relation.go 2014-05-13 04:30:48 +0000 |
410 | +++ state/relation.go 2014-05-22 09:18:59 +0000 |
411 | @@ -216,7 +216,7 @@ |
412 | Update: bson.D{{"$inc", bson.D{{"relationcount", -1}}}}, |
413 | }) |
414 | } |
415 | - cleanupOp := r.st.newCleanupOp("settings", fmt.Sprintf("r#%d#", r.Id())) |
416 | + cleanupOp := r.st.newCleanupOp(cleanupRelationSettings, fmt.Sprintf("r#%d#", r.Id())) |
417 | return append(ops, cleanupOp), nil |
418 | } |
419 | |
420 | |
421 | === modified file 'state/relationunit.go' |
422 | --- state/relationunit.go 2014-05-20 13:05:58 +0000 |
423 | +++ state/relationunit.go 2014-05-22 09:18:59 +0000 |
424 | @@ -326,13 +326,26 @@ |
425 | return fmt.Errorf("cannot leave scope for %s: inconsistent state", desc) |
426 | } |
427 | |
428 | -// InScope returns whether the relation unit has entered scope or not. |
429 | +// InScope returns whether the relation unit has entered scope and not left it. |
430 | func (ru *RelationUnit) InScope() (bool, error) { |
431 | + return ru.inScope(nil) |
432 | +} |
433 | + |
434 | +// Joined returns whether the relation unit has entered scope and neither left |
435 | +// it nor prepared to leave it. |
436 | +func (ru *RelationUnit) Joined() (bool, error) { |
437 | + return ru.inScope(bson.D{{"departing", bson.D{{"$ne", true}}}}) |
438 | +} |
439 | + |
440 | +// inScope returns whether a scope document exists satisfying the supplied |
441 | +// selector. |
442 | +func (ru *RelationUnit) inScope(sel bson.D) (bool, error) { |
443 | key, err := ru.key(ru.unit.Name()) |
444 | if err != nil { |
445 | return false, err |
446 | } |
447 | - count, err := ru.st.relationScopes.FindId(key).Count() |
448 | + sel = append(sel, bson.D{{"_id", key}}...) |
449 | + count, err := ru.st.relationScopes.Find(sel).Count() |
450 | if err != nil { |
451 | return false, err |
452 | } |
453 | |
454 | === modified file 'state/relationunit_test.go' |
455 | --- state/relationunit_test.go 2014-05-20 13:05:58 +0000 |
456 | +++ state/relationunit_test.go 2014-05-22 09:18:59 +0000 |
457 | @@ -35,11 +35,25 @@ |
458 | } |
459 | |
460 | func assertNotInScope(c *gc.C, ru *state.RelationUnit) { |
461 | + assertNotJoined(c, ru) |
462 | ok, err := ru.InScope() |
463 | c.Assert(err, gc.IsNil) |
464 | c.Assert(ok, jc.IsFalse) |
465 | } |
466 | |
467 | +func assertJoined(c *gc.C, ru *state.RelationUnit) { |
468 | + assertInScope(c, ru) |
469 | + ok, err := ru.Joined() |
470 | + c.Assert(err, gc.IsNil) |
471 | + c.Assert(ok, jc.IsTrue) |
472 | +} |
473 | + |
474 | +func assertNotJoined(c *gc.C, ru *state.RelationUnit) { |
475 | + ok, err := ru.Joined() |
476 | + c.Assert(err, gc.IsNil) |
477 | + c.Assert(ok, jc.IsFalse) |
478 | +} |
479 | + |
480 | func (s *RelationUnitSuite) TestReadSettingsErrors(c *gc.C) { |
481 | riak := s.AddTestingService(c, "riak", s.AddTestingCharm(c, "riak")) |
482 | u0, err := riak.AddUnit() |
483 | @@ -94,7 +108,7 @@ |
484 | } |
485 | } |
486 | assertSettings(pr.u0, normal) |
487 | - assertInScope(c, pr.ru0) |
488 | + assertJoined(c, pr.ru0) |
489 | |
490 | // Check that EnterScope when scope already entered does not touch |
491 | // settings at all. |
492 | @@ -102,7 +116,7 @@ |
493 | err = pr.ru0.EnterScope(changed) |
494 | c.Assert(err, gc.IsNil) |
495 | assertSettings(pr.u0, normal) |
496 | - assertInScope(c, pr.ru0) |
497 | + assertJoined(c, pr.ru0) |
498 | |
499 | // Leave scope, check settings are still as accessible as before. |
500 | err = pr.ru0.LeaveScope() |
501 | @@ -115,7 +129,7 @@ |
502 | err = pr.ru0.EnterScope(changed) |
503 | c.Assert(err, gc.IsNil) |
504 | assertSettings(pr.u0, changed) |
505 | - assertInScope(c, pr.ru0) |
506 | + assertJoined(c, pr.ru0) |
507 | |
508 | // Leave and re-enter with nil nettings, and check they overwrite to become |
509 | // an empty map. |
510 | @@ -125,14 +139,14 @@ |
511 | err = pr.ru0.EnterScope(nil) |
512 | c.Assert(err, gc.IsNil) |
513 | assertSettings(pr.u0, map[string]interface{}{}) |
514 | - assertInScope(c, pr.ru0) |
515 | + assertJoined(c, pr.ru0) |
516 | |
517 | // Check that entering scope for the first time with nil settings works correctly. |
518 | assertNotInScope(c, pr.ru1) |
519 | err = pr.ru1.EnterScope(nil) |
520 | c.Assert(err, gc.IsNil) |
521 | assertSettings(pr.u1, map[string]interface{}{}) |
522 | - assertInScope(c, pr.ru1) |
523 | + assertJoined(c, pr.ru1) |
524 | } |
525 | |
526 | func (s *RelationUnitSuite) TestProReqSettings(c *gc.C) { |
527 | @@ -154,7 +168,7 @@ |
528 | node.Set("meme", "foul-bachelor-frog") |
529 | _, err = node.Write() |
530 | c.Assert(err, gc.IsNil) |
531 | - assertInScope(c, prr.pru0) |
532 | + assertJoined(c, prr.pru0) |
533 | |
534 | // Check settings can be read by every RU. |
535 | for _, ru := range rus { |
536 | @@ -184,7 +198,7 @@ |
537 | node.Set("meme", "foul-bachelor-frog") |
538 | _, err = node.Write() |
539 | c.Assert(err, gc.IsNil) |
540 | - assertInScope(c, prr.pru0) |
541 | + assertJoined(c, prr.pru0) |
542 | |
543 | // Check settings can be read by RUs in the same container. |
544 | rus0 := RUs{prr.pru0, prr.rru0} |
545 | @@ -229,13 +243,13 @@ |
546 | err = pru.EnterScope(nil) |
547 | c.Assert(err, gc.IsNil) |
548 | assertSubCount(1) |
549 | - assertInScope(c, pru) |
550 | + assertJoined(c, pru) |
551 | |
552 | // Enter principal scope again and check no more subordinates created. |
553 | err = pru.EnterScope(nil) |
554 | c.Assert(err, gc.IsNil) |
555 | assertSubCount(1) |
556 | - assertInScope(c, pru) |
557 | + assertJoined(c, pru) |
558 | |
559 | // Leave principal scope, then re-enter, and check that still no further |
560 | // subordinates are created. |
561 | @@ -245,7 +259,7 @@ |
562 | err = pru.EnterScope(nil) |
563 | c.Assert(err, gc.IsNil) |
564 | runits := assertSubCount(1) |
565 | - assertInScope(c, pru) |
566 | + assertJoined(c, pru) |
567 | |
568 | // Set the subordinate to Dying, and enter scope again; because the scope |
569 | // is already entered, no error is returned. |
570 | @@ -254,7 +268,7 @@ |
571 | c.Assert(err, gc.IsNil) |
572 | err = pru.EnterScope(nil) |
573 | c.Assert(err, gc.IsNil) |
574 | - assertInScope(c, pru) |
575 | + assertJoined(c, pru) |
576 | |
577 | // Leave scope, then try to enter again with the Dying subordinate. |
578 | err = pru.LeaveScope() |
579 | @@ -275,7 +289,7 @@ |
580 | err = pru.EnterScope(nil) |
581 | c.Assert(err, gc.IsNil) |
582 | assertSubCount(1) |
583 | - assertInScope(c, pru) |
584 | + assertJoined(c, pru) |
585 | } |
586 | |
587 | func (s *RelationUnitSuite) TestDestroyRelationWithUnitsInScope(c *gc.C) { |
588 | @@ -287,11 +301,11 @@ |
589 | assertNotInScope(c, pr.ru0) |
590 | err := pr.ru0.EnterScope(map[string]interface{}{"some": "settings"}) |
591 | c.Assert(err, gc.IsNil) |
592 | - assertInScope(c, pr.ru0) |
593 | + assertJoined(c, pr.ru0) |
594 | assertNotInScope(c, pr.ru1) |
595 | err = pr.ru1.EnterScope(nil) |
596 | c.Assert(err, gc.IsNil) |
597 | - assertInScope(c, pr.ru1) |
598 | + assertJoined(c, pr.ru1) |
599 | err = pr.svc.Destroy() |
600 | c.Assert(err, gc.IsNil) |
601 | err = rel.Refresh() |
602 | @@ -309,7 +323,7 @@ |
603 | c.Assert(err, gc.ErrorMatches, `cannot read settings for unit "riak/2" in relation "riak:ring": settings not found`) |
604 | |
605 | // ru0 leaves the scope; check that service Destroy is still a no-op. |
606 | - assertInScope(c, pr.ru0) |
607 | + assertJoined(c, pr.ru0) |
608 | err = pr.ru0.LeaveScope() |
609 | c.Assert(err, gc.IsNil) |
610 | assertNotInScope(c, pr.ru0) |
611 | @@ -328,7 +342,7 @@ |
612 | assertSettings() |
613 | |
614 | // The final unit leaves the scope, and cleans up after itself. |
615 | - assertInScope(c, pr.ru1) |
616 | + assertJoined(c, pr.ru1) |
617 | err = pr.ru1.LeaveScope() |
618 | c.Assert(err, gc.IsNil) |
619 | assertNotInScope(c, pr.ru1) |
620 | @@ -353,11 +367,11 @@ |
621 | assertNotInScope(c, pr.ru0) |
622 | err := pr.ru0.EnterScope(nil) |
623 | c.Assert(err, gc.IsNil) |
624 | - assertInScope(c, pr.ru0) |
625 | + assertJoined(c, pr.ru0) |
626 | assertNotInScope(c, pr.ru1) |
627 | err = pr.ru1.EnterScope(nil) |
628 | c.Assert(err, gc.IsNil) |
629 | - assertInScope(c, pr.ru1) |
630 | + assertJoined(c, pr.ru1) |
631 | |
632 | // One unit becomes Dying, then re-enters the scope; this is not an error, |
633 | // because the state is already as requested. |
634 | @@ -365,7 +379,7 @@ |
635 | c.Assert(err, gc.IsNil) |
636 | err = pr.ru0.EnterScope(nil) |
637 | c.Assert(err, gc.IsNil) |
638 | - assertInScope(c, pr.ru0) |
639 | + assertJoined(c, pr.ru0) |
640 | |
641 | // Two units leave... |
642 | err = pr.ru0.LeaveScope() |
643 | @@ -384,7 +398,7 @@ |
644 | assertNotInScope(c, pr.ru2) |
645 | err = pr.ru2.EnterScope(nil) |
646 | c.Assert(err, gc.IsNil) |
647 | - assertInScope(c, pr.ru2) |
648 | + assertJoined(c, pr.ru2) |
649 | |
650 | // ...but Dying units cannot. |
651 | err = pr.u3.Destroy() |
652 | @@ -421,7 +435,7 @@ |
653 | node, err := pr.ru0.Settings() |
654 | c.Assert(err, gc.IsNil) |
655 | c.Assert(node.Map(), gc.DeepEquals, map[string]interface{}{"foo": "bar"}) |
656 | - assertInScope(c, pr.ru0) |
657 | + assertJoined(c, pr.ru0) |
658 | |
659 | // ru1 enters; check change is observed. |
660 | assertNotInScope(c, pr.ru1) |
661 | @@ -429,20 +443,20 @@ |
662 | c.Assert(err, gc.IsNil) |
663 | s.assertScopeChange(c, w0, []string{"riak/1"}, nil) |
664 | s.assertNoScopeChange(c, w0) |
665 | - assertInScope(c, pr.ru1) |
666 | + assertJoined(c, pr.ru1) |
667 | |
668 | // ru1 enters again, check no problems and no changes. |
669 | err = pr.ru1.EnterScope(nil) |
670 | c.Assert(err, gc.IsNil) |
671 | s.assertNoScopeChange(c, w0) |
672 | - assertInScope(c, pr.ru1) |
673 | + assertJoined(c, pr.ru1) |
674 | |
675 | // Stop watching; ru2 enters. |
676 | testing.AssertStop(c, w0) |
677 | assertNotInScope(c, pr.ru2) |
678 | err = pr.ru2.EnterScope(nil) |
679 | c.Assert(err, gc.IsNil) |
680 | - assertInScope(c, pr.ru2) |
681 | + assertJoined(c, pr.ru2) |
682 | |
683 | // Start watch again, check initial event. |
684 | w0 = pr.ru0.WatchScope() |
685 | @@ -451,7 +465,7 @@ |
686 | s.assertNoScopeChange(c, w0) |
687 | |
688 | // ru1 leaves; check event. |
689 | - assertInScope(c, pr.ru1) |
690 | + assertJoined(c, pr.ru1) |
691 | err = pr.ru1.LeaveScope() |
692 | c.Assert(err, gc.IsNil) |
693 | s.assertScopeChange(c, w0, nil, []string{"riak/1"}) |
694 | @@ -489,7 +503,7 @@ |
695 | s.assertScopeChange(c, w, []string{"mysql/0"}, nil) |
696 | } |
697 | s.assertNoScopeChange(c, ws...) |
698 | - assertInScope(c, prr.pru0) |
699 | + assertJoined(c, prr.pru0) |
700 | |
701 | // req0 enters; check detected only by pro RUs. |
702 | assertNotInScope(c, prr.rru0) |
703 | @@ -502,7 +516,7 @@ |
704 | s.assertScopeChange(c, w, []string{"wordpress/0"}, nil) |
705 | } |
706 | s.assertNoScopeChange(c, ws...) |
707 | - assertInScope(c, prr.rru0) |
708 | + assertJoined(c, prr.rru0) |
709 | |
710 | // Stop watches; remaining RUs enter. |
711 | for _, w := range ws { |
712 | @@ -511,11 +525,11 @@ |
713 | assertNotInScope(c, prr.pru1) |
714 | err = prr.pru1.EnterScope(nil) |
715 | c.Assert(err, gc.IsNil) |
716 | - assertInScope(c, prr.pru1) |
717 | + assertJoined(c, prr.pru1) |
718 | assertNotInScope(c, prr.rru1) |
719 | err = prr.rru1.EnterScope(nil) |
720 | c.Assert(err, gc.IsNil) |
721 | - assertInScope(c, prr.rru0) |
722 | + assertJoined(c, prr.rru0) |
723 | |
724 | // Start new watches, check initial events. |
725 | ws = prr.watches() |
726 | @@ -531,7 +545,7 @@ |
727 | s.assertNoScopeChange(c, ws...) |
728 | |
729 | // pru0 leaves; check detected only by req RUs. |
730 | - assertInScope(c, prr.pru0) |
731 | + assertJoined(c, prr.pru0) |
732 | err = prr.pru0.LeaveScope() |
733 | c.Assert(err, gc.IsNil) |
734 | for _, w := range rws() { |
735 | @@ -541,7 +555,7 @@ |
736 | assertNotInScope(c, prr.pru0) |
737 | |
738 | // rru0 leaves; check detected only by pro RUs. |
739 | - assertInScope(c, prr.rru0) |
740 | + assertJoined(c, prr.rru0) |
741 | err = prr.rru0.LeaveScope() |
742 | c.Assert(err, gc.IsNil) |
743 | for _, w := range pws() { |
744 | @@ -570,7 +584,7 @@ |
745 | c.Assert(err, gc.IsNil) |
746 | s.assertScopeChange(c, ws[2], []string{"mysql/0"}, nil) |
747 | s.assertNoScopeChange(c, ws...) |
748 | - assertInScope(c, prr.pru0) |
749 | + assertJoined(c, prr.pru0) |
750 | |
751 | // req1 enters; check detected only by same-container pro. |
752 | assertNotInScope(c, prr.rru1) |
753 | @@ -578,7 +592,7 @@ |
754 | c.Assert(err, gc.IsNil) |
755 | s.assertScopeChange(c, ws[1], []string{"logging/1"}, nil) |
756 | s.assertNoScopeChange(c, ws...) |
757 | - assertInScope(c, prr.rru1) |
758 | + assertJoined(c, prr.rru1) |
759 | |
760 | // Stop watches; remaining RUs enter scope. |
761 | for _, w := range ws { |
762 | @@ -601,11 +615,11 @@ |
763 | s.assertScopeChange(c, ws[2], []string{"mysql/0"}, nil) |
764 | s.assertScopeChange(c, ws[3], []string{"mysql/1"}, nil) |
765 | s.assertNoScopeChange(c, ws...) |
766 | - assertInScope(c, prr.pru1) |
767 | - assertInScope(c, prr.rru0) |
768 | + assertJoined(c, prr.pru1) |
769 | + assertJoined(c, prr.rru0) |
770 | |
771 | // pru0 leaves; check detected only by same-container req. |
772 | - assertInScope(c, prr.pru0) |
773 | + assertJoined(c, prr.pru0) |
774 | err = prr.pru0.LeaveScope() |
775 | c.Assert(err, gc.IsNil) |
776 | s.assertScopeChange(c, ws[2], nil, []string{"mysql/0"}) |
777 | @@ -613,7 +627,7 @@ |
778 | assertNotInScope(c, prr.pru0) |
779 | |
780 | // rru0 leaves; check detected only by same-container pro. |
781 | - assertInScope(c, prr.rru0) |
782 | + assertJoined(c, prr.rru0) |
783 | err = prr.rru0.LeaveScope() |
784 | c.Assert(err, gc.IsNil) |
785 | s.assertScopeChange(c, ws[0], nil, []string{"logging/0"}) |
786 | @@ -678,6 +692,7 @@ |
787 | s.assertScopeChange(c, w0, nil, []string{"wordpress/0"}) |
788 | s.assertNoScopeChange(c, w0) |
789 | assertInScope(c, prr.rru0) |
790 | + assertNotJoined(c, prr.rru0) |
791 | |
792 | // rru1 leaves, and the relation is destroyed; it's not removed, because |
793 | // rru0 keeps it alive until it really leaves scope. |
794 | |
795 | === modified file 'state/service.go' |
796 | --- state/service.go 2014-05-13 04:30:48 +0000 |
797 | +++ state/service.go 2014-05-22 09:18:59 +0000 |
798 | @@ -189,7 +189,7 @@ |
799 | // about is that *some* unit is, or is not, keeping the service from |
800 | // being removed: the difference between 1 unit and 1000 is irrelevant. |
801 | if s.doc.UnitCount > 0 { |
802 | - ops = append(ops, s.st.newCleanupOp("units", s.doc.Name+"/")) |
803 | + ops = append(ops, s.st.newCleanupOp(cleanupUnitsForDyingService, s.doc.Name+"/")) |
804 | notLastRefs = append(notLastRefs, bson.D{{"unitcount", bson.D{{"$gt", 0}}}}...) |
805 | } else { |
806 | notLastRefs = append(notLastRefs, bson.D{{"unitcount", 0}}...) |
807 | |
808 | === modified file 'state/service_test.go' |
809 | --- state/service_test.go 2014-05-21 00:15:27 +0000 |
810 | +++ state/service_test.go 2014-05-22 09:18:59 +0000 |
811 | @@ -1102,6 +1102,13 @@ |
812 | } |
813 | } |
814 | |
815 | + // Check for queued unit cleanups, and run them. |
816 | + dirty, err = s.State.NeedsCleanup() |
817 | + c.Assert(err, gc.IsNil) |
818 | + c.Assert(dirty, gc.Equals, true) |
819 | + err = s.State.Cleanup() |
820 | + c.Assert(err, gc.IsNil) |
821 | + |
822 | // Check we're now clean. |
823 | dirty, err = s.State.NeedsCleanup() |
824 | c.Assert(err, gc.IsNil) |
825 | |
826 | === modified file 'state/unit.go' |
827 | --- state/unit.go 2014-05-13 04:30:48 +0000 |
828 | +++ state/unit.go 2014-05-22 09:18:59 +0000 |
829 | @@ -322,12 +322,13 @@ |
830 | // the number of tests that have to change and defer that improvement to |
831 | // its own CL. |
832 | minUnitsOp := minUnitsTriggerOp(u.st, u.ServiceName()) |
833 | + cleanupOp := u.st.newCleanupOp(cleanupDyingUnit, u.doc.Name) |
834 | setDyingOps := []txn.Op{{ |
835 | C: u.st.units.Name, |
836 | Id: u.doc.Name, |
837 | Assert: isAliveDoc, |
838 | Update: bson.D{{"$set", bson.D{{"life", Dying}}}}, |
839 | - }, minUnitsOp} |
840 | + }, cleanupOp, minUnitsOp} |
841 | if u.doc.Principal != "" { |
842 | return setDyingOps, nil |
843 | } else if len(u.doc.Subordinates) != 0 { |
844 | @@ -482,25 +483,43 @@ |
845 | return names |
846 | } |
847 | |
848 | -// JoinedRelations returns the relations for which the unit is in scope. |
849 | -func (u *Unit) JoinedRelations() ([]*Relation, error) { |
850 | +// RelationsJoined returns the relations for which the unit has entered scope |
851 | +// and neither left it nor prepared to leave it |
852 | +func (u *Unit) RelationsJoined() ([]*Relation, error) { |
853 | + return u.relations(func(ru *RelationUnit) (bool, error) { |
854 | + return ru.Joined() |
855 | + }) |
856 | +} |
857 | + |
858 | +// RelationsInScope returns the relations for which the unit has entered scope |
859 | +// and not left it. |
860 | +func (u *Unit) RelationsInScope() ([]*Relation, error) { |
861 | + return u.relations(func(ru *RelationUnit) (bool, error) { |
862 | + return ru.InScope() |
863 | + }) |
864 | +} |
865 | + |
866 | +type relationPredicate func(ru *RelationUnit) (bool, error) |
867 | + |
868 | +// relations implements RelationsJoined and RelationsInScope. |
869 | +func (u *Unit) relations(predicate relationPredicate) ([]*Relation, error) { |
870 | candidates, err := serviceRelations(u.st, u.doc.Service) |
871 | if err != nil { |
872 | return nil, err |
873 | } |
874 | - var joinedRelations []*Relation |
875 | + var filtered []*Relation |
876 | for _, relation := range candidates { |
877 | relationUnit, err := relation.Unit(u) |
878 | if err != nil { |
879 | return nil, err |
880 | } |
881 | - if inScope, err := relationUnit.InScope(); err != nil { |
882 | + if include, err := predicate(relationUnit); err != nil { |
883 | return nil, err |
884 | - } else if inScope { |
885 | - joinedRelations = append(joinedRelations, relation) |
886 | + } else if include { |
887 | + filtered = append(filtered, relation) |
888 | } |
889 | } |
890 | - return joinedRelations, nil |
891 | + return filtered, nil |
892 | } |
893 | |
894 | // DeployerTag returns the tag of the agent responsible for deploying |
895 | |
896 | === modified file 'state/unit_test.go' |
897 | --- state/unit_test.go 2014-05-13 04:30:48 +0000 |
898 | +++ state/unit_test.go 2014-05-22 09:18:59 +0000 |
899 | @@ -1018,7 +1018,7 @@ |
900 | c.Assert(principal, gc.Equals, "") |
901 | } |
902 | |
903 | -func (s *UnitSuite) TestJoinedRelations(c *gc.C) { |
904 | +func (s *UnitSuite) TestRelations(c *gc.C) { |
905 | wordpress0 := s.unit |
906 | mysql := s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql")) |
907 | mysql0, err := mysql.AddUnit() |
908 | @@ -1028,35 +1028,48 @@ |
909 | rel, err := s.State.AddRelation(eps...) |
910 | c.Assert(err, gc.IsNil) |
911 | |
912 | - assertJoinedRelations := func(unit *state.Unit, expect ...*state.Relation) { |
913 | - actual, err := unit.JoinedRelations() |
914 | - c.Assert(err, gc.IsNil) |
915 | + assertEquals := func(actual, expect []*state.Relation) { |
916 | c.Assert(actual, gc.HasLen, len(expect)) |
917 | for i, a := range actual { |
918 | c.Assert(a.Id(), gc.Equals, expect[i].Id()) |
919 | } |
920 | } |
921 | - assertJoinedRelations(wordpress0) |
922 | - assertJoinedRelations(mysql0) |
923 | + assertRelationsJoined := func(unit *state.Unit, expect ...*state.Relation) { |
924 | + actual, err := unit.RelationsJoined() |
925 | + c.Assert(err, gc.IsNil) |
926 | + assertEquals(actual, expect) |
927 | + } |
928 | + assertRelationsInScope := func(unit *state.Unit, expect ...*state.Relation) { |
929 | + actual, err := unit.RelationsInScope() |
930 | + c.Assert(err, gc.IsNil) |
931 | + assertEquals(actual, expect) |
932 | + } |
933 | + assertRelations := func(unit *state.Unit, expect ...*state.Relation) { |
934 | + assertRelationsInScope(unit, expect...) |
935 | + assertRelationsJoined(unit, expect...) |
936 | + } |
937 | + assertRelations(wordpress0) |
938 | + assertRelations(mysql0) |
939 | |
940 | mysql0ru, err := rel.Unit(mysql0) |
941 | c.Assert(err, gc.IsNil) |
942 | err = mysql0ru.EnterScope(nil) |
943 | c.Assert(err, gc.IsNil) |
944 | - assertJoinedRelations(wordpress0) |
945 | - assertJoinedRelations(mysql0, rel) |
946 | + assertRelations(wordpress0) |
947 | + assertRelations(mysql0, rel) |
948 | |
949 | wordpress0ru, err := rel.Unit(wordpress0) |
950 | c.Assert(err, gc.IsNil) |
951 | err = wordpress0ru.EnterScope(nil) |
952 | c.Assert(err, gc.IsNil) |
953 | - assertJoinedRelations(wordpress0, rel) |
954 | - assertJoinedRelations(mysql0, rel) |
955 | + assertRelations(wordpress0, rel) |
956 | + assertRelations(mysql0, rel) |
957 | |
958 | - err = mysql0ru.LeaveScope() |
959 | + err = mysql0ru.PrepareLeaveScope() |
960 | c.Assert(err, gc.IsNil) |
961 | - assertJoinedRelations(wordpress0, rel) |
962 | - assertJoinedRelations(mysql0) |
963 | + assertRelations(wordpress0, rel) |
964 | + assertRelationsInScope(mysql0, rel) |
965 | + assertRelationsJoined(mysql0) |
966 | } |
967 | |
968 | func (s *UnitSuite) TestRemove(c *gc.C) { |