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 }