github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/peergrouper/machinetracker.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package peergrouper 5 6 import ( 7 "fmt" 8 "sync" 9 10 "github.com/juju/errors" 11 12 "github.com/juju/juju/mongo" 13 "github.com/juju/juju/network" 14 "github.com/juju/juju/worker/catacomb" 15 ) 16 17 // machineTracker is a worker which reports changes of interest to 18 // the peergrouper for a single machine in state. 19 type machineTracker struct { 20 catacomb catacomb.Catacomb 21 notifyCh chan struct{} 22 stm stateMachine 23 24 mu sync.Mutex 25 26 // Outside of the machineTracker implementation itself, these 27 // should always be accessed via the getter methods in order to be 28 // protected by the mutex. 29 id string 30 wantsVote bool 31 apiHostPorts []network.HostPort 32 mongoHostPorts []network.HostPort 33 } 34 35 func newMachineTracker(stm stateMachine, notifyCh chan struct{}) (*machineTracker, error) { 36 m := &machineTracker{ 37 notifyCh: notifyCh, 38 id: stm.Id(), 39 stm: stm, 40 apiHostPorts: stm.APIHostPorts(), 41 mongoHostPorts: stm.MongoHostPorts(), 42 wantsVote: stm.WantsVote(), 43 } 44 err := catacomb.Invoke(catacomb.Plan{ 45 Site: &m.catacomb, 46 Work: m.loop, 47 }) 48 if err != nil { 49 return nil, errors.Trace(err) 50 } 51 return m, nil 52 } 53 54 // Kill implements Worker. 55 func (m *machineTracker) Kill() { 56 m.catacomb.Kill(nil) 57 } 58 59 // Wait implements Worker. 60 func (m *machineTracker) Wait() error { 61 return m.catacomb.Wait() 62 } 63 64 // Id returns the id of the machine being tracked. 65 func (m *machineTracker) Id() string { 66 m.mu.Lock() 67 defer m.mu.Unlock() 68 return m.id 69 } 70 71 // WantsVote returns whether the machine wants to vote (according to 72 // state). 73 func (m *machineTracker) WantsVote() bool { 74 m.mu.Lock() 75 defer m.mu.Unlock() 76 return m.wantsVote 77 } 78 79 // WantsVote returns the MongoDB hostports from state. 80 func (m *machineTracker) MongoHostPorts() []network.HostPort { 81 m.mu.Lock() 82 defer m.mu.Unlock() 83 out := make([]network.HostPort, len(m.mongoHostPorts)) 84 copy(out, m.mongoHostPorts) 85 return out 86 } 87 88 // APIHostPorts returns the API server hostports from state. 89 func (m *machineTracker) APIHostPorts() []network.HostPort { 90 m.mu.Lock() 91 defer m.mu.Unlock() 92 out := make([]network.HostPort, len(m.apiHostPorts)) 93 copy(out, m.apiHostPorts) 94 return out 95 } 96 97 // SelectMongoHostPort returns the best hostport for the machine for 98 // MongoDB use, perhaps using the space provided. 99 func (m *machineTracker) SelectMongoHostPort(mongoSpace network.SpaceName) string { 100 m.mu.Lock() 101 defer m.mu.Unlock() 102 103 if mongoSpace != "" { 104 return mongo.SelectPeerHostPortBySpace(m.mongoHostPorts, mongoSpace) 105 } 106 return mongo.SelectPeerHostPort(m.mongoHostPorts) 107 } 108 109 func (m *machineTracker) String() string { 110 return m.Id() 111 } 112 113 func (m *machineTracker) GoString() string { 114 m.mu.Lock() 115 defer m.mu.Unlock() 116 117 return fmt.Sprintf("&peergrouper.machine{id: %q, wantsVote: %v, hostPorts: %v}", 118 m.id, m.wantsVote, m.mongoHostPorts) 119 } 120 121 func (m *machineTracker) loop() error { 122 watcher := m.stm.Watch() 123 if err := m.catacomb.Add(watcher); err != nil { 124 return errors.Trace(err) 125 } 126 127 var notifyCh chan struct{} 128 for { 129 select { 130 case <-m.catacomb.Dying(): 131 return m.catacomb.ErrDying() 132 case _, ok := <-watcher.Changes(): 133 if !ok { 134 return watcher.Err() 135 } 136 changed, err := m.hasChanged() 137 if err != nil { 138 return errors.Trace(err) 139 } 140 if changed { 141 notifyCh = m.notifyCh 142 } 143 case notifyCh <- struct{}{}: 144 notifyCh = nil 145 } 146 } 147 } 148 149 func (m *machineTracker) hasChanged() (bool, error) { 150 m.mu.Lock() 151 defer m.mu.Unlock() 152 153 if err := m.stm.Refresh(); err != nil { 154 if errors.IsNotFound(err) { 155 // We want to be robust when the machine 156 // state is out of date with respect to the 157 // controller info, so if the machine 158 // has been removed, just assume that 159 // no change has happened - the machine 160 // loop will be stopped very soon anyway. 161 return false, nil 162 } 163 return false, errors.Trace(err) 164 } 165 changed := false 166 if wantsVote := m.stm.WantsVote(); wantsVote != m.wantsVote { 167 m.wantsVote = wantsVote 168 changed = true 169 } 170 if hps := m.stm.MongoHostPorts(); !hostPortsEqual(hps, m.mongoHostPorts) { 171 m.mongoHostPorts = hps 172 changed = true 173 } 174 if hps := m.stm.APIHostPorts(); !hostPortsEqual(hps, m.apiHostPorts) { 175 m.apiHostPorts = hps 176 changed = true 177 } 178 return changed, nil 179 } 180 181 func hostPortsEqual(hps1, hps2 []network.HostPort) bool { 182 if len(hps1) != len(hps2) { 183 return false 184 } 185 for i := range hps1 { 186 if hps1[i] != hps2[i] { 187 return false 188 } 189 } 190 return true 191 }