Merge lp:~fwereade/juju-core/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: 2755
Proposed branch: lp:~fwereade/juju-core/prepare-leave-scope
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 421 lines (+249/-66)
3 files modified
state/relationunit.go (+29/-3)
state/relationunit_test.go (+79/-0)
state/watcher.go (+141/-63)
To merge this branch: bzr merge lp:~fwereade/juju-core/prepare-leave-scope
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+181065@code.launchpad.net

Commit message

state: RelationUnit.PrepareLeaveScope

Added a mechanism whereby units can report imminent departure from a
relation, such that related ones see them disappear immediately and
reconfigure to ignore them *before* they actually disappear for good.

https://codereview.appspot.com/12841044/

Description of the change

state: RelationUnit.PrepareLeaveScope

Added a mechanism whereby units can report imminent departure from a
relation, such that related ones see them disappear immediately and
reconfigure to ignore them *before* they actually disappear for good.

Exactly when the mechanism should be invoked remains open; it's clear
that it's a good idea for peers and providers, but I'm not sure it's
sensible for providers to see the departure of requirers until it actually
happens (because providers may have set up creds for the specific
requirers, and it'd be unhelpful to cut off their access before they're
ready).

Thoughts?

https://codereview.appspot.com/12841044/

To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

Reviewers: mp+181065_code.launchpad.net,

Message:
Please take a look.

Description:
state: RelationUnit.PrepareLeaveScope

Added a mechanism whereby units can report imminent departure from a
relation, such that related ones see them disappear immediately and
reconfigure to ignore them *before* they actually disappear for good.

Exactly when the mechanism should be invoked remains open; it's clear
that it's a good idea for peers and providers, but I'm not sure it's
sensible for providers to see the departure of requirers until it
actually
happens (because providers may have set up creds for the specific
requirers, and it'd be unhelpful to cut off their access before they're
ready).

Thoughts?

https://code.launchpad.net/~fwereade/juju-core/prepare-leave-scope/+merge/181065

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/12841044/

Affected files:
   A [revision details]
   M state/relationunit.go
   M state/relationunit_test.go
   M state/watcher.go

Revision history for this message
Ian Booth (wallyworld) wrote :

The code looks sane. I must confess I don't fully grok the higher level
use cases around how the functionality will be consumed. LGTM

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

https://codereview.appspot.com/12841044/diff/1/state/relationunit_test.go#newcode662
state/relationunit_test.go:662: c.Assert(prr.rru0.InScope(), gc.Equals,
true)
Since it's one line, could we also test that rru1 is still in scope,
just because?

https://codereview.appspot.com/12841044/

Revision history for this message
John A Meinel (jameinel) wrote :

Can we get a final decision on whether we want to finish this off and land it, or reject it so it gets out of the review queue?

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

LGTM. Doc changes needed anywhere?

https://codereview.appspot.com/12841044/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'state/relationunit.go'
2--- state/relationunit.go 2014-04-17 12:47:50 +0000
3+++ state/relationunit.go 2014-05-14 15:04:58 +0000
4@@ -121,7 +121,7 @@
5 C: ru.st.relationScopes.Name,
6 Id: ruKey,
7 Assert: txn.DocMissing,
8- Insert: relationScopeDoc{ruKey},
9+ Insert: relationScopeDoc{Key: ruKey},
10 })
11
12 // * If the unit should have a subordinate, and does not, create it.
13@@ -223,6 +223,27 @@
14 }}, lDoc.Id, nil
15 }
16
17+// PrepareLeaveScope causes the unit to be reported as departed by watchers,
18+// but does not *actually* leave the scope, to avoid triggering relation
19+// cleanup.
20+func (ru *RelationUnit) PrepareLeaveScope() error {
21+ key, err := ru.key(ru.unit.Name())
22+ if err != nil {
23+ return err
24+ }
25+ if count, err := ru.st.relationScopes.FindId(key).Count(); err != nil {
26+ return err
27+ } else if count == 0 {
28+ return nil
29+ }
30+ ops := []txn.Op{{
31+ C: ru.st.relationScopes.Name,
32+ Id: key,
33+ Update: bson.D{{"$set", bson.D{{"departing", true}}}},
34+ }}
35+ return ru.st.runTransaction(ops)
36+}
37+
38 // LeaveScope signals that the unit has left its scope in the relation.
39 // After the unit has left its relation scope, it is no longer a member
40 // of the relation; if the relation is dying when its last member unit
41@@ -376,10 +397,15 @@
42 // relationScopeDoc represents a unit which is in a relation scope.
43 // The relation, container, role, and unit are all encoded in the key.
44 type relationScopeDoc struct {
45- Key string `bson:"_id"`
46+ Key string `bson:"_id"`
47+ Departing bool
48 }
49
50 func (d *relationScopeDoc) unitName() string {
51- parts := strings.Split(d.Key, "#")
52+ return unitNameFromScopeKey(d.Key)
53+}
54+
55+func unitNameFromScopeKey(key string) string {
56+ parts := strings.Split(key, "#")
57 return parts[len(parts)-1]
58 }
59
60=== modified file 'state/relationunit_test.go'
61--- state/relationunit_test.go 2014-04-14 12:36:13 +0000
62+++ state/relationunit_test.go 2014-05-14 15:04:58 +0000
63@@ -621,6 +621,85 @@
64 assertNotInScope(c, prr.rru0)
65 }
66
67+func (s *RelationUnitSuite) TestCoalesceWatchScope(c *gc.C) {
68+ pr := NewPeerRelation(c, s.State)
69+
70+ // Test empty initial event.
71+ w0 := pr.ru0.WatchScope()
72+ defer testing.AssertStop(c, w0)
73+ s.assertScopeChange(c, w0, nil, nil)
74+ s.assertNoScopeChange(c, w0)
75+
76+ // ru1 and ru2 enter; check changes observed together.
77+ err := pr.ru1.EnterScope(nil)
78+ c.Assert(err, gc.IsNil)
79+ err = pr.ru2.EnterScope(nil)
80+ c.Assert(err, gc.IsNil)
81+ s.assertScopeChange(c, w0, []string{"riak/1", "riak/2"}, nil)
82+ s.assertNoScopeChange(c, w0)
83+
84+ // ru1 leaves and re-enters; check no change observed.
85+ err = pr.ru1.LeaveScope()
86+ c.Assert(err, gc.IsNil)
87+ err = pr.ru1.EnterScope(nil)
88+ c.Assert(err, gc.IsNil)
89+ s.assertNoScopeChange(c, w0)
90+
91+ // ru1 and ru2 leave; check changes observed together.
92+ err = pr.ru1.LeaveScope()
93+ c.Assert(err, gc.IsNil)
94+ err = pr.ru2.LeaveScope()
95+ c.Assert(err, gc.IsNil)
96+ s.assertScopeChange(c, w0, nil, []string{"riak/1", "riak/2"})
97+ s.assertNoScopeChange(c, w0)
98+}
99+
100+func (s *RelationUnitSuite) TestPrepareLeaveScope(c *gc.C) {
101+ prr := NewProReqRelation(c, &s.ConnSuite, charm.ScopeGlobal)
102+
103+ // Test empty initial event.
104+ w0 := prr.pru0.WatchScope()
105+ defer testing.AssertStop(c, w0)
106+ s.assertScopeChange(c, w0, nil, nil)
107+ s.assertNoScopeChange(c, w0)
108+
109+ // rru0 and rru1 enter; check changes.
110+ err := prr.rru0.EnterScope(nil)
111+ c.Assert(err, gc.IsNil)
112+ err = prr.rru1.EnterScope(nil)
113+ c.Assert(err, gc.IsNil)
114+ s.assertScopeChange(c, w0, []string{"wordpress/0", "wordpress/1"}, nil)
115+ s.assertNoScopeChange(c, w0)
116+
117+ // rru0 notifies that it will leave soon; it's reported as departed by the
118+ // watcher, but InScope remains accurate.
119+ err = prr.rru0.PrepareLeaveScope()
120+ c.Assert(err, gc.IsNil)
121+ s.assertScopeChange(c, w0, nil, []string{"wordpress/0"})
122+ s.assertNoScopeChange(c, w0)
123+ assertInScope(c, prr.rru0)
124+
125+ // rru1 leaves, and the relation is destroyed; it's not removed, because
126+ // rru0 keeps it alive until it really leaves scope.
127+ err = prr.rru1.LeaveScope()
128+ c.Assert(err, gc.IsNil)
129+ s.assertScopeChange(c, w0, nil, []string{"wordpress/1"})
130+ s.assertNoScopeChange(c, w0)
131+ err = prr.rel.Destroy()
132+ c.Assert(err, gc.IsNil)
133+ err = prr.rel.Refresh()
134+ c.Assert(err, gc.IsNil)
135+
136+ // rru0 really leaves; the relation is cleaned up.
137+ err = prr.rru0.LeaveScope()
138+ c.Assert(err, gc.IsNil)
139+ err = prr.rel.Destroy()
140+ c.Assert(err, gc.IsNil)
141+ s.assertNoScopeChange(c, w0)
142+ err = prr.rel.Refresh()
143+ c.Assert(err, jc.Satisfies, errors.IsNotFound)
144+}
145+
146 func (s *RelationUnitSuite) assertScopeChange(c *gc.C, w *state.RelationScopeWatcher, entered, left []string) {
147 s.State.StartSync()
148 select {
149
150=== modified file 'state/watcher.go'
151--- state/watcher.go 2014-04-30 23:18:40 +0000
152+++ state/watcher.go 2014-05-14 15:04:58 +0000
153@@ -448,14 +448,54 @@
154 return w.out
155 }
156
157-// RelationScopeWatcher observes changes to the set of units
158-// in a particular relation scope.
159-type RelationScopeWatcher struct {
160- commonWatcher
161- prefix string
162- ignore string
163- knownUnits set.Strings
164- out chan *RelationScopeChange
165+// scopeInfo holds a RelationScopeWatcher's last-delivered state, and any
166+// known but undelivered changes thereto.
167+type scopeInfo struct {
168+ base map[string]bool
169+ diff map[string]bool
170+}
171+
172+func (info *scopeInfo) add(name string) {
173+ if info.base[name] {
174+ delete(info.diff, name)
175+ } else {
176+ info.diff[name] = true
177+ }
178+}
179+
180+func (info *scopeInfo) remove(name string) {
181+ if info.base[name] {
182+ info.diff[name] = false
183+ } else {
184+ delete(info.diff, name)
185+ }
186+}
187+
188+func (info *scopeInfo) commit() {
189+ for name, change := range info.diff {
190+ if change {
191+ info.base[name] = true
192+ } else {
193+ delete(info.base, name)
194+ }
195+ }
196+ info.diff = map[string]bool{}
197+}
198+
199+func (info *scopeInfo) hasChanges() bool {
200+ return len(info.diff) > 0
201+}
202+
203+func (info *scopeInfo) changes() *RelationScopeChange {
204+ ch := &RelationScopeChange{}
205+ for name, change := range info.diff {
206+ if change {
207+ ch.Entered = append(ch.Entered, name)
208+ } else {
209+ ch.Left = append(ch.Left, name)
210+ }
211+ }
212+ return ch
213 }
214
215 var _ Watcher = (*RelationScopeWatcher)(nil)
216@@ -467,6 +507,15 @@
217 Left []string
218 }
219
220+// RelationScopeWatcher observes changes to the set of units
221+// in a particular relation scope.
222+type RelationScopeWatcher struct {
223+ commonWatcher
224+ prefix string
225+ ignore string
226+ out chan *RelationScopeChange
227+}
228+
229 func newRelationScopeWatcher(st *State, scope, ignore string) *RelationScopeWatcher {
230 w := &RelationScopeWatcher{
231 commonWatcher: commonWatcher{st: st},
232@@ -489,58 +538,75 @@
233 return w.out
234 }
235
236-func (changes *RelationScopeChange) isEmpty() bool {
237- return len(changes.Entered)+len(changes.Left) == 0
238-}
239-
240-func (w *RelationScopeWatcher) mergeChange(changes *RelationScopeChange, ch watcher.Change) (err error) {
241- doc := &relationScopeDoc{ch.Id.(string)}
242- if !strings.HasPrefix(doc.Key, w.prefix) {
243- return nil
244- }
245- name := doc.unitName()
246- if name == w.ignore {
247- return nil
248- }
249- if ch.Revno == -1 {
250- if w.knownUnits.Contains(name) {
251- changes.Left = append(changes.Left, name)
252- w.knownUnits.Remove(name)
253- }
254- return nil
255- }
256- if !w.knownUnits.Contains(name) {
257- changes.Entered = append(changes.Entered, name)
258- w.knownUnits.Add(name)
259- }
260- return nil
261-}
262-
263-func (w *RelationScopeWatcher) getInitialEvent() (initial *RelationScopeChange, err error) {
264- changes := &RelationScopeChange{}
265+// initialInfo returns an uncommitted scopeInfo with the current set of units.
266+func (w *RelationScopeWatcher) initialInfo() (info *scopeInfo, err error) {
267 docs := []relationScopeDoc{}
268- sel := bson.D{{"_id", bson.D{{"$regex", "^" + w.prefix}}}}
269- err = w.st.relationScopes.Find(sel).All(&docs)
270- if err != nil {
271+ sel := bson.D{
272+ {"_id", bson.D{{"$regex", "^" + w.prefix}}},
273+ {"departing", bson.D{{"$ne", true}}},
274+ }
275+ if err = w.st.relationScopes.Find(sel).All(&docs); err != nil {
276 return nil, err
277 }
278+ info = &scopeInfo{
279+ base: map[string]bool{},
280+ diff: map[string]bool{},
281+ }
282 for _, doc := range docs {
283 if name := doc.unitName(); name != w.ignore {
284- changes.Entered = append(changes.Entered, name)
285- w.knownUnits.Add(name)
286- }
287- }
288- return changes, nil
289+ info.add(name)
290+ }
291+ }
292+ return info, nil
293+}
294+
295+// mergeChanges updates info with the contents of the changes in ids. False
296+// values are always treated as removed; true values cause the associated
297+// document to be read, and whether it's treated as added or removed depends
298+// on the value of the document's Departing field.
299+func (w *RelationScopeWatcher) mergeChanges(info *scopeInfo, ids map[interface{}]bool) (err error) {
300+ existIds := []string{}
301+ for id_, exists := range ids {
302+ id, ok := id_.(string)
303+ if !ok {
304+ logger.Warningf("ignoring bad relation scope id: %#v", id_)
305+ continue
306+ }
307+ if exists {
308+ existIds = append(existIds, id)
309+ } else {
310+ doc := &relationScopeDoc{Key: id}
311+ info.remove(doc.unitName())
312+ }
313+ }
314+ docs := []relationScopeDoc{}
315+ sel := bson.D{{"_id", bson.D{{"$in", existIds}}}}
316+ if err := w.st.relationScopes.Find(sel).All(&docs); err != nil {
317+ return err
318+ }
319+ for _, doc := range docs {
320+ name := doc.unitName()
321+ if doc.Departing {
322+ info.remove(name)
323+ } else if name != w.ignore {
324+ info.add(name)
325+ }
326+ }
327+ return nil
328 }
329
330 func (w *RelationScopeWatcher) loop() error {
331- ch := make(chan watcher.Change)
332- w.st.watcher.WatchCollection(w.st.relationScopes.Name, ch)
333- defer w.st.watcher.UnwatchCollection(w.st.relationScopes.Name, ch)
334- changes, err := w.getInitialEvent()
335+ in := make(chan watcher.Change)
336+ filter := func(key interface{}) bool {
337+ return strings.HasPrefix(key.(string), w.prefix)
338+ }
339+ w.st.watcher.WatchCollectionWithFilter(w.st.relationScopes.Name, in, filter)
340+ defer w.st.watcher.UnwatchCollection(w.st.relationScopes.Name, in)
341+ info, err := w.initialInfo()
342 if err != nil {
343 return err
344 }
345+ sent := false
346 out := w.out
347 for {
348 select {
349@@ -548,15 +614,22 @@
350 return stateWatcherDeadError(w.st.watcher.Err())
351 case <-w.tomb.Dying():
352 return tomb.ErrDying
353- case c := <-ch:
354- if err := w.mergeChange(changes, c); err != nil {
355+ case ch := <-in:
356+ latest, ok := collect(ch, in, w.tomb.Dying())
357+ if !ok {
358+ return tomb.ErrDying
359+ }
360+ if err := w.mergeChanges(info, latest); err != nil {
361 return err
362 }
363- if !changes.isEmpty() {
364+ if info.hasChanges() {
365 out = w.out
366+ } else if sent {
367+ out = nil
368 }
369- case out <- changes:
370- changes = &RelationScopeChange{}
371+ case out <- info.changes():
372+ info.commit()
373+ sent = true
374 out = nil
375 }
376 }
377@@ -607,6 +680,15 @@
378 return len(changes.Changed)+len(changes.Departed) == 0
379 }
380
381+func setRelationUnitChangeVersion(changes *params.RelationUnitsChange, key string, revno int64) {
382+ name := unitNameFromScopeKey(key)
383+ settings := params.UnitSettings{Version: revno}
384+ if changes.Changed == nil {
385+ changes.Changed = map[string]params.UnitSettings{}
386+ }
387+ changes.Changed[name] = settings
388+}
389+
390 // mergeSettings reads the relation settings node for the unit with the
391 // supplied id, and sets a value in the Changed field keyed on the unit's
392 // name. It returns the mgo/txn revision number of the settings node.
393@@ -615,13 +697,7 @@
394 if err != nil {
395 return -1, err
396 }
397- name := (&relationScopeDoc{key}).unitName()
398- settings := params.UnitSettings{Version: node.txnRevno}
399- if changes.Changed == nil {
400- changes.Changed = map[string]params.UnitSettings{name: settings}
401- } else {
402- changes.Changed[name] = settings
403- }
404+ setRelationUnitChangeVersion(changes, key, node.txnRevno)
405 return node.txnRevno, nil
406 }
407
408@@ -696,9 +772,11 @@
409 out = nil
410 }
411 case c := <-w.updates:
412- if _, err = w.mergeSettings(&changes, c.Id.(string)); err != nil {
413- return err
414+ id, ok := c.Id.(string)
415+ if !ok {
416+ logger.Warningf("ignoring bad relation scope id: %#v", c.Id)
417 }
418+ setRelationUnitChangeVersion(&changes, id, c.Revno)
419 out = w.out
420 case out <- changes:
421 sentInitial = true

Subscribers

People subscribed via source and target branches

to status/vote changes: