github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  
    10  	"github.com/juju/juju/worker/fortress"
    11  	"github.com/juju/juju/worker/uniter/operation"
    12  	"github.com/juju/juju/worker/uniter/remotestate"
    13  )
    14  
    15  // ErrLoopAborted is used to signal that the loop is exiting because it
    16  // received a value on its config's Abort chan.
    17  var ErrLoopAborted = errors.New("resolver loop aborted")
    18  
    19  // LoopConfig contains configuration parameters for the resolver loop.
    20  type LoopConfig struct {
    21  	Resolver      Resolver
    22  	Watcher       remotestate.Watcher
    23  	Executor      operation.Executor
    24  	Factory       operation.Factory
    25  	Abort         <-chan struct{}
    26  	OnIdle        func() error
    27  	CharmDirGuard fortress.Guard
    28  }
    29  
    30  // Loop repeatedly waits for remote state changes, feeding the local and
    31  // remote state to the provided Resolver to generate Operations which are
    32  // then run with the provided Executor.
    33  //
    34  // The provided "onIdle" function will be called when the loop is waiting
    35  // for remote state changes due to a lack of work to perform. It will not
    36  // be called when a change is anticipated (i.e. due to ErrWaiting).
    37  //
    38  // The resolver loop can be controlled in the following ways:
    39  //  - if the "abort" channel is signalled, then the loop will
    40  //    exit with ErrLoopAborted
    41  //  - if the resolver returns ErrWaiting, then no operations
    42  //    will be executed until the remote state has changed
    43  //    again
    44  //  - if the resolver returns ErrNoOperation, then "onIdle"
    45  //    will be invoked and the loop will wait until the remote
    46  //    state has changed again
    47  //  - if the resolver, onIdle, or executor return some other
    48  //    error, the loop will exit immediately
    49  func Loop(cfg LoopConfig, localState *LocalState) error {
    50  	rf := &resolverOpFactory{Factory: cfg.Factory, LocalState: localState}
    51  
    52  	// Initialize charmdir availability before entering the loop in case we're recovering from a restart.
    53  	err := updateCharmDir(cfg.Executor.State(), cfg.CharmDirGuard, cfg.Abort)
    54  	if err != nil {
    55  		return errors.Trace(err)
    56  	}
    57  
    58  	for {
    59  		rf.RemoteState = cfg.Watcher.Snapshot()
    60  		rf.LocalState.State = cfg.Executor.State()
    61  
    62  		op, err := cfg.Resolver.NextOp(*rf.LocalState, rf.RemoteState, rf)
    63  		for err == nil {
    64  			logger.Tracef("running op: %v", op)
    65  			if err := cfg.Executor.Run(op); err != nil {
    66  				return errors.Trace(err)
    67  			}
    68  			// Refresh snapshot, in case remote state
    69  			// changed between operations.
    70  			rf.RemoteState = cfg.Watcher.Snapshot()
    71  			rf.LocalState.State = cfg.Executor.State()
    72  
    73  			err = updateCharmDir(rf.LocalState.State, cfg.CharmDirGuard, cfg.Abort)
    74  			if err != nil {
    75  				return errors.Trace(err)
    76  			}
    77  
    78  			op, err = cfg.Resolver.NextOp(*rf.LocalState, rf.RemoteState, rf)
    79  		}
    80  
    81  		switch errors.Cause(err) {
    82  		case nil:
    83  		case ErrWaiting:
    84  			// If a resolver is waiting for events to
    85  			// complete, the agent is not idle.
    86  		case ErrNoOperation:
    87  			if cfg.OnIdle != nil {
    88  				if err := cfg.OnIdle(); err != nil {
    89  					return errors.Trace(err)
    90  				}
    91  			}
    92  		default:
    93  			return err
    94  		}
    95  
    96  		select {
    97  		case <-cfg.Abort:
    98  			return ErrLoopAborted
    99  		case <-cfg.Watcher.RemoteStateChanged():
   100  		}
   101  	}
   102  }
   103  
   104  // updateCharmDir sets charm directory availability for sharing among
   105  // concurrent workers according to local operation state.
   106  func updateCharmDir(opState operation.State, guard fortress.Guard, abort fortress.Abort) error {
   107  	var changing bool
   108  
   109  	// Determine if the charm content is changing.
   110  	if opState.Kind == operation.Install || opState.Kind == operation.Upgrade {
   111  		changing = true
   112  	} else if opState.Kind == operation.RunHook && opState.Hook != nil && opState.Hook.Kind == hooks.UpgradeCharm {
   113  		changing = true
   114  	}
   115  
   116  	available := opState.Started && !opState.Stopped && !changing
   117  	logger.Tracef("charmdir: available=%v opState: started=%v stopped=%v changing=%v",
   118  		available, opState.Started, opState.Stopped, changing)
   119  	if available {
   120  		return guard.Unlock()
   121  	} else {
   122  		return guard.Lockdown(abort)
   123  	}
   124  }