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  }