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  }