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