github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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.v4" 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 } 32 33 // NewExecutor returns an Executor which takes its starting state from the 34 // supplied path, and records state changes there. If no state file exists, 35 // the executor's starting state will include a queued Install hook, for 36 // the charm identified by the supplied func. 37 func NewExecutor(stateFilePath string, getInstallCharm func() (*corecharm.URL, error)) (Executor, error) { 38 file := NewStateFile(stateFilePath) 39 state, err := file.Read() 40 if err == ErrNoStateFile { 41 charmURL, err := getInstallCharm() 42 if err != nil { 43 return nil, err 44 } 45 state = &State{ 46 Kind: Install, 47 Step: Queued, 48 CharmURL: charmURL, 49 } 50 } else if err != nil { 51 return nil, err 52 } 53 return &executor{ 54 file: file, 55 state: state, 56 }, nil 57 } 58 59 // State is part of the Executor interface. 60 func (x *executor) State() State { 61 return *x.state 62 } 63 64 // Run is part of the Executor interface. 65 func (x *executor) Run(op Operation) error { 66 logger.Infof("running operation %v", op) 67 switch err := x.do(op, stepPrepare); errors.Cause(err) { 68 case ErrSkipExecute: 69 case nil: 70 if err := x.do(op, stepExecute); err != nil { 71 return err 72 } 73 default: 74 return err 75 } 76 return x.do(op, stepCommit) 77 } 78 79 // Skip is part of the Executor interface. 80 func (x *executor) Skip(op Operation) error { 81 logger.Infof("skipping operation %v", op) 82 return x.do(op, stepCommit) 83 } 84 85 func (x *executor) do(op Operation, step executorStep) (err error) { 86 message := step.message(op) 87 logger.Infof(message) 88 newState, firstErr := step.run(op, *x.state) 89 if newState != nil { 90 writeErr := x.writeState(*newState) 91 if firstErr == nil { 92 firstErr = writeErr 93 } else if writeErr != nil { 94 logger.Errorf("after %s: %v", message, writeErr) 95 } 96 } 97 return errors.Annotatef(firstErr, message) 98 } 99 100 func (x *executor) writeState(newState State) error { 101 if err := newState.validate(); err != nil { 102 return err 103 } 104 if err := x.file.Write(&newState); err != nil { 105 return errors.Annotatef(err, "writing state") 106 } 107 x.state = &newState 108 return nil 109 }