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