github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/actions/resolver.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package actions
     5  
     6  import (
     7  	"github.com/juju/loggo"
     8  
     9  	"github.com/juju/juju/worker/uniter/operation"
    10  	"github.com/juju/juju/worker/uniter/remotestate"
    11  	"github.com/juju/juju/worker/uniter/resolver"
    12  )
    13  
    14  var logger = loggo.GetLogger("juju.worker.uniter.actions")
    15  
    16  type actionsResolver struct{}
    17  
    18  // NewResolver returns a new resolver with determines which action related operation
    19  // should be run based on local and remote uniter states.
    20  //
    21  // TODO(axw) 2015-10-27 #1510333
    22  // Use the same method as in the runcommands resolver
    23  // for updating the remote state snapshot when an
    24  // action is completed.
    25  func NewResolver() resolver.Resolver {
    26  	return &actionsResolver{}
    27  }
    28  
    29  func nextAction(pendingActions []string, completedActions map[string]struct{}) (string, error) {
    30  	for _, action := range pendingActions {
    31  		if _, ok := completedActions[action]; !ok {
    32  			return action, nil
    33  		}
    34  	}
    35  	return "", resolver.ErrNoOperation
    36  }
    37  
    38  // NextOp implements the resolver.Resolver interface.
    39  func (r *actionsResolver) NextOp(
    40  	localState resolver.LocalState,
    41  	remoteState remotestate.Snapshot,
    42  	opFactory operation.Factory,
    43  ) (operation.Operation, error) {
    44  	// If there are no operation left to be run, then we cannot return the
    45  	// error signaling such here, we must first check to see if an action is
    46  	// already running (that has been interrupted) before we declare that
    47  	// there is nothing to do.
    48  	nextAction, err := nextAction(remoteState.Actions, localState.CompletedActions)
    49  	if err != nil && err != resolver.ErrNoOperation {
    50  		return nil, err
    51  	}
    52  	switch localState.Kind {
    53  	case operation.RunHook:
    54  		// We can still run actions if the unit is in a hook error state.
    55  		if localState.Step == operation.Pending && err == nil {
    56  			return opFactory.NewAction(nextAction)
    57  		}
    58  	case operation.RunAction:
    59  		if localState.Hook != nil {
    60  			logger.Infof("found incomplete action %v; ignoring", localState.ActionId)
    61  			logger.Infof("recommitting prior %q hook", localState.Hook.Kind)
    62  			return opFactory.NewSkipHook(*localState.Hook)
    63  		} else {
    64  			logger.Infof("%q hook is nil", operation.RunAction)
    65  
    66  			// If the next action is the same as what the uniter is
    67  			// currently running then this means that the uniter was
    68  			// some how interrupted (killed) when running the action
    69  			// and before updating the remote state to indicate that
    70  			// the action was completed. The only safe thing to do
    71  			// is fail the action, since rerunning an arbitrary
    72  			// command can potentially be hazardous.
    73  			if nextAction == *localState.ActionId {
    74  				return opFactory.NewFailAction(*localState.ActionId)
    75  			}
    76  
    77  			// If the next action is different then what the uniter
    78  			// is currently running, then the uniter may have been
    79  			// interrupted while running the action but the remote
    80  			// state was updated. Thus, the semantics of
    81  			// (re)preparing the running operation should move the
    82  			// uniter's state along safely. Thus, we return the
    83  			// running action.
    84  			return opFactory.NewAction(*localState.ActionId)
    85  		}
    86  	case operation.Continue:
    87  		if err != resolver.ErrNoOperation {
    88  			return opFactory.NewAction(nextAction)
    89  		}
    90  	}
    91  	return nil, resolver.ErrNoOperation
    92  }