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 }