github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/peergrouper/controllertracker.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 "github.com/juju/worker/v3/catacomb" 13 14 "github.com/juju/juju/core/network" 15 ) 16 17 // controllerTracker is a worker which reports changes of interest to 18 // the peergrouper for a single controller in state. 19 type controllerTracker struct { 20 catacomb catacomb.Catacomb 21 notifyCh chan struct{} 22 node ControllerNode 23 host ControllerHost 24 25 mu sync.Mutex 26 27 // Outside of the controllerTracker implementation itself, these 28 // should always be accessed via the getter methods in order to be 29 // protected by the mutex. 30 id string 31 wantsVote bool 32 hasVote bool 33 addresses network.SpaceAddresses 34 } 35 36 func newControllerTracker(node ControllerNode, host ControllerHost, notifyCh chan struct{}) (*controllerTracker, error) { 37 m := &controllerTracker{ 38 notifyCh: notifyCh, 39 id: node.Id(), 40 node: node, 41 host: host, 42 addresses: host.Addresses(), 43 wantsVote: node.WantsVote(), 44 hasVote: node.HasVote(), 45 } 46 err := catacomb.Invoke(catacomb.Plan{ 47 Site: &m.catacomb, 48 Work: m.loop, 49 }) 50 if err != nil { 51 return nil, errors.Trace(err) 52 } 53 return m, nil 54 } 55 56 // Kill implements Worker. 57 func (c *controllerTracker) Kill() { 58 c.catacomb.Kill(nil) 59 } 60 61 // Wait implements Worker. 62 func (c *controllerTracker) Wait() error { 63 return c.catacomb.Wait() 64 } 65 66 // Id returns the id of the controller being tracked. 67 func (c *controllerTracker) Id() string { 68 c.mu.Lock() 69 defer c.mu.Unlock() 70 return c.id 71 } 72 73 // WantsVote returns whether the controller wants to vote (according to 74 // state). 75 func (c *controllerTracker) WantsVote() bool { 76 c.mu.Lock() 77 defer c.mu.Unlock() 78 return c.wantsVote 79 } 80 81 // HasVote returns whether the controller has a vote (according to 82 // state). 83 func (c *controllerTracker) HasVote() bool { 84 c.mu.Lock() 85 defer c.mu.Unlock() 86 return c.hasVote 87 } 88 89 // Addresses returns the controller addresses from state. 90 func (c *controllerTracker) Addresses() network.SpaceAddresses { 91 c.mu.Lock() 92 defer c.mu.Unlock() 93 out := make(network.SpaceAddresses, len(c.addresses)) 94 copy(out, c.addresses) 95 return out 96 } 97 98 // SelectMongoAddressFromSpace returns the best address on the controller node for MongoDB peer 99 // use, using the input space. 100 func (c *controllerTracker) SelectMongoAddressFromSpace(port int, space network.SpaceInfo) (string, error) { 101 if space.ID == "" { 102 return "", fmt.Errorf( 103 "empty space supplied as an argument for selecting Mongo address for controller node %q", c.id) 104 } 105 106 c.mu.Lock() 107 hostPorts := network.SpaceAddressesWithPort(c.addresses, port) 108 c.mu.Unlock() 109 110 addrs, ok := hostPorts.InSpaces(space) 111 if ok { 112 addr := network.DialAddress(addrs[0]) 113 logger.Debugf("controller node %q selected address %q by space %q from %v", c.id, addr, space.Name, hostPorts) 114 return addr, nil 115 } 116 117 // If we end up here, then there are no addresses available in the 118 // specified space. This should not happen, because the configured 119 // space is used as a constraint when first enabling HA. 120 return "", errors.NotFoundf("addresses for controller node %q in space %q", c.id, space.Name) 121 } 122 123 // GetPotentialMongoHostPorts simply returns all the available addresses 124 // with the Mongo port appended. 125 func (c *controllerTracker) GetPotentialMongoHostPorts(port int) network.SpaceHostPorts { 126 c.mu.Lock() 127 defer c.mu.Unlock() 128 return network.SpaceAddressesWithPort(c.addresses, port) 129 } 130 131 func (c *controllerTracker) String() string { 132 return c.Id() 133 } 134 135 func (c *controllerTracker) GoString() string { 136 c.mu.Lock() 137 defer c.mu.Unlock() 138 139 return fmt.Sprintf( 140 "&peergrouper.controller{id: %q, wantsVote: %v, hasVote: %v, addresses: %v}", 141 c.id, c.wantsVote, c.hasVote, c.addresses, 142 ) 143 } 144 145 func (c *controllerTracker) loop() error { 146 hostWatcher := c.host.Watch() 147 if err := c.catacomb.Add(hostWatcher); err != nil { 148 return errors.Trace(err) 149 } 150 nodeWatcher := c.node.Watch() 151 if err := c.catacomb.Add(nodeWatcher); err != nil { 152 return errors.Trace(err) 153 } 154 155 var notifyCh chan struct{} 156 for { 157 select { 158 case <-c.catacomb.Dying(): 159 return c.catacomb.ErrDying() 160 case _, ok := <-hostWatcher.Changes(): 161 if !ok { 162 return hostWatcher.Err() 163 } 164 changed, err := c.hasHostChanged() 165 if err != nil { 166 return errors.Trace(err) 167 } 168 if changed { 169 notifyCh = c.notifyCh 170 } 171 case _, ok := <-nodeWatcher.Changes(): 172 if !ok { 173 return nodeWatcher.Err() 174 } 175 changed, err := c.hasNodeChanged() 176 if err != nil { 177 return errors.Trace(err) 178 } 179 if changed { 180 notifyCh = c.notifyCh 181 } 182 case notifyCh <- struct{}{}: 183 notifyCh = nil 184 } 185 } 186 } 187 188 func (c *controllerTracker) hasHostChanged() (bool, error) { 189 c.mu.Lock() 190 defer c.mu.Unlock() 191 192 if err := c.host.Refresh(); err != nil { 193 if errors.IsNotFound(err) { 194 // We want to be robust when the controller 195 // state is out of date with respect to the 196 // controller info, so if the controller 197 // has been removed, just assume that 198 // no change has happened - the controller 199 // loop will be stopped very soon anyway. 200 return false, nil 201 } 202 return false, errors.Trace(err) 203 } 204 changed := false 205 if addrs := c.host.Addresses(); !reflect.DeepEqual(addrs, c.addresses) { 206 c.addresses = addrs 207 changed = true 208 } 209 return changed, nil 210 } 211 212 func (c *controllerTracker) hasNodeChanged() (bool, error) { 213 c.mu.Lock() 214 defer c.mu.Unlock() 215 216 if err := c.node.Refresh(); err != nil { 217 if errors.IsNotFound(err) { 218 // We want to be robust when the node 219 // state is out of date with respect to the 220 // controller info, so if the node 221 // has been removed, just assume that 222 // no change has happened - the controller 223 // loop will be stopped very soon anyway. 224 return false, nil 225 } 226 return false, errors.Trace(err) 227 } 228 // hasVote doesn't count towards a node change but 229 // we still want to record the latest value. 230 c.hasVote = c.node.HasVote() 231 232 changed := false 233 if wantsVote := c.node.WantsVote(); wantsVote != c.wantsVote { 234 c.wantsVote = wantsVote 235 changed = true 236 } 237 return changed, nil 238 }