github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/peergrouper/worker.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package peergrouper 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/replicaset" 13 14 "github.com/juju/juju/apiserver/common/networkingcommon" 15 "github.com/juju/juju/environs/config" 16 "github.com/juju/juju/instance" 17 "github.com/juju/juju/network" 18 "github.com/juju/juju/state" 19 "github.com/juju/juju/status" 20 "github.com/juju/juju/worker" 21 "github.com/juju/juju/worker/catacomb" 22 ) 23 24 var logger = loggo.GetLogger("juju.worker.peergrouper") 25 26 type stateInterface interface { 27 Machine(id string) (stateMachine, error) 28 WatchControllerInfo() state.NotifyWatcher 29 WatchControllerStatusChanges() state.StringsWatcher 30 ControllerInfo() (*state.ControllerInfo, error) 31 MongoSession() mongoSession 32 Space(id string) (SpaceReader, error) 33 SetOrGetMongoSpaceName(spaceName network.SpaceName) (network.SpaceName, error) 34 SetMongoSpaceState(mongoSpaceState state.MongoSpaceStates) error 35 ModelConfig() (*config.Config, error) 36 } 37 38 type stateMachine interface { 39 Id() string 40 InstanceId() (instance.Id, error) 41 Status() (status.StatusInfo, error) 42 Refresh() error 43 Watch() state.NotifyWatcher 44 WantsVote() bool 45 HasVote() bool 46 SetHasVote(hasVote bool) error 47 APIHostPorts() []network.HostPort 48 MongoHostPorts() []network.HostPort 49 } 50 51 type mongoSession interface { 52 CurrentStatus() (*replicaset.Status, error) 53 CurrentMembers() ([]replicaset.Member, error) 54 Set([]replicaset.Member) error 55 } 56 57 type publisherInterface interface { 58 // publish publishes information about the given controllers 59 // to whomsoever it may concern. When it is called there 60 // is no guarantee that any of the information has actually changed. 61 publishAPIServers(apiServers [][]network.HostPort, instanceIds []instance.Id) error 62 } 63 64 var ( 65 // If we fail to set the mongo replica set members, 66 // we start retrying with the following interval, 67 // before exponentially backing off with each further 68 // attempt. 69 initialRetryInterval = 2 * time.Second 70 71 // maxRetryInterval holds the maximum interval 72 // between retry attempts. 73 maxRetryInterval = 5 * time.Minute 74 75 // pollInterval holds the interval at which the replica set 76 // members will be updated even in the absence of changes 77 // to State. This enables us to make changes to members 78 // that are triggered by changes to member status. 79 pollInterval = 1 * time.Minute 80 ) 81 82 // pgWorker is a worker which watches the controller machines in state 83 // as well as the MongoDB replicaset configuration, adding and 84 // removing controller machines as they change or are added and 85 // removed. 86 type pgWorker struct { 87 catacomb catacomb.Catacomb 88 89 // st represents the State. It is an interface so we can swap 90 // out the implementation during testing. 91 st stateInterface 92 93 // machineChanges receives events from the machineTrackers when 94 // controller machines change in ways that are relevant to the 95 // peergrouper. 96 machineChanges chan struct{} 97 98 // machineTrackers holds the workers which track the machines we 99 // are currently watching (all the controller machines). 100 machineTrackers map[string]*machineTracker 101 102 // publisher holds the implementation of the API 103 // address publisher. 104 publisher publisherInterface 105 106 providerSupportsSpaces bool 107 } 108 109 // New returns a new worker that maintains the mongo replica set 110 // with respect to the given state. 111 func New(st *state.State) (worker.Worker, error) { 112 cfg, err := st.ModelConfig() 113 if err != nil { 114 return nil, err 115 } 116 shim := &stateShim{ 117 State: st, 118 mongoPort: cfg.StatePort(), 119 apiPort: cfg.APIPort(), 120 } 121 supportsSpaces := networkingcommon.SupportsSpaces(shim) == nil 122 return newWorker(shim, newPublisher(st, cfg.PreferIPv6()), supportsSpaces) 123 } 124 125 func newWorker(st stateInterface, pub publisherInterface, supportsSpaces bool) (worker.Worker, error) { 126 w := &pgWorker{ 127 st: st, 128 machineChanges: make(chan struct{}), 129 machineTrackers: make(map[string]*machineTracker), 130 publisher: pub, 131 providerSupportsSpaces: supportsSpaces, 132 } 133 err := catacomb.Invoke(catacomb.Plan{ 134 Site: &w.catacomb, 135 Work: w.loop, 136 }) 137 if err != nil { 138 return nil, errors.Trace(err) 139 } 140 return w, nil 141 } 142 143 func (w *pgWorker) Kill() { 144 w.catacomb.Kill(nil) 145 } 146 147 func (w *pgWorker) Wait() error { 148 return w.catacomb.Wait() 149 } 150 151 func (w *pgWorker) loop() error { 152 controllerChanges, err := w.watchForControllerChanges() 153 if err != nil { 154 return errors.Trace(err) 155 } 156 157 var updateChan <-chan time.Time 158 retryInterval := initialRetryInterval 159 160 for { 161 select { 162 case <-w.catacomb.Dying(): 163 return w.catacomb.ErrDying() 164 case <-controllerChanges: 165 changed, err := w.updateControllerMachines() 166 if err != nil { 167 return errors.Trace(err) 168 } 169 if changed { 170 // A controller machine was added or removed, update 171 // the replica set immediately. 172 // TODO(fwereade): 2016-03-17 lp:1558657 173 updateChan = time.After(0) 174 } 175 176 case <-w.machineChanges: 177 // One of the controller machines changed, update the 178 // replica set immediately. 179 // TODO(fwereade): 2016-03-17 lp:1558657 180 updateChan = time.After(0) 181 182 case <-updateChan: 183 ok := true 184 servers, instanceIds, err := w.apiPublishInfo() 185 if err != nil { 186 return fmt.Errorf("cannot get API server info: %v", err) 187 } 188 if err := w.publisher.publishAPIServers(servers, instanceIds); err != nil { 189 logger.Errorf("cannot publish API server addresses: %v", err) 190 ok = false 191 } 192 if err := w.updateReplicaset(); err != nil { 193 if _, isReplicaSetError := err.(*replicaSetError); !isReplicaSetError { 194 return err 195 } 196 logger.Errorf("cannot set replicaset: %v", err) 197 ok = false 198 } 199 if ok { 200 // Update the replica set members occasionally 201 // to keep them up to date with the current 202 // replica set member statuses. 203 // TODO(fwereade): 2016-03-17 lp:1558657 204 updateChan = time.After(pollInterval) 205 retryInterval = initialRetryInterval 206 } else { 207 // TODO(fwereade): 2016-03-17 lp:1558657 208 updateChan = time.After(retryInterval) 209 retryInterval *= 2 210 if retryInterval > maxRetryInterval { 211 retryInterval = maxRetryInterval 212 } 213 } 214 215 } 216 } 217 } 218 219 // watchForControllerChanges starts two watchers pertaining to changes 220 // to the controllers, returning a channel which will receive events 221 // if either watcher fires. 222 func (w *pgWorker) watchForControllerChanges() (<-chan struct{}, error) { 223 controllerInfoWatcher := w.st.WatchControllerInfo() 224 if err := w.catacomb.Add(controllerInfoWatcher); err != nil { 225 return nil, errors.Trace(err) 226 } 227 228 controllerStatusWatcher := w.st.WatchControllerStatusChanges() 229 if err := w.catacomb.Add(controllerStatusWatcher); err != nil { 230 return nil, errors.Trace(err) 231 } 232 233 out := make(chan struct{}) 234 go func() { 235 for { 236 select { 237 case <-w.catacomb.Dying(): 238 return 239 case <-controllerInfoWatcher.Changes(): 240 out <- struct{}{} 241 case <-controllerStatusWatcher.Changes(): 242 out <- struct{}{} 243 } 244 } 245 }() 246 return out, nil 247 } 248 249 // updateControllerMachines updates the peergrouper's current list of 250 // controller machines, as well as starting and stopping trackers for 251 // them as they are added and removed. 252 func (w *pgWorker) updateControllerMachines() (bool, error) { 253 info, err := w.st.ControllerInfo() 254 if err != nil { 255 return false, fmt.Errorf("cannot get controller info: %v", err) 256 } 257 258 logger.Debugf("controller machines in state: %#v", info.MachineIds) 259 changed := false 260 261 // Stop machine goroutines that no longer correspond to controller 262 // machines. 263 for _, m := range w.machineTrackers { 264 if !inStrings(m.Id(), info.MachineIds) { 265 worker.Stop(m) 266 delete(w.machineTrackers, m.Id()) 267 changed = true 268 } 269 } 270 271 // Start machines with no watcher 272 for _, id := range info.MachineIds { 273 if _, ok := w.machineTrackers[id]; ok { 274 continue 275 } 276 logger.Debugf("found new machine %q", id) 277 stm, err := w.st.Machine(id) 278 if err != nil { 279 if errors.IsNotFound(err) { 280 // If the machine isn't found, it must have been 281 // removed and will soon enough be removed 282 // from the controller list. This will probably 283 // never happen, but we'll code defensively anyway. 284 logger.Warningf("machine %q from controller list not found", id) 285 continue 286 } 287 return false, fmt.Errorf("cannot get machine %q: %v", id, err) 288 } 289 290 // Don't add the machine unless it is "Started" 291 machineStatus, err := stm.Status() 292 if err != nil { 293 return false, errors.Annotatef(err, "cannot get status for machine %q", id) 294 } 295 if machineStatus.Status == status.StatusStarted { 296 logger.Debugf("machine %q has started, adding it to peergrouper list", id) 297 tracker, err := newMachineTracker(stm, w.machineChanges) 298 if err != nil { 299 return false, errors.Trace(err) 300 } 301 if err := w.catacomb.Add(tracker); err != nil { 302 return false, errors.Trace(err) 303 } 304 w.machineTrackers[id] = tracker 305 changed = true 306 } else { 307 logger.Debugf("machine %q not ready: %v", id, machineStatus.Status) 308 } 309 310 } 311 return changed, nil 312 } 313 314 func inStrings(t string, ss []string) bool { 315 for _, s := range ss { 316 if s == t { 317 return true 318 } 319 } 320 return false 321 } 322 323 func (w *pgWorker) apiPublishInfo() ([][]network.HostPort, []instance.Id, error) { 324 servers := make([][]network.HostPort, 0, len(w.machineTrackers)) 325 instanceIds := make([]instance.Id, 0, len(w.machineTrackers)) 326 for _, m := range w.machineTrackers { 327 if len(m.APIHostPorts()) == 0 { 328 continue 329 } 330 instanceId, err := m.stm.InstanceId() 331 if err != nil { 332 return nil, nil, err 333 } 334 instanceIds = append(instanceIds, instanceId) 335 servers = append(servers, m.APIHostPorts()) 336 337 } 338 return servers, instanceIds, nil 339 } 340 341 // peerGroupInfo collates current session information about the 342 // mongo peer group with information from state machines. 343 func (w *pgWorker) peerGroupInfo() (*peerGroupInfo, error) { 344 session := w.st.MongoSession() 345 info := &peerGroupInfo{} 346 var err error 347 status, err := session.CurrentStatus() 348 if err != nil { 349 return nil, fmt.Errorf("cannot get replica set status: %v", err) 350 } 351 info.statuses = status.Members 352 info.members, err = session.CurrentMembers() 353 if err != nil { 354 return nil, fmt.Errorf("cannot get replica set members: %v", err) 355 } 356 info.machineTrackers = w.machineTrackers 357 358 spaceName, err := w.getMongoSpace(mongoAddresses(info.machineTrackers)) 359 if err != nil { 360 return nil, err 361 } 362 info.mongoSpace = spaceName 363 364 return info, nil 365 } 366 367 func mongoAddresses(machines map[string]*machineTracker) [][]network.Address { 368 addresses := make([][]network.Address, len(machines)) 369 i := 0 370 for _, m := range machines { 371 for _, hp := range m.MongoHostPorts() { 372 addresses[i] = append(addresses[i], hp.Address) 373 } 374 i++ 375 } 376 return addresses 377 } 378 379 // getMongoSpace updates info with the space that Mongo servers should exist in. 380 func (w *pgWorker) getMongoSpace(addrs [][]network.Address) (network.SpaceName, error) { 381 unset := network.SpaceName("") 382 383 stateInfo, err := w.st.ControllerInfo() 384 if err != nil { 385 return unset, errors.Annotate(err, "cannot get state server info") 386 } 387 388 switch stateInfo.MongoSpaceState { 389 case state.MongoSpaceUnknown: 390 if !w.providerSupportsSpaces { 391 err := w.st.SetMongoSpaceState(state.MongoSpaceUnsupported) 392 if err != nil { 393 return unset, errors.Annotate(err, "cannot set Mongo space state") 394 } 395 return unset, nil 396 } 397 398 // We want to find a space that contains all Mongo servers so we can 399 // use it to look up the IP address of each Mongo server to be used 400 // to set up the peer group. 401 spaceStats := generateSpaceStats(addrs) 402 if spaceStats.LargestSpaceContainsAll == false { 403 err := w.st.SetMongoSpaceState(state.MongoSpaceInvalid) 404 if err != nil { 405 return unset, errors.Annotate(err, "cannot set Mongo space state") 406 } 407 logger.Warningf("couldn't find a space containing all peer group machines") 408 return unset, nil 409 } else { 410 spaceName, err := w.st.SetOrGetMongoSpaceName(spaceStats.LargestSpace) 411 if err != nil { 412 return unset, errors.Annotate(err, "error setting/getting Mongo space") 413 } 414 return spaceName, nil 415 } 416 417 case state.MongoSpaceValid: 418 space, err := w.st.Space(stateInfo.MongoSpaceName) 419 if err != nil { 420 return unset, errors.Annotate(err, "looking up space") 421 } 422 return network.SpaceName(space.Name()), nil 423 } 424 425 return unset, nil 426 } 427 428 // replicaSetError holds an error returned as a result 429 // of calling replicaset.Set. As this is expected to fail 430 // in the normal course of things, it needs special treatment. 431 type replicaSetError struct { 432 error 433 } 434 435 // updateReplicaset sets the current replica set members, and applies the 436 // given voting status to machines in the state. 437 func (w *pgWorker) updateReplicaset() error { 438 info, err := w.peerGroupInfo() 439 if err != nil { 440 return errors.Annotate(err, "cannot get peergrouper info") 441 } 442 members, voting, err := desiredPeerGroup(info) 443 if err != nil { 444 return fmt.Errorf("cannot compute desired peer group: %v", err) 445 } 446 if members != nil { 447 logger.Debugf("desired peer group members: %#v", members) 448 } else { 449 logger.Debugf("no change in desired peer group (voting %#v)", voting) 450 } 451 452 // We cannot change the HasVote flag of a machine in state at exactly 453 // the same moment as changing its voting status in the replica set. 454 // 455 // Thus we need to be careful that a machine which is actually a voting 456 // member is not seen to not have a vote, because otherwise 457 // there is nothing to prevent the machine being removed. 458 // 459 // To avoid this happening, we make sure when we call SetReplicaSet, 460 // that the voting status of machines is the union of both old 461 // and new voting machines - that is the set of HasVote machines 462 // is a superset of all the actual voting machines. 463 // 464 // Only after the call has taken place do we reset the voting status 465 // of the machines that have lost their vote. 466 // 467 // If there's a crash, the voting status may not reflect the 468 // actual voting status for a while, but when things come 469 // back on line, it will be sorted out, as desiredReplicaSet 470 // will return the actual voting status. 471 // 472 // Note that we potentially update the HasVote status of the machines even 473 // if the members have not changed. 474 var added, removed []*machineTracker 475 for m, hasVote := range voting { 476 switch { 477 case hasVote && !m.stm.HasVote(): 478 added = append(added, m) 479 case !hasVote && m.stm.HasVote(): 480 removed = append(removed, m) 481 } 482 } 483 if err := setHasVote(added, true); err != nil { 484 return errors.Annotate(err, "cannot set HasVote added") 485 } 486 if members != nil { 487 if err := w.st.MongoSession().Set(members); err != nil { 488 // We've failed to set the replica set, so revert back 489 // to the previous settings. 490 if err1 := setHasVote(added, false); err1 != nil { 491 logger.Errorf("cannot revert machine voting after failure to change replica set: %v", err1) 492 } 493 return &replicaSetError{err} 494 } 495 logger.Infof("successfully changed replica set to %#v", members) 496 } 497 if err := setHasVote(removed, false); err != nil { 498 return errors.Annotate(err, "cannot set HasVote removed") 499 } 500 return nil 501 } 502 503 // setHasVote sets the HasVote status of all the given 504 // machines to hasVote. 505 func setHasVote(ms []*machineTracker, hasVote bool) error { 506 if len(ms) == 0 { 507 return nil 508 } 509 logger.Infof("setting HasVote=%v on machines %v", hasVote, ms) 510 for _, m := range ms { 511 if err := m.stm.SetHasVote(hasVote); err != nil { 512 return fmt.Errorf("cannot set voting status of %q to %v: %v", m.Id(), hasVote, err) 513 } 514 } 515 return nil 516 } 517 518 // allSpaceStats holds a SpaceStats for both API and Mongo machines 519 type allSpaceStats struct { 520 APIMachines spaceStats 521 MongoMachines spaceStats 522 } 523 524 // SpaceStats holds information useful when choosing which space to pick an 525 // address from. 526 type spaceStats struct { 527 SpaceRefCount map[network.SpaceName]int 528 LargestSpace network.SpaceName 529 LargestSpaceSize int 530 LargestSpaceContainsAll bool 531 } 532 533 // generateSpaceStats takes a list of machine addresses and returns information 534 // about what spaces are referenced by those machines. 535 func generateSpaceStats(addresses [][]network.Address) spaceStats { 536 var stats spaceStats 537 stats.SpaceRefCount = make(map[network.SpaceName]int) 538 539 for i := range addresses { 540 for _, addr := range addresses[i] { 541 v := stats.SpaceRefCount[addr.SpaceName] 542 v++ 543 stats.SpaceRefCount[addr.SpaceName] = v 544 545 if v > stats.LargestSpaceSize { 546 stats.LargestSpace = addr.SpaceName 547 stats.LargestSpaceSize = v 548 } 549 } 550 } 551 552 stats.LargestSpaceContainsAll = stats.LargestSpaceSize == len(addresses) 553 554 return stats 555 }