github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/apiconfigwatcher/manifold.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiconfigwatcher
     5  
     6  import (
     7  	"sort"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/utils/v3/voyeur"
    11  	"github.com/juju/worker/v3"
    12  	"github.com/juju/worker/v3/dependency"
    13  	"gopkg.in/tomb.v2"
    14  
    15  	"github.com/juju/juju/agent"
    16  )
    17  
    18  // Logger represents the methods used by the worker to log information.
    19  type Logger interface {
    20  	Debugf(string, ...interface{})
    21  	Errorf(string, ...interface{})
    22  }
    23  
    24  // logger is here to stop the desire of creating a package level logger.
    25  // Don't do this, instead use the one passed as manifold config.
    26  type logger interface{}
    27  
    28  var _ logger = struct{}{}
    29  
    30  type ManifoldConfig struct {
    31  	AgentName          string
    32  	AgentConfigChanged *voyeur.Value
    33  	Logger             Logger
    34  }
    35  
    36  // Manifold returns a dependency.Manifold which wraps an agent's
    37  // voyeur.Value which is set whenever the agent config is
    38  // changed. When the API server addresses in the config change the
    39  // manifold will bounce itself.
    40  //
    41  // The manifold is intended to be a dependency for the api-caller
    42  // manifold and is required to support model migrations.
    43  func Manifold(config ManifoldConfig) dependency.Manifold {
    44  	return dependency.Manifold{
    45  		Inputs: []string{config.AgentName},
    46  		Start: func(context dependency.Context) (worker.Worker, error) {
    47  			if config.AgentConfigChanged == nil {
    48  				return nil, errors.NotValidf("nil AgentConfigChanged")
    49  			}
    50  
    51  			var a agent.Agent
    52  			if err := context.Get(config.AgentName, &a); err != nil {
    53  				return nil, err
    54  			}
    55  
    56  			w := &apiconfigwatcher{
    57  				agent:              a,
    58  				agentConfigChanged: config.AgentConfigChanged,
    59  				logger:             config.Logger,
    60  			}
    61  			w.tomb.Go(w.loop)
    62  			return w, nil
    63  		},
    64  	}
    65  }
    66  
    67  type apiconfigwatcher struct {
    68  	tomb               tomb.Tomb
    69  	agent              agent.Agent
    70  	agentConfigChanged *voyeur.Value
    71  	addrs              []string
    72  	logger             Logger
    73  }
    74  
    75  func (w *apiconfigwatcher) loop() error {
    76  	w.addrs = w.getAPIAddresses()
    77  	watch := w.agentConfigChanged.Watch()
    78  	defer watch.Close()
    79  
    80  	// TODO(mjs) - this is pretty awful. There should be a
    81  	// NotifyWatcher for voyeur.Value. Note also that this code is
    82  	// repeated elsewhere.
    83  	watchCh := make(chan bool)
    84  	go func() {
    85  		for {
    86  			if watch.Next() {
    87  				select {
    88  				case <-w.tomb.Dying():
    89  					return
    90  				case watchCh <- true:
    91  				}
    92  			} else {
    93  				// watcher or voyeur.Value closed.
    94  				close(watchCh)
    95  				return
    96  			}
    97  		}
    98  	}()
    99  
   100  	for {
   101  		// Always unconditionally check for a change in API addresses
   102  		// first, in case there was a change between the start func
   103  		// and the call to Watch.
   104  		if !stringSliceEq(w.addrs, w.getAPIAddresses()) {
   105  			w.logger.Debugf("API addresses changed in agent config")
   106  			return dependency.ErrBounce
   107  		}
   108  
   109  		select {
   110  		case <-w.tomb.Dying():
   111  			return tomb.ErrDying
   112  		case _, ok := <-watchCh:
   113  			if !ok {
   114  				return errors.New("config changed value closed")
   115  			}
   116  		}
   117  	}
   118  }
   119  
   120  // Kill implements worker.Worker.
   121  func (w *apiconfigwatcher) Kill() {
   122  	w.tomb.Kill(nil)
   123  }
   124  
   125  // Wait implements worker.Worker.
   126  func (w *apiconfigwatcher) Wait() error {
   127  	return w.tomb.Wait()
   128  }
   129  
   130  func (w *apiconfigwatcher) getAPIAddresses() []string {
   131  	config := w.agent.CurrentConfig()
   132  	addrs, err := config.APIAddresses()
   133  	if err != nil {
   134  		w.logger.Errorf("retrieving API addresses: %s", err)
   135  		addrs = nil
   136  	}
   137  	sort.Strings(addrs)
   138  	return addrs
   139  }
   140  
   141  func stringSliceEq(a, b []string) bool {
   142  	if a == nil && b == nil {
   143  		return true
   144  	}
   145  
   146  	if a == nil || b == nil {
   147  		return false
   148  	}
   149  
   150  	if len(a) != len(b) {
   151  		return false
   152  	}
   153  
   154  	for i := range a {
   155  		if a[i] != b[i] {
   156  			return false
   157  		}
   158  	}
   159  	return true
   160  }