github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/worker/uniter/resolver/loop.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package resolver
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"gopkg.in/juju/charm.v6-unstable/hooks"
     9  	"launchpad.net/tomb"
    10  
    11  	"github.com/juju/juju/worker/charmdir"
    12  	"github.com/juju/juju/worker/uniter/operation"
    13  	"github.com/juju/juju/worker/uniter/remotestate"
    14  )
    15  
    16  // LoopConfig contains configuration parameters for the resolver loop.
    17  type LoopConfig struct {
    18  	Resolver       Resolver
    19  	Watcher        remotestate.Watcher
    20  	Executor       operation.Executor
    21  	Factory        operation.Factory
    22  	Dying          <-chan struct{}
    23  	OnIdle         func() error
    24  	CharmDirLocker charmdir.Locker
    25  }
    26  
    27  // Loop repeatedly waits for remote state changes, feeding the local and
    28  // remote state to the provided Resolver to generate Operations which are
    29  // then run with the provided Executor.
    30  //
    31  // The provided "onIdle" function will be called when the loop is waiting
    32  // for remote state changes due to a lack of work to perform. It will not
    33  // be called when a change is anticipated (i.e. due to ErrWaiting).
    34  //
    35  // The resolver loop can be controlled in the following ways:
    36  //  - if the "dying" channel is signalled, then the loop will
    37  //    exit with tomb.ErrDying
    38  //  - if the resolver returns ErrWaiting, then no operations
    39  //    will be executed until the remote state has changed
    40  //    again
    41  //  - if the resolver returns ErrNoOperation, then "onIdle"
    42  //    will be invoked and the loop will wait until the remote
    43  //    state has changed again
    44  //  - if the resolver, onIdle, or executor return some other
    45  //    error, the loop will exit immediately
    46  func Loop(cfg LoopConfig, localState *LocalState) error {
    47  	rf := &resolverOpFactory{Factory: cfg.Factory, LocalState: localState}
    48  
    49  	// Initialize charmdir availability before entering the loop in case we're recovering from a restart.
    50  	updateCharmDir(cfg.Executor.State(), cfg.CharmDirLocker)
    51  
    52  	for {
    53  		rf.RemoteState = cfg.Watcher.Snapshot()
    54  		rf.LocalState.State = cfg.Executor.State()
    55  
    56  		op, err := cfg.Resolver.NextOp(*rf.LocalState, rf.RemoteState, rf)
    57  		for err == nil {
    58  			logger.Tracef("running op: %v", op)
    59  			if err := cfg.Executor.Run(op); err != nil {
    60  				return errors.Trace(err)
    61  			}
    62  			// Refresh snapshot, in case remote state
    63  			// changed between operations.
    64  			rf.RemoteState = cfg.Watcher.Snapshot()
    65  			rf.LocalState.State = cfg.Executor.State()
    66  			op, err = cfg.Resolver.NextOp(*rf.LocalState, rf.RemoteState, rf)
    67  		}
    68  
    69  		updateCharmDir(rf.LocalState.State, cfg.CharmDirLocker)
    70  
    71  		switch errors.Cause(err) {
    72  		case nil:
    73  		case ErrWaiting:
    74  			// If a resolver is waiting for events to
    75  			// complete, the agent is not idle.
    76  		case ErrNoOperation:
    77  			if cfg.OnIdle != nil {
    78  				if err := cfg.OnIdle(); err != nil {
    79  					return errors.Trace(err)
    80  				}
    81  			}
    82  		default:
    83  			return err
    84  		}
    85  
    86  		select {
    87  		case <-cfg.Dying:
    88  			return tomb.ErrDying
    89  		case <-cfg.Watcher.RemoteStateChanged():
    90  		}
    91  	}
    92  }
    93  
    94  // updateCharmDir sets charm directory availability for sharing among
    95  // concurrent workers according to local operation state.
    96  func updateCharmDir(opState operation.State, locker charmdir.Locker) {
    97  	var changing bool
    98  
    99  	// Determine if the charm content is changing.
   100  	if opState.Kind == operation.Install || opState.Kind == operation.Upgrade {
   101  		changing = true
   102  	} else if opState.Kind == operation.RunHook && opState.Hook != nil && opState.Hook.Kind == hooks.UpgradeCharm {
   103  		changing = true
   104  	}
   105  
   106  	available := opState.Started && !opState.Stopped && !changing
   107  	logger.Tracef("charmdir: available=%v opState: started=%v stopped=%v changing=%v",
   108  		available, opState.Started, opState.Stopped, changing)
   109  	locker.SetAvailable(available)
   110  }