forked from juju/juju
/
executor.go
112 lines (97 loc) · 2.8 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
// Copyright 2014-2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package operation
import (
"fmt"
"github.com/juju/errors"
)
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)
}
// 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, initialState State, acquireLock func(string) (func(), error)) (Executor, error) {
file := NewStateFile(stateFilePath)
state, err := file.Read()
if err == ErrNoStateFile {
state = &initialState
} 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) error {
logger.Debugf("running operation %v", op)
if op.NeedsGlobalMachineLock() {
releaser, err := x.acquireMachineLock(op.String())
if err != nil {
return errors.Annotate(err, "could not acquire lock")
}
defer logger.Debugf("lock released")
defer releaser()
}
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.Debugf("skipping operation %v", op)
return x.do(op, stepCommit)
}
func (x *executor) do(op Operation, step executorStep) (err error) {
message := step.message(op)
logger.Debugf(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
}