launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/uniter/relation/hookqueue.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package relation 5 6 import ( 7 "launchpad.net/tomb" 8 "sort" 9 10 "launchpad.net/juju-core/charm/hooks" 11 "launchpad.net/juju-core/state/api/params" 12 "launchpad.net/juju-core/state/watcher" 13 "launchpad.net/juju-core/worker/uniter/hook" 14 ) 15 16 // HookQueue is the minimal interface implemented by both AliveHookQueue and 17 // DyingHookQueue. 18 type HookQueue interface { 19 hookQueue() 20 Stop() error 21 } 22 23 // RelationUnitsWatcher is used to enable deterministic testing of 24 // AliveHookQueue, by supplying a reliable stream of RelationUnitsChange 25 // events; usually, it will be a *state.RelationUnitsWatcher. 26 type RelationUnitsWatcher interface { 27 Err() error 28 Stop() error 29 Changes() <-chan params.RelationUnitsChange 30 } 31 32 // AliveHookQueue aggregates values obtained from a relation units watcher 33 // and sends out details about hooks that must be executed in the unit. 34 type AliveHookQueue struct { 35 tomb tomb.Tomb 36 w RelationUnitsWatcher 37 out chan<- hook.Info 38 relationId int 39 40 // info holds information about all units that were added to the 41 // queue and haven't had a "relation-departed" event popped. This 42 // means the unit may be in info and not currently in the queue 43 // itself. 44 info map[string]*unitInfo 45 46 // head and tail are the ends of the queue. 47 head, tail *unitInfo 48 49 // changedPending, if not empty, indicates that the most recently 50 // popped event was a "relation-joined" for the named unit, and 51 // therefore that the next event must be a "relation-changed" 52 // for that same unit. 53 // If changedPending is not empty, the queue is considered non- 54 // empty, even if head is nil. 55 changedPending string 56 } 57 58 // unitInfo holds unit information for management by AliveHookQueue. 59 type unitInfo struct { 60 // unit holds the name of the unit. 61 unit string 62 63 // version and settings hold the most recent settings known 64 // to the AliveHookQueue. 65 version int64 66 67 // joined is set to true when a "relation-joined" is popped for this unit. 68 joined bool 69 70 // hookKind holds the current idea of the next hook that should 71 // be run for the unit, and is empty if and only if the unit 72 // is not queued. 73 hookKind hooks.Kind 74 75 // prev and next define the position in the queue of the 76 // unit's next hook. 77 prev, next *unitInfo 78 } 79 80 // NewAliveHookQueue returns a new AliveHookQueue that aggregates the values 81 // obtained from the w watcher and sends into out the details about hooks that 82 // must be executed in the unit. It guarantees that the stream of hooks will 83 // respect the guarantees Juju makes about hook execution order. If any values 84 // have previously been received from w's Changes channel, the AliveHookQueue's 85 // behaviour is undefined. 86 func NewAliveHookQueue(initial *State, out chan<- hook.Info, w RelationUnitsWatcher) *AliveHookQueue { 87 q := &AliveHookQueue{ 88 w: w, 89 out: out, 90 relationId: initial.RelationId, 91 info: map[string]*unitInfo{}, 92 } 93 go q.loop(initial) 94 return q 95 } 96 97 func (q *AliveHookQueue) loop(initial *State) { 98 defer q.tomb.Done() 99 defer watcher.Stop(q.w, &q.tomb) 100 101 // Consume initial event, and reconcile with initial state, by inserting 102 // a new RelationUnitsChange before the initial event, which schedules 103 // every missing unit for immediate departure before anything else happens 104 // (apart from a single potential required post-joined changed event). 105 ch1, ok := <-q.w.Changes() 106 if !ok { 107 q.tomb.Kill(watcher.MustErr(q.w)) 108 return 109 } 110 if len(ch1.Departed) != 0 { 111 panic("AliveHookQueue must be started with a fresh RelationUnitsWatcher") 112 } 113 q.changedPending = initial.ChangedPending 114 ch0 := params.RelationUnitsChange{} 115 for unit, version := range initial.Members { 116 q.info[unit] = &unitInfo{ 117 unit: unit, 118 version: version, 119 joined: true, 120 } 121 if _, found := ch1.Changed[unit]; !found { 122 ch0.Departed = append(ch0.Departed, unit) 123 } 124 } 125 q.update(ch0) 126 q.update(ch1) 127 128 var next hook.Info 129 var out chan<- hook.Info 130 for { 131 if q.empty() { 132 out = nil 133 } else { 134 out = q.out 135 next = q.next() 136 } 137 select { 138 case <-q.tomb.Dying(): 139 return 140 case ch, ok := <-q.w.Changes(): 141 if !ok { 142 q.tomb.Kill(watcher.MustErr(q.w)) 143 return 144 } 145 q.update(ch) 146 case out <- next: 147 q.pop() 148 } 149 } 150 } 151 152 func (q *AliveHookQueue) hookQueue() { 153 panic("interface sentinel method, do not call") 154 } 155 156 // Stop stops the AliveHookQueue and returns any errors encountered during 157 // operation or while shutting down. 158 func (q *AliveHookQueue) Stop() error { 159 q.tomb.Kill(nil) 160 return q.tomb.Wait() 161 } 162 163 // empty returns true if the queue is empty. 164 func (q *AliveHookQueue) empty() bool { 165 return q.head == nil && q.changedPending == "" 166 } 167 168 // update modifies the queue such that the hook.Info values it sends will 169 // reflect the supplied change. 170 func (q *AliveHookQueue) update(ruc params.RelationUnitsChange) { 171 // Enforce consistent addition order, mainly for testing purposes. 172 changedUnits := []string{} 173 for unit := range ruc.Changed { 174 changedUnits = append(changedUnits, unit) 175 } 176 sort.Strings(changedUnits) 177 178 for _, unit := range changedUnits { 179 settings := ruc.Changed[unit] 180 info, found := q.info[unit] 181 if !found { 182 info = &unitInfo{unit: unit} 183 q.info[unit] = info 184 q.queue(unit, hooks.RelationJoined) 185 } else if info.hookKind != hooks.RelationJoined { 186 if settings.Version != info.version { 187 q.queue(unit, hooks.RelationChanged) 188 } else { 189 q.unqueue(unit) 190 } 191 } 192 info.version = settings.Version 193 } 194 195 for _, unit := range ruc.Departed { 196 if q.info[unit].hookKind == hooks.RelationJoined { 197 q.unqueue(unit) 198 } else { 199 q.queue(unit, hooks.RelationDeparted) 200 } 201 } 202 } 203 204 // pop advances the queue. It will panic if the queue is already empty. 205 func (q *AliveHookQueue) pop() { 206 if q.empty() { 207 panic("queue is empty") 208 } 209 if q.changedPending != "" { 210 if q.info[q.changedPending].hookKind == hooks.RelationChanged { 211 // We just ran this very hook; no sense keeping it queued. 212 q.unqueue(q.changedPending) 213 } 214 q.changedPending = "" 215 } else { 216 old := *q.head 217 q.unqueue(q.head.unit) 218 if old.hookKind == hooks.RelationJoined { 219 q.changedPending = old.unit 220 q.info[old.unit].joined = true 221 } else if old.hookKind == hooks.RelationDeparted { 222 delete(q.info, old.unit) 223 } 224 } 225 } 226 227 // next returns the next hook.Info value to send. 228 func (q *AliveHookQueue) next() hook.Info { 229 if q.empty() { 230 panic("queue is empty") 231 } 232 var unit string 233 var kind hooks.Kind 234 if q.changedPending != "" { 235 unit = q.changedPending 236 kind = hooks.RelationChanged 237 } else { 238 unit = q.head.unit 239 kind = q.head.hookKind 240 } 241 version := q.info[unit].version 242 return hook.Info{ 243 Kind: kind, 244 RelationId: q.relationId, 245 RemoteUnit: unit, 246 ChangeVersion: version, 247 } 248 } 249 250 // queue sets the next hook to be run for the named unit, and places it 251 // at the tail of the queue if it is not already queued. It will panic 252 // if the unit is not in q.info. 253 func (q *AliveHookQueue) queue(unit string, kind hooks.Kind) { 254 // If the unit is not in the queue, place it at the tail. 255 info := q.info[unit] 256 if info.hookKind == "" { 257 info.prev = q.tail 258 if q.tail != nil { 259 q.tail.next = info 260 } 261 q.tail = info 262 263 // If the queue is empty, the tail is also the head. 264 if q.head == nil { 265 q.head = info 266 } 267 } 268 info.hookKind = kind 269 } 270 271 // unqueue removes the named unit from the queue. It is fine to 272 // unqueue a unit that is not in the queue, but it will panic if 273 // the unit is not in q.info. 274 func (q *AliveHookQueue) unqueue(unit string) { 275 if q.head == nil { 276 // The queue is empty, nothing to do. 277 return 278 } 279 280 // Get the unit info and clear its next action. 281 info := q.info[unit] 282 if info.hookKind == "" { 283 // The unit is not in the queue, nothing to do. 284 return 285 } 286 info.hookKind = "" 287 288 // Update queue pointers. 289 if info.prev == nil { 290 q.head = info.next 291 } else { 292 info.prev.next = info.next 293 } 294 if info.next == nil { 295 q.tail = info.prev 296 } else { 297 info.next.prev = info.prev 298 } 299 info.prev = nil 300 info.next = nil 301 } 302 303 // DyingHookQueue is a hook queue that deals with a relation that is being 304 // shut down. It honours the obligations of an AliveHookQueue with respect to 305 // relation hook execution order; as soon as those obligations are fulfilled, 306 // it sends a "relation-departed" hook for every relation member, and finally a 307 // "relation-broken" hook for the relation itself. 308 type DyingHookQueue struct { 309 tomb tomb.Tomb 310 out chan<- hook.Info 311 relationId int 312 members map[string]int64 313 changedPending string 314 } 315 316 // NewDyingHookQueue returns a new DyingHookQueue that shuts down the state in 317 // initial. 318 func NewDyingHookQueue(initial *State, out chan<- hook.Info) *DyingHookQueue { 319 q := &DyingHookQueue{ 320 out: out, 321 relationId: initial.RelationId, 322 members: map[string]int64{}, 323 changedPending: initial.ChangedPending, 324 } 325 for m, v := range initial.Members { 326 q.members[m] = v 327 } 328 go q.loop() 329 return q 330 } 331 332 func (q *DyingHookQueue) loop() { 333 defer q.tomb.Done() 334 335 // Honour any expected relation-changed hook. 336 if q.changedPending != "" { 337 select { 338 case <-q.tomb.Dying(): 339 return 340 case q.out <- q.hookInfo(hooks.RelationChanged, q.changedPending): 341 } 342 } 343 344 // Depart in consistent order, mainly for testing purposes. 345 departs := []string{} 346 for m := range q.members { 347 departs = append(departs, m) 348 } 349 sort.Strings(departs) 350 for _, unit := range departs { 351 select { 352 case <-q.tomb.Dying(): 353 return 354 case q.out <- q.hookInfo(hooks.RelationDeparted, unit): 355 } 356 } 357 358 // Finally break the relation. 359 select { 360 case <-q.tomb.Dying(): 361 return 362 case q.out <- hook.Info{Kind: hooks.RelationBroken, RelationId: q.relationId}: 363 } 364 q.tomb.Kill(nil) 365 return 366 } 367 368 // hookInfo updates the queue's internal membership state according to the 369 // supplied information, and returns a hook.Info reflecting that change. 370 func (q *DyingHookQueue) hookInfo(kind hooks.Kind, unit string) hook.Info { 371 hi := hook.Info{ 372 Kind: kind, 373 RelationId: q.relationId, 374 RemoteUnit: unit, 375 ChangeVersion: q.members[unit], 376 } 377 return hi 378 } 379 380 func (q *DyingHookQueue) hookQueue() { 381 panic("interface sentinel method, do not call") 382 } 383 384 // Stop stops the DyingHookQueue and returns any errors encountered 385 // during operation or while shutting down. 386 func (q *DyingHookQueue) Stop() error { 387 q.tomb.Kill(nil) 388 return q.tomb.Wait() 389 }