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  }