github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/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/errors"
     8  
     9  	"github.com/juju/juju/worker/common/charmrunner"
    10  	"github.com/juju/juju/worker/uniter/operation"
    11  	"github.com/juju/juju/worker/uniter/remotestate"
    12  	"github.com/juju/juju/worker/uniter/resolver"
    13  )
    14  
    15  // Logger is here to stop the desire of creating a package level Logger.
    16  // Don't do this, instead use the one passed into the NewResolver as needed.
    17  type logger interface{}
    18  
    19  var _ logger = struct{}{}
    20  
    21  // Logger represents the logging methods used by the actions resolver.
    22  type Logger interface {
    23  	Infof(string, ...interface{})
    24  	Debugf(string, ...interface{})
    25  }
    26  
    27  type actionsResolver struct {
    28  	logger Logger
    29  }
    30  
    31  // NewResolver returns a new resolver with determines which action related operation
    32  // should be run based on local and remote uniter states.
    33  //
    34  // TODO(axw) 2015-10-27 #1510333
    35  // Use the same method as in the runcommands resolver
    36  // for updating the remote state snapshot when an
    37  // action is completed.
    38  func NewResolver(logger Logger) resolver.Resolver {
    39  	return &actionsResolver{logger: logger}
    40  }
    41  
    42  func nextAction(pendingActions []string, completedActions map[string]struct{}) (string, error) {
    43  	for _, action := range pendingActions {
    44  		if _, ok := completedActions[action]; !ok {
    45  			return action, nil
    46  		}
    47  	}
    48  	return "", resolver.ErrNoOperation
    49  }
    50  
    51  // NextOp implements the resolver.Resolver interface.
    52  func (r *actionsResolver) NextOp(
    53  	localState resolver.LocalState,
    54  	remoteState remotestate.Snapshot,
    55  	opFactory operation.Factory,
    56  ) (op operation.Operation, err error) {
    57  	// During CAAS unit initialization action operations are
    58  	// deferred until the unit is running. If the remote charm needs
    59  	// updating, hold off on action running.
    60  	if remoteState.ActionsBlocked || localState.OutdatedRemoteCharm {
    61  		r.logger.Infof("actions are blocked=%v; outdated remote charm=%v - have pending actions: %v", remoteState.ActionsBlocked, localState.OutdatedRemoteCharm, remoteState.ActionsPending)
    62  		if localState.ActionId == nil {
    63  			r.logger.Debugf("actions are blocked, no in flight actions")
    64  			return nil, resolver.ErrNoOperation
    65  		}
    66  		// If we were somehow running an action during remote container changes/restart
    67  		// we need to fail it and move on.
    68  		r.logger.Infof("incomplete action %v is blocked", *localState.ActionId)
    69  		if localState.Kind == operation.RunAction {
    70  			if localState.Hook != nil {
    71  				r.logger.Infof("recommitting prior %q hook", localState.Hook.Kind)
    72  				return opFactory.NewSkipHook(*localState.Hook)
    73  			}
    74  			return opFactory.NewFailAction(*localState.ActionId)
    75  		}
    76  		return nil, resolver.ErrNoOperation
    77  	}
    78  	// If there are no operation left to be run, then we cannot return the
    79  	// error signaling such here, we must first check to see if an action is
    80  	// already running (that has been interrupted) before we declare that
    81  	// there is nothing to do.
    82  	nextActionId, err := nextAction(remoteState.ActionsPending, localState.CompletedActions)
    83  	if err != nil && err != resolver.ErrNoOperation {
    84  		return nil, err
    85  	}
    86  	if nextActionId == "" {
    87  		r.logger.Debugf("no next action from pending=%v; completed=%v", remoteState.ActionsPending, localState.CompletedActions)
    88  	}
    89  
    90  	defer func() {
    91  		if errors.Cause(err) == charmrunner.ErrActionNotAvailable {
    92  			if localState.Step == operation.Pending && localState.ActionId != nil {
    93  				r.logger.Infof("found missing not yet started action %v; running fail action", *localState.ActionId)
    94  				op, err = opFactory.NewFailAction(*localState.ActionId)
    95  			} else if nextActionId != "" {
    96  				r.logger.Infof("found missing incomplete action %v; running fail action", nextActionId)
    97  				op, err = opFactory.NewFailAction(nextActionId)
    98  			} else {
    99  				err = resolver.ErrNoOperation
   100  			}
   101  		}
   102  	}()
   103  
   104  	switch localState.Kind {
   105  	case operation.RunHook:
   106  		// We can still run actions if the unit is in a hook error state.
   107  		if localState.Step == operation.Pending && nextActionId != "" {
   108  			return opFactory.NewAction(nextActionId)
   109  		}
   110  	case operation.RunAction:
   111  		if localState.Hook != nil {
   112  			r.logger.Infof("found incomplete action %v; ignoring", localState.ActionId)
   113  			r.logger.Infof("recommitting prior %q hook", localState.Hook.Kind)
   114  			return opFactory.NewSkipHook(*localState.Hook)
   115  		}
   116  
   117  		r.logger.Infof("%q hook is nil, so running action %v", operation.RunAction, nextActionId)
   118  		// If the next action is the same as what the uniter is
   119  		// currently running then this means that the uniter was
   120  		// some how interrupted (killed) when running the action
   121  		// and before updating the remote state to indicate that
   122  		// the action was completed. The only safe thing to do
   123  		// is fail the action, since rerunning an arbitrary
   124  		// command can potentially be hazardous.
   125  		if nextActionId == *localState.ActionId {
   126  			r.logger.Debugf("unit agent was interrupted while running action %v", *localState.ActionId)
   127  			return opFactory.NewFailAction(*localState.ActionId)
   128  		}
   129  
   130  		// If the next action is different then what the uniter
   131  		// is currently running, then the uniter may have been
   132  		// interrupted while running the action but the remote
   133  		// state was updated. Thus, the semantics of
   134  		// (re)preparing the running operation should move the
   135  		// uniter's state along safely. Thus, we return the
   136  		// running action.
   137  		return opFactory.NewAction(*localState.ActionId)
   138  	case operation.Continue:
   139  		if nextActionId != "" {
   140  			return opFactory.NewAction(nextActionId)
   141  		}
   142  	}
   143  	return nil, resolver.ErrNoOperation
   144  }