github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/worker/uniter/relation/livesource.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package relation 5 6 import ( 7 "sort" 8 9 "github.com/juju/errors" 10 "gopkg.in/juju/charm.v4/hooks" 11 12 "github.com/juju/juju/state/multiwatcher" 13 "github.com/juju/juju/worker/uniter/hook" 14 ) 15 16 // liveSource maintains a minimal queue of hooks that need to be run to reflect 17 // relation state changes exposed via a RelationUnitsWatcher. 18 type liveSource struct { 19 relationId int 20 21 // info holds information about all units that were added to the 22 // queue and haven't had a "relation-departed" event popped. This 23 // means the unit may be in info and not currently in the queue 24 // itself. 25 info map[string]*unitInfo 26 27 // head and tail are the ends of the queue. 28 head, tail *unitInfo 29 30 // changedPending, if not empty, indicates that the most recently 31 // popped event was a "relation-joined" for the named unit, and 32 // therefore that the next event must be a "relation-changed" 33 // for that same unit. 34 // If changedPending is not empty, the queue is considered non- 35 // empty, even if head is nil. 36 changedPending string 37 38 started bool 39 watcher RelationUnitsWatcher 40 } 41 42 // unitInfo holds unit information for management by liveSource. 43 type unitInfo struct { 44 // unit holds the name of the unit. 45 unit string 46 47 // version and settings hold the most recent settings known 48 // to the AliveHookQueue. 49 version int64 50 51 // joined is set to true when a "relation-joined" is popped for this unit. 52 joined bool 53 54 // hookKind holds the current idea of the next hook that should 55 // be run for the unit, and is empty if and only if the unit 56 // is not queued. 57 hookKind hooks.Kind 58 59 // prev and next define the position in the queue of the 60 // unit's next hook. 61 prev, next *unitInfo 62 } 63 64 // NewLiveHookSource returns a new HookSource that aggregates the values 65 // obtained from the w watcher and generates the hooks that must be executed 66 // in the unit. It guarantees that the stream of hooks will respect the 67 // guarantees Juju makes about hook execution order. If any values have 68 // previously been received from w's Changes channel, the HookSource's 69 // behaviour is undefined. 70 func NewLiveHookSource(initial *State, w RelationUnitsWatcher) HookSource { 71 info := map[string]*unitInfo{} 72 for unit, version := range initial.Members { 73 info[unit] = &unitInfo{ 74 unit: unit, 75 version: version, 76 joined: true, 77 } 78 } 79 return &liveSource{ 80 watcher: w, 81 info: info, 82 relationId: initial.RelationId, 83 changedPending: initial.ChangedPending, 84 } 85 } 86 87 // Changes returns a channel sending a stream of RelationUnitsChange events 88 // that need to be delivered to Update in order for the source to function 89 // correctly. In particular, the first event represents the ideal state of 90 // the relation, and must be delivered for the source to be able to calculate 91 // the desired hooks. 92 func (q *liveSource) Changes() <-chan multiwatcher.RelationUnitsChange { 93 return q.watcher.Changes() 94 } 95 96 // Stop cleans up the liveSource's resources and stops sending changes. 97 func (q *liveSource) Stop() error { 98 return q.watcher.Stop() 99 } 100 101 // Update modifies the queue such that the hook.Info values it sends will 102 // reflect the supplied change. 103 func (q *liveSource) Update(change multiwatcher.RelationUnitsChange) error { 104 if !q.started { 105 q.started = true 106 // The first event represents the ideal final state of the system. 107 // If it contains any Departed notifications, it cannot be one of 108 // those -- most likely the watcher was not a fresh one -- and we're 109 // completely hosed. 110 if len(change.Departed) != 0 { 111 return errors.Errorf("hook source watcher sent bad event: %#v", change) 112 } 113 // Anyway, before we can generate actual hooks, we have to generate 114 // departed hooks for any previously-known members not reflected in 115 // the ideal state, and insert those at the head of the queue. The 116 // easiest way to do this is to inject a departure update for those 117 // missing members before processing the ideal state. 118 departs := multiwatcher.RelationUnitsChange{} 119 for unit := range q.info { 120 if _, found := change.Changed[unit]; !found { 121 departs.Departed = append(departs.Departed, unit) 122 } 123 } 124 q.update(departs) 125 } 126 q.update(change) 127 return nil 128 } 129 130 // Empty returns true if the queue is empty. 131 func (q *liveSource) Empty() bool { 132 // If the first event has not yet been delivered, we cannot correctly 133 // determine the schedule, so we pretend to be empty rather than expose 134 // an incorrect hook. 135 if !q.started { 136 return true 137 } 138 return q.head == nil && q.changedPending == "" 139 } 140 141 // Next returns the next hook.Info value to send. It will panic if the queue is 142 // empty. 143 func (q *liveSource) Next() hook.Info { 144 if q.Empty() { 145 panic("queue is empty") 146 } 147 var unit string 148 var kind hooks.Kind 149 if q.changedPending != "" { 150 unit = q.changedPending 151 kind = hooks.RelationChanged 152 } else { 153 unit = q.head.unit 154 kind = q.head.hookKind 155 } 156 version := q.info[unit].version 157 return hook.Info{ 158 Kind: kind, 159 RelationId: q.relationId, 160 RemoteUnit: unit, 161 ChangeVersion: version, 162 } 163 } 164 165 // Pop advances the queue. It will panic if the queue is already empty. 166 func (q *liveSource) Pop() { 167 if q.Empty() { 168 panic("queue is empty") 169 } 170 if q.changedPending != "" { 171 if q.info[q.changedPending].hookKind == hooks.RelationChanged { 172 // We just ran this very hook; no sense keeping it queued. 173 q.unqueue(q.changedPending) 174 } 175 q.changedPending = "" 176 } else { 177 old := *q.head 178 q.unqueue(q.head.unit) 179 if old.hookKind == hooks.RelationJoined { 180 q.changedPending = old.unit 181 q.info[old.unit].joined = true 182 } else if old.hookKind == hooks.RelationDeparted { 183 delete(q.info, old.unit) 184 } 185 } 186 } 187 188 func (q *liveSource) update(change multiwatcher.RelationUnitsChange) { 189 // Enforce consistent addition order, mainly for testing purposes. 190 changedUnits := []string{} 191 for unit := range change.Changed { 192 changedUnits = append(changedUnits, unit) 193 } 194 sort.Strings(changedUnits) 195 196 for _, unit := range changedUnits { 197 settings := change.Changed[unit] 198 info, found := q.info[unit] 199 if !found { 200 info = &unitInfo{unit: unit} 201 q.info[unit] = info 202 q.queue(unit, hooks.RelationJoined) 203 } else if info.hookKind != hooks.RelationJoined { 204 if settings.Version != info.version { 205 q.queue(unit, hooks.RelationChanged) 206 } else { 207 q.unqueue(unit) 208 } 209 } 210 info.version = settings.Version 211 } 212 213 for _, unit := range change.Departed { 214 if q.info[unit].hookKind == hooks.RelationJoined { 215 q.unqueue(unit) 216 } else { 217 q.queue(unit, hooks.RelationDeparted) 218 } 219 } 220 } 221 222 // queue sets the next hook to be run for the named unit, and places it 223 // at the tail of the queue if it is not already queued. It will panic 224 // if the unit is not in q.info. 225 func (q *liveSource) queue(unit string, kind hooks.Kind) { 226 // If the unit is not in the queue, place it at the tail. 227 info := q.info[unit] 228 if info.hookKind == "" { 229 info.prev = q.tail 230 if q.tail != nil { 231 q.tail.next = info 232 } 233 q.tail = info 234 235 // If the queue is empty, the tail is also the head. 236 if q.head == nil { 237 q.head = info 238 } 239 } 240 info.hookKind = kind 241 } 242 243 // unqueue removes the named unit from the queue. It is fine to 244 // unqueue a unit that is not in the queue, but it will panic if 245 // the unit is not in q.info. 246 func (q *liveSource) unqueue(unit string) { 247 if q.head == nil { 248 // The queue is empty, nothing to do. 249 return 250 } 251 252 // Get the unit info and clear its next action. 253 info := q.info[unit] 254 if info.hookKind == "" { 255 // The unit is not in the queue, nothing to do. 256 return 257 } 258 info.hookKind = "" 259 260 // Update queue pointers. 261 if info.prev == nil { 262 q.head = info.next 263 } else { 264 info.prev.next = info.next 265 } 266 if info.next == nil { 267 q.tail = info.prev 268 } else { 269 info.next.prev = info.prev 270 } 271 info.prev = nil 272 info.next = nil 273 }