github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/operation/executor.go (about) 1 // Copyright 2014-2015 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 "github.com/juju/mutex" 11 corecharm "gopkg.in/juju/charm.v6-unstable" 12 ) 13 14 type executorStep struct { 15 verb string 16 run func(op Operation, state State) (*State, error) 17 } 18 19 func (step executorStep) message(op Operation) string { 20 return fmt.Sprintf("%s operation %q", step.verb, op) 21 } 22 23 var ( 24 stepPrepare = executorStep{"preparing", Operation.Prepare} 25 stepExecute = executorStep{"executing", Operation.Execute} 26 stepCommit = executorStep{"committing", Operation.Commit} 27 ) 28 29 type executor struct { 30 file *StateFile 31 state *State 32 acquireMachineLock func() (mutex.Releaser, error) 33 } 34 35 // NewExecutor returns an Executor which takes its starting state from the 36 // supplied path, and records state changes there. If no state file exists, 37 // the executor's starting state will include a queued Install hook, for 38 // the charm identified by the supplied func. 39 func NewExecutor(stateFilePath string, getInstallCharm func() (*corecharm.URL, error), acquireLock func() (mutex.Releaser, error)) (Executor, error) { 40 file := NewStateFile(stateFilePath) 41 state, err := file.Read() 42 if err == ErrNoStateFile { 43 charmURL, err := getInstallCharm() 44 if err != nil { 45 return nil, err 46 } 47 state = &State{ 48 Kind: Install, 49 Step: Queued, 50 CharmURL: charmURL, 51 } 52 } else if err != nil { 53 return nil, err 54 } 55 return &executor{ 56 file: file, 57 state: state, 58 acquireMachineLock: acquireLock, 59 }, nil 60 } 61 62 // State is part of the Executor interface. 63 func (x *executor) State() State { 64 return *x.state 65 } 66 67 // Run is part of the Executor interface. 68 func (x *executor) Run(op Operation) (runErr error) { 69 logger.Debugf("running operation %v", op) 70 71 if op.NeedsGlobalMachineLock() { 72 releaser, err := x.acquireMachineLock() 73 if err != nil { 74 return errors.Annotate(err, "could not acquire lock") 75 } 76 defer logger.Debugf("lock released") 77 defer releaser.Release() 78 } 79 80 switch err := x.do(op, stepPrepare); errors.Cause(err) { 81 case ErrSkipExecute: 82 case nil: 83 if err := x.do(op, stepExecute); err != nil { 84 return err 85 } 86 default: 87 return err 88 } 89 return x.do(op, stepCommit) 90 } 91 92 // Skip is part of the Executor interface. 93 func (x *executor) Skip(op Operation) error { 94 logger.Debugf("skipping operation %v", op) 95 return x.do(op, stepCommit) 96 } 97 98 func (x *executor) do(op Operation, step executorStep) (err error) { 99 message := step.message(op) 100 logger.Debugf(message) 101 newState, firstErr := step.run(op, *x.state) 102 if newState != nil { 103 writeErr := x.writeState(*newState) 104 if firstErr == nil { 105 firstErr = writeErr 106 } else if writeErr != nil { 107 logger.Errorf("after %s: %v", message, writeErr) 108 } 109 } 110 return errors.Annotatef(firstErr, message) 111 } 112 113 func (x *executor) writeState(newState State) error { 114 if err := newState.validate(); err != nil { 115 return err 116 } 117 if err := x.file.Write(&newState); err != nil { 118 return errors.Annotatef(err, "writing state") 119 } 120 x.state = &newState 121 return nil 122 }