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