-
Notifications
You must be signed in to change notification settings - Fork 494
/
resolver.go
131 lines (117 loc) · 4.58 KB
/
resolver.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
131
// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package actions
import (
"github.com/juju/errors"
"github.com/juju/juju/worker/common/charmrunner"
"github.com/juju/juju/worker/uniter/operation"
"github.com/juju/juju/worker/uniter/remotestate"
"github.com/juju/juju/worker/uniter/resolver"
)
// Logger is here to stop the desire of creating a package level Logger.
// Don't do this, instead use the one passed into the NewResolver as needed.
type logger interface{}
var _ logger = struct{}{}
// Logger represents the logging methods used by the actions resolver.
type Logger interface {
Infof(string, ...interface{})
}
type actionsResolver struct {
logger Logger
}
// NewResolver returns a new resolver with determines which action related operation
// should be run based on local and remote uniter states.
//
// TODO(axw) 2015-10-27 #1510333
// Use the same method as in the runcommands resolver
// for updating the remote state snapshot when an
// action is completed.
func NewResolver(logger Logger) resolver.Resolver {
return &actionsResolver{logger: logger}
}
func nextAction(pendingActions []string, completedActions map[string]struct{}) (string, error) {
for _, action := range pendingActions {
if _, ok := completedActions[action]; !ok {
return action, nil
}
}
return "", resolver.ErrNoOperation
}
// NextOp implements the resolver.Resolver interface.
func (r *actionsResolver) NextOp(
localState resolver.LocalState,
remoteState remotestate.Snapshot,
opFactory operation.Factory,
) (op operation.Operation, err error) {
// During CAAS unit initialization action operations are
// deferred until the unit is running. If the remote charm needs
// updating, hold off on action running.
if remoteState.ActionsBlocked || localState.OutdatedRemoteCharm {
// If we were somehow running an action during remote container changes/restart
// we need to fail it and move on.
if localState.Kind == operation.RunAction {
if localState.Hook != nil {
r.logger.Infof("found incomplete action %v; ignoring", localState.ActionId)
r.logger.Infof("recommitting prior %q hook", localState.Hook.Kind)
return opFactory.NewSkipHook(*localState.Hook)
}
return opFactory.NewFailAction(*localState.ActionId)
}
return nil, resolver.ErrNoOperation
}
// If there are no operation left to be run, then we cannot return the
// error signaling such here, we must first check to see if an action is
// already running (that has been interrupted) before we declare that
// there is nothing to do.
nextAction, err := nextAction(remoteState.ActionsPending, localState.CompletedActions)
if err != nil && err != resolver.ErrNoOperation {
return nil, err
}
defer func() {
if errors.Cause(err) == charmrunner.ErrActionNotAvailable {
if localState.Step == operation.Pending && localState.ActionId != nil {
r.logger.Infof("found missing action %v; ignoring", *localState.ActionId)
op, err = opFactory.NewFailAction(*localState.ActionId)
} else {
err = resolver.ErrNoOperation
}
}
}()
switch localState.Kind {
case operation.RunHook:
// We can still run actions if the unit is in a hook error state.
if localState.Step == operation.Pending && err == nil {
return opFactory.NewAction(nextAction)
}
case operation.RunAction:
if localState.Hook != nil {
r.logger.Infof("found incomplete action %v; ignoring", localState.ActionId)
r.logger.Infof("recommitting prior %q hook", localState.Hook.Kind)
return opFactory.NewSkipHook(*localState.Hook)
}
r.logger.Infof("%q hook is nil", operation.RunAction)
// If the next action is the same as what the uniter is
// currently running then this means that the uniter was
// some how interrupted (killed) when running the action
// and before updating the remote state to indicate that
// the action was completed. The only safe thing to do
// is fail the action, since rerunning an arbitrary
// command can potentially be hazardous.
if nextAction == *localState.ActionId {
return opFactory.NewFailAction(*localState.ActionId)
}
// If the next action is different then what the uniter
// is currently running, then the uniter may have been
// interrupted while running the action but the remote
// state was updated. Thus, the semantics of
// (re)preparing the running operation should move the
// uniter's state along safely. Thus, we return the
// running action.
return opFactory.NewAction(*localState.ActionId)
case operation.Continue:
if err != resolver.ErrNoOperation {
return opFactory.NewAction(nextAction)
}
}
return nil, resolver.ErrNoOperation
}