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