github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/stateconfigwatcher/manifold.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package stateconfigwatcher
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  	"github.com/juju/utils/voyeur"
    10  	"gopkg.in/juju/names.v2"
    11  	"gopkg.in/juju/worker.v1"
    12  	"gopkg.in/juju/worker.v1/dependency"
    13  	"gopkg.in/tomb.v2"
    14  
    15  	"github.com/juju/juju/agent"
    16  )
    17  
    18  var logger = loggo.GetLogger("juju.worker.stateconfigwatcher")
    19  
    20  type ManifoldConfig struct {
    21  	AgentName          string
    22  	AgentConfigChanged *voyeur.Value
    23  }
    24  
    25  // Manifold returns a dependency.Manifold which wraps the machine
    26  // agent's voyeur.Value which gets set whenever it the machine agent's
    27  // config is changed. Whenever the config is updated the presence of
    28  // state serving info is checked and if state serving info was added
    29  // or removed the manifold worker will bounce itself.
    30  //
    31  // The manifold offes a single boolean output which will be true if
    32  // state serving info is available (i.e. the machine agent should be a
    33  // state server) and false otherwise.
    34  //
    35  // This manifold is intended to be used as a dependency for the state
    36  // manifold.
    37  func Manifold(config ManifoldConfig) dependency.Manifold {
    38  	return dependency.Manifold{
    39  		Inputs: []string{config.AgentName},
    40  		Start: func(context dependency.Context) (worker.Worker, error) {
    41  			var a agent.Agent
    42  			if err := context.Get(config.AgentName, &a); err != nil {
    43  				return nil, err
    44  			}
    45  
    46  			if config.AgentConfigChanged == nil {
    47  				return nil, errors.NotValidf("nil AgentConfigChanged")
    48  			}
    49  
    50  			if _, ok := a.CurrentConfig().Tag().(names.MachineTag); !ok {
    51  				return nil, errors.New("manifold can only be used with a machine agent")
    52  			}
    53  
    54  			w := &stateConfigWatcher{
    55  				agent:              a,
    56  				agentConfigChanged: config.AgentConfigChanged,
    57  			}
    58  			w.tomb.Go(w.loop)
    59  			return w, nil
    60  		},
    61  		Output: outputFunc,
    62  	}
    63  }
    64  
    65  // outputFunc extracts a bool from a *stateConfigWatcher. If true, the
    66  // agent is a state server.
    67  func outputFunc(in worker.Worker, out interface{}) error {
    68  	inWorker, _ := in.(*stateConfigWatcher)
    69  	if inWorker == nil {
    70  		return errors.Errorf("in should be a %T; got %T", inWorker, in)
    71  	}
    72  	switch outPointer := out.(type) {
    73  	case *bool:
    74  		*outPointer = inWorker.isStateServer()
    75  	default:
    76  		return errors.Errorf("out should be *bool; got %T", out)
    77  	}
    78  	return nil
    79  }
    80  
    81  type stateConfigWatcher struct {
    82  	tomb               tomb.Tomb
    83  	agent              agent.Agent
    84  	agentConfigChanged *voyeur.Value
    85  }
    86  
    87  func (w *stateConfigWatcher) isStateServer() bool {
    88  	config := w.agent.CurrentConfig()
    89  	_, ok := config.StateServingInfo()
    90  	return ok
    91  }
    92  
    93  func (w *stateConfigWatcher) loop() error {
    94  	watch := w.agentConfigChanged.Watch()
    95  	defer watch.Close()
    96  
    97  	lastValue := w.isStateServer()
    98  
    99  	watchCh := make(chan bool)
   100  	go func() {
   101  		for {
   102  			if watch.Next() {
   103  				select {
   104  				case <-w.tomb.Dying():
   105  					return
   106  				case watchCh <- true:
   107  				}
   108  			} else {
   109  				// watcher or voyeur.Value closed.
   110  				close(watchCh)
   111  				return
   112  			}
   113  		}
   114  	}()
   115  
   116  	for {
   117  		select {
   118  		case <-w.tomb.Dying():
   119  			logger.Infof("tomb dying")
   120  			return tomb.ErrDying
   121  		case _, ok := <-watchCh:
   122  			if !ok {
   123  				return errors.New("config changed value closed")
   124  			}
   125  			if w.isStateServer() != lastValue {
   126  				// State serving info has been set or unset so restart
   127  				// so that dependents get notified. ErrBounce ensures
   128  				// that the manifold is restarted quickly.
   129  				logger.Debugf("state serving info change in agent config")
   130  				return dependency.ErrBounce
   131  			}
   132  		}
   133  	}
   134  }
   135  
   136  // Kill implements worker.Worker.
   137  func (w *stateConfigWatcher) Kill() {
   138  	w.tomb.Kill(nil)
   139  }
   140  
   141  // Wait implements worker.Worker.
   142  func (w *stateConfigWatcher) Wait() error {
   143  	return w.tomb.Wait()
   144  }