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 }