github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/uniter/operation/runaction.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package operation
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  
    11  	"github.com/juju/juju/api/agent/uniter"
    12  	"github.com/juju/juju/rpc/params"
    13  	"github.com/juju/juju/worker/common/charmrunner"
    14  	"github.com/juju/juju/worker/uniter/remotestate"
    15  	"github.com/juju/juju/worker/uniter/runner"
    16  )
    17  
    18  type runAction struct {
    19  	action  *uniter.Action
    20  	change  int
    21  	changed chan struct{}
    22  	cancel  chan struct{}
    23  
    24  	callbacks     Callbacks
    25  	runnerFactory runner.Factory
    26  
    27  	name   string
    28  	runner runner.Runner
    29  	logger Logger
    30  }
    31  
    32  // String is part of the Operation interface.
    33  func (ra *runAction) String() string {
    34  	return fmt.Sprintf("run action %s", ra.action.ID())
    35  }
    36  
    37  // NeedsGlobalMachineLock is part of the Operation interface.
    38  func (ra *runAction) NeedsGlobalMachineLock() bool {
    39  	return !ra.action.Parallel() || ra.action.ExecutionGroup() != ""
    40  }
    41  
    42  // ExecutionGroup is part of the Operation interface.
    43  func (ra *runAction) ExecutionGroup() string {
    44  	return ra.action.ExecutionGroup()
    45  }
    46  
    47  // Prepare ensures that the action is valid and can be executed. If not, it
    48  // will return ErrSkipExecute. It preserves any hook recorded in the supplied
    49  // state.
    50  // Prepare is part of the Operation interface.
    51  func (ra *runAction) Prepare(state State) (*State, error) {
    52  	ra.changed = make(chan struct{}, 1)
    53  	ra.cancel = make(chan struct{})
    54  	actionID := ra.action.ID()
    55  	rnr, err := ra.runnerFactory.NewActionRunner(ra.action, ra.cancel)
    56  	if cause := errors.Cause(err); charmrunner.IsBadActionError(cause) {
    57  		if err := ra.callbacks.FailAction(actionID, err.Error()); err != nil {
    58  			return nil, err
    59  		}
    60  		return nil, ErrSkipExecute
    61  	} else if cause == charmrunner.ErrActionNotAvailable {
    62  		return nil, ErrSkipExecute
    63  	} else if err != nil {
    64  		return nil, errors.Annotatef(err, "cannot create runner for action %q", actionID)
    65  	}
    66  	actionData, err := rnr.Context().ActionData()
    67  	if err != nil {
    68  		// this should *really* never happen, but let's not panic
    69  		return nil, errors.Trace(err)
    70  	}
    71  	err = rnr.Context().Prepare()
    72  	if err != nil {
    73  		return nil, errors.Trace(err)
    74  	}
    75  	ra.name = actionData.Name
    76  	ra.runner = rnr
    77  	return stateChange{
    78  		Kind:     RunAction,
    79  		Step:     Pending,
    80  		ActionId: &actionID,
    81  		Hook:     state.Hook,
    82  	}.apply(state), nil
    83  }
    84  
    85  // Execute runs the action, and preserves any hook recorded in the supplied state.
    86  // Execute is part of the Operation interface.
    87  func (ra *runAction) Execute(state State) (*State, error) {
    88  	message := fmt.Sprintf("running action %s", ra.name)
    89  	if err := ra.callbacks.SetExecutingStatus(message); err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	done := make(chan struct{})
    94  	wait := make(chan struct{})
    95  	actionID := ra.action.ID()
    96  	go func() {
    97  		defer close(wait)
    98  		for {
    99  			select {
   100  			case <-done:
   101  				return
   102  			case <-ra.changed:
   103  			}
   104  			status, err := ra.callbacks.ActionStatus(actionID)
   105  			if err != nil {
   106  				ra.logger.Warningf("unable to get action status for %q: %v", actionID, err)
   107  				continue
   108  			}
   109  			if status == params.ActionAborting {
   110  				ra.logger.Infof("action %s aborting", actionID)
   111  				close(ra.cancel)
   112  				return
   113  			}
   114  		}
   115  	}()
   116  
   117  	handlerType, err := ra.runner.RunAction(ra.name)
   118  	close(done)
   119  	<-wait
   120  
   121  	if err != nil {
   122  		// This indicates an actual error -- an action merely failing should
   123  		// be handled inside the Runner, and returned as nil.
   124  		return nil, errors.Annotatef(err, "action %q (via %s) failed", ra.name, handlerType)
   125  	}
   126  	return stateChange{
   127  		Kind:     RunAction,
   128  		Step:     Done,
   129  		ActionId: &actionID,
   130  		Hook:     state.Hook,
   131  	}.apply(state), nil
   132  }
   133  
   134  // Commit preserves the recorded hook, and returns a neutral state.
   135  // Commit is part of the Operation interface.
   136  func (ra *runAction) Commit(state State) (*State, error) {
   137  	return stateChange{
   138  		Kind: continuationKind(state),
   139  		Step: Pending,
   140  		Hook: state.Hook,
   141  	}.apply(state), nil
   142  }
   143  
   144  // RemoteStateChanged is called when the remote state changed during execution
   145  // of the operation.
   146  func (ra *runAction) RemoteStateChanged(snapshot remotestate.Snapshot) {
   147  	actionID := ra.action.ID()
   148  	change, ok := snapshot.ActionChanged[actionID]
   149  	if !ok {
   150  		ra.logger.Errorf("action %s missing action changed entry", actionID)
   151  		// Shouldn't happen.
   152  		return
   153  	}
   154  	if ra.change < change {
   155  		ra.change = change
   156  		ra.logger.Errorf("running action %s changed", actionID)
   157  		select {
   158  		case ra.changed <- struct{}{}:
   159  		default:
   160  		}
   161  	}
   162  }
   163  
   164  // continuationKind determines what State Kind the operation
   165  // should return after Commit.
   166  func continuationKind(state State) Kind {
   167  	switch {
   168  	case state.Hook != nil:
   169  		return RunHook
   170  	default:
   171  		return Continue
   172  	}
   173  }