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