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