github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "reflect" 9 "sync" 10 11 "github.com/juju/errors" 12 "gopkg.in/juju/worker.v1/catacomb" 13 14 "github.com/juju/juju/network" 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 Machine 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 addresses []network.Address 32 } 33 34 func newMachineTracker(stm Machine, notifyCh chan struct{}) (*machineTracker, error) { 35 m := &machineTracker{ 36 notifyCh: notifyCh, 37 id: stm.Id(), 38 stm: stm, 39 addresses: stm.Addresses(), 40 wantsVote: stm.WantsVote(), 41 } 42 err := catacomb.Invoke(catacomb.Plan{ 43 Site: &m.catacomb, 44 Work: m.loop, 45 }) 46 if err != nil { 47 return nil, errors.Trace(err) 48 } 49 return m, nil 50 } 51 52 // Kill implements Worker. 53 func (m *machineTracker) Kill() { 54 m.catacomb.Kill(nil) 55 } 56 57 // Wait implements Worker. 58 func (m *machineTracker) Wait() error { 59 return m.catacomb.Wait() 60 } 61 62 // Id returns the id of the machine being tracked. 63 func (m *machineTracker) Id() string { 64 m.mu.Lock() 65 defer m.mu.Unlock() 66 return m.id 67 } 68 69 // WantsVote returns whether the machine wants to vote (according to 70 // state). 71 func (m *machineTracker) WantsVote() bool { 72 m.mu.Lock() 73 defer m.mu.Unlock() 74 return m.wantsVote 75 } 76 77 // Addresses returns the machine addresses from state. 78 func (m *machineTracker) Addresses() []network.Address { 79 m.mu.Lock() 80 defer m.mu.Unlock() 81 out := make([]network.Address, len(m.addresses)) 82 copy(out, m.addresses) 83 return out 84 } 85 86 // SelectMongoAddress returns the best address on the machine for MongoDB peer 87 // use, using the input space. 88 // An error is returned if the empty space is supplied. 89 func (m *machineTracker) SelectMongoAddressFromSpace(port int, space network.SpaceName) (string, error) { 90 if space == "" { 91 return "", fmt.Errorf("empty space supplied as an argument for selecting Mongo address for machine %q", m.id) 92 } 93 94 m.mu.Lock() 95 hostPorts := network.AddressesWithPort(m.addresses, port) 96 m.mu.Unlock() 97 98 addrs, ok := network.SelectHostPortsBySpaceNames(hostPorts, space) 99 if ok { 100 addr := addrs[0].NetAddr() 101 logger.Debugf("machine %q selected address %q by space %q from %v", m.id, addr, space, hostPorts) 102 return addr, nil 103 } 104 105 // If we end up here, then there are no addresses available in the 106 // specified space. This should not happen, because the configured 107 // space is used as a constraint when first enabling HA. 108 return "", errors.NotFoundf("addresses for machine %q in space %q", m.id, space) 109 } 110 111 // GetPotentialMongoHostPorts simply returns all the available addresses 112 // with the Mongo port appended. 113 func (m *machineTracker) GetPotentialMongoHostPorts(port int) []network.HostPort { 114 m.mu.Lock() 115 defer m.mu.Unlock() 116 return network.AddressesWithPort(m.addresses, port) 117 } 118 119 func (m *machineTracker) String() string { 120 return m.Id() 121 } 122 123 func (m *machineTracker) GoString() string { 124 m.mu.Lock() 125 defer m.mu.Unlock() 126 127 return fmt.Sprintf( 128 "&peergrouper.machine{id: %q, wantsVote: %v, addresses: %v}", 129 m.id, m.wantsVote, m.addresses, 130 ) 131 } 132 133 func (m *machineTracker) loop() error { 134 watcher := m.stm.Watch() 135 if err := m.catacomb.Add(watcher); err != nil { 136 return errors.Trace(err) 137 } 138 139 var notifyCh chan struct{} 140 for { 141 select { 142 case <-m.catacomb.Dying(): 143 return m.catacomb.ErrDying() 144 case _, ok := <-watcher.Changes(): 145 if !ok { 146 return watcher.Err() 147 } 148 changed, err := m.hasChanged() 149 if err != nil { 150 return errors.Trace(err) 151 } 152 if changed { 153 notifyCh = m.notifyCh 154 } 155 case notifyCh <- struct{}{}: 156 notifyCh = nil 157 } 158 } 159 } 160 161 func (m *machineTracker) hasChanged() (bool, error) { 162 m.mu.Lock() 163 defer m.mu.Unlock() 164 165 if err := m.stm.Refresh(); err != nil { 166 if errors.IsNotFound(err) { 167 // We want to be robust when the machine 168 // state is out of date with respect to the 169 // controller info, so if the machine 170 // has been removed, just assume that 171 // no change has happened - the machine 172 // loop will be stopped very soon anyway. 173 return false, nil 174 } 175 return false, errors.Trace(err) 176 } 177 changed := false 178 if wantsVote := m.stm.WantsVote(); wantsVote != m.wantsVote { 179 m.wantsVote = wantsVote 180 changed = true 181 } 182 if addrs := m.stm.Addresses(); !reflect.DeepEqual(addrs, m.addresses) { 183 m.addresses = addrs 184 changed = true 185 } 186 return changed, nil 187 }