github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/state/manifold.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"gopkg.in/tomb.v1"
    12  
    13  	coreagent "github.com/juju/juju/agent"
    14  	"github.com/juju/juju/state"
    15  	"github.com/juju/juju/worker"
    16  	"github.com/juju/juju/worker/dependency"
    17  )
    18  
    19  var logger = loggo.GetLogger("juju.worker.state")
    20  
    21  // ManifoldConfig provides the dependencies for Manifold.
    22  type ManifoldConfig struct {
    23  	AgentName              string
    24  	StateConfigWatcherName string
    25  	OpenState              func(coreagent.Config) (*state.State, error)
    26  	PingInterval           time.Duration
    27  }
    28  
    29  const defaultPingInterval = 15 * time.Second
    30  
    31  // Manifold returns a manifold whose worker which wraps a
    32  // *state.State, which is in turn wrapper by a StateTracker.  It will
    33  // exit if the State's associated mongodb session dies.
    34  func Manifold(config ManifoldConfig) dependency.Manifold {
    35  	return dependency.Manifold{
    36  		Inputs: []string{
    37  			config.AgentName,
    38  			config.StateConfigWatcherName,
    39  		},
    40  		Start: func(context dependency.Context) (worker.Worker, error) {
    41  			// First, a sanity check.
    42  			if config.OpenState == nil {
    43  				return nil, errors.New("OpenState is nil in config")
    44  			}
    45  
    46  			// Get the agent.
    47  			var agent coreagent.Agent
    48  			if err := context.Get(config.AgentName, &agent); err != nil {
    49  				return nil, err
    50  			}
    51  
    52  			// Confirm we're running in a state server by asking the
    53  			// stateconfigwatcher manifold.
    54  			var haveStateConfig bool
    55  			if err := context.Get(config.StateConfigWatcherName, &haveStateConfig); err != nil {
    56  				return nil, err
    57  			}
    58  			if !haveStateConfig {
    59  				return nil, dependency.ErrMissing
    60  			}
    61  
    62  			st, err := config.OpenState(agent.CurrentConfig())
    63  			if err != nil {
    64  				return nil, errors.Trace(err)
    65  			}
    66  			stTracker := newStateTracker(st)
    67  
    68  			pingInterval := config.PingInterval
    69  			if pingInterval == 0 {
    70  				pingInterval = defaultPingInterval
    71  			}
    72  
    73  			w := &stateWorker{
    74  				stTracker:    stTracker,
    75  				pingInterval: pingInterval,
    76  			}
    77  			go func() {
    78  				defer w.tomb.Done()
    79  				w.tomb.Kill(w.loop())
    80  				if err := stTracker.Done(); err != nil {
    81  					logger.Errorf("error releasing state: %v", err)
    82  				}
    83  			}()
    84  			return w, nil
    85  		},
    86  		Output: outputFunc,
    87  	}
    88  }
    89  
    90  // outputFunc extracts a *StateTracker from a *stateWorker.
    91  func outputFunc(in worker.Worker, out interface{}) error {
    92  	inWorker, _ := in.(*stateWorker)
    93  	if inWorker == nil {
    94  		return errors.Errorf("in should be a %T; got %T", inWorker, in)
    95  	}
    96  
    97  	switch outPointer := out.(type) {
    98  	case *StateTracker:
    99  		*outPointer = inWorker.stTracker
   100  	default:
   101  		return errors.Errorf("out should be *state.State; got %T", out)
   102  	}
   103  	return nil
   104  }
   105  
   106  type stateWorker struct {
   107  	tomb         tomb.Tomb
   108  	stTracker    StateTracker
   109  	pingInterval time.Duration
   110  }
   111  
   112  func (w *stateWorker) loop() error {
   113  	st, err := w.stTracker.Use()
   114  	if err != nil {
   115  		return errors.Annotate(err, "failed to obtain state")
   116  	}
   117  	defer w.stTracker.Done()
   118  
   119  	for {
   120  		select {
   121  		case <-w.tomb.Dying():
   122  			return tomb.ErrDying
   123  		case <-time.After(w.pingInterval):
   124  			if err := st.Ping(); err != nil {
   125  				return errors.Annotate(err, "state ping failed")
   126  			}
   127  		}
   128  	}
   129  }
   130  
   131  // Kill is part of the worker.Worker interface.
   132  func (w *stateWorker) Kill() {
   133  	w.tomb.Kill(nil)
   134  }
   135  
   136  // Wait is part of the worker.Worker interface.
   137  func (w *stateWorker) Wait() error {
   138  	return w.tomb.Wait()
   139  }