github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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/tomb.v1"
    12  
    13  	"github.com/juju/juju/agent"
    14  	"github.com/juju/juju/worker"
    15  	"github.com/juju/juju/worker/dependency"
    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  			go func() {
    59  				defer w.tomb.Done()
    60  				w.tomb.Kill(w.loop())
    61  			}()
    62  			return w, nil
    63  		},
    64  		Output: outputFunc,
    65  	}
    66  }
    67  
    68  // outputFunc extracts a bool from a *stateConfigWatcher. If true, the
    69  // agent is a state server.
    70  func outputFunc(in worker.Worker, out interface{}) error {
    71  	inWorker, _ := in.(*stateConfigWatcher)
    72  	if inWorker == nil {
    73  		return errors.Errorf("in should be a %T; got %T", inWorker, in)
    74  	}
    75  	switch outPointer := out.(type) {
    76  	case *bool:
    77  		*outPointer = inWorker.isStateServer()
    78  	default:
    79  		return errors.Errorf("out should be *bool; got %T", out)
    80  	}
    81  	return nil
    82  }
    83  
    84  type stateConfigWatcher struct {
    85  	tomb               tomb.Tomb
    86  	agent              agent.Agent
    87  	agentConfigChanged *voyeur.Value
    88  }
    89  
    90  func (w *stateConfigWatcher) isStateServer() bool {
    91  	config := w.agent.CurrentConfig()
    92  	_, ok := config.StateServingInfo()
    93  	return ok
    94  }
    95  
    96  func (w *stateConfigWatcher) loop() error {
    97  	watch := w.agentConfigChanged.Watch()
    98  	defer watch.Close()
    99  
   100  	lastValue := w.isStateServer()
   101  
   102  	watchCh := make(chan bool)
   103  	go func() {
   104  		for {
   105  			if watch.Next() {
   106  				select {
   107  				case <-w.tomb.Dying():
   108  					return
   109  				case watchCh <- true:
   110  				}
   111  			} else {
   112  				// watcher or voyeur.Value closed.
   113  				close(watchCh)
   114  				return
   115  			}
   116  		}
   117  	}()
   118  
   119  	for {
   120  		select {
   121  		case <-w.tomb.Dying():
   122  			logger.Infof("tomb dying")
   123  			return tomb.ErrDying
   124  		case _, ok := <-watchCh:
   125  			if !ok {
   126  				return errors.New("config changed value closed")
   127  			}
   128  			if w.isStateServer() != lastValue {
   129  				// State serving info has been set or unset so restart
   130  				// so that dependents get notified. ErrBounce ensures
   131  				// that the manifold is restarted quickly.
   132  				logger.Debugf("state serving info change in agent config")
   133  				return dependency.ErrBounce
   134  			}
   135  		}
   136  	}
   137  }
   138  
   139  // Kill implements worker.Worker.
   140  func (w *stateConfigWatcher) Kill() {
   141  	w.tomb.Kill(nil)
   142  }
   143  
   144  // Wait implements worker.Worker.
   145  func (w *stateConfigWatcher) Wait() error {
   146  	return w.tomb.Wait()
   147  }