Merge lp:~fwereade/juju-core/use-prepare-leave-scope 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: 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
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.JoinedRelations API, because I want to
    wait for API versioning

https://codereview.appspot.com/94540043/

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.JoinedRelations API, because I want to
    wait for API versioning

https://codereview.appspot.com/94540043/

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) {

Subscribers

People subscribed via source and target branches

to status/vote changes: