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  }