1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
7
"github.com/juju/errors"
8
"github.com/juju/testing"
9
jc "github.com/juju/testing/checkers"
10
gc "gopkg.in/check.v1"
11
"gopkg.in/juju/charm.v6-unstable"
12
"gopkg.in/juju/charm.v6-unstable/hooks"
13
"gopkg.in/juju/names.v2"
15
"github.com/juju/juju/apiserver/params"
16
"github.com/juju/juju/worker/uniter"
17
uniteractions "github.com/juju/juju/worker/uniter/actions"
18
"github.com/juju/juju/worker/uniter/hook"
19
"github.com/juju/juju/worker/uniter/leadership"
20
"github.com/juju/juju/worker/uniter/operation"
21
"github.com/juju/juju/worker/uniter/relation"
22
"github.com/juju/juju/worker/uniter/remotestate"
23
"github.com/juju/juju/worker/uniter/resolver"
24
"github.com/juju/juju/worker/uniter/storage"
27
type resolverSuite struct {
29
charmModifiedVersion int
31
remoteState remotestate.Snapshot
32
opFactory operation.Factory
33
resolver resolver.Resolver
34
resolverConfig uniter.ResolverConfig
36
clearResolved func() error
37
reportHookError func(hook.Info) error
40
var _ = gc.Suite(&resolverSuite{})
42
func (s *resolverSuite) SetUpTest(c *gc.C) {
43
s.stub = testing.Stub{}
44
s.charmURL = charm.MustParseURL("cs:precise/mysql-2")
45
s.remoteState = remotestate.Snapshot{
46
CharmModifiedVersion: s.charmModifiedVersion,
49
s.opFactory = operation.NewFactory(operation.FactoryParams{})
51
attachments, err := storage.NewAttachments(&dummyStorageAccessor{}, names.NewUnitTag("u/0"), c.MkDir(), nil)
52
c.Assert(err, jc.ErrorIsNil)
54
s.clearResolved = func() error {
55
return errors.New("unexpected resolved")
58
s.reportHookError = func(hook.Info) error {
59
return errors.New("unexpected report hook error")
62
s.resolverConfig = uniter.ResolverConfig{
63
ClearResolved: func() error { return s.clearResolved() },
64
ReportHookError: func(info hook.Info) error { return s.reportHookError(info) },
65
FixDeployer: func() error { return nil },
66
StartRetryHookTimer: func() { s.stub.AddCall("StartRetryHookTimer") },
67
StopRetryHookTimer: func() { s.stub.AddCall("StopRetryHookTimer") },
68
ShouldRetryHooks: true,
69
Leadership: leadership.NewResolver(),
70
Actions: uniteractions.NewResolver(),
71
Relations: relation.NewRelationsResolver(&dummyRelations{}),
72
Storage: storage.NewResolver(attachments),
73
Commands: nopResolver{},
76
s.resolver = uniter.NewUniterResolver(s.resolverConfig)
79
// TestStartedNotInstalled tests whether the Started flag overrides the
80
// Installed flag being unset, in the event of an unexpected inconsistency in
82
func (s *resolverSuite) TestStartedNotInstalled(c *gc.C) {
83
localState := resolver.LocalState{
84
CharmModifiedVersion: s.charmModifiedVersion,
86
State: operation.State{
87
Kind: operation.Continue,
92
_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
93
c.Assert(err, gc.Equals, resolver.ErrNoOperation)
96
// TestNotStartedNotInstalled tests whether the next operation for an
97
// uninstalled local state is an install hook operation.
98
func (s *resolverSuite) TestNotStartedNotInstalled(c *gc.C) {
99
localState := resolver.LocalState{
100
CharmModifiedVersion: s.charmModifiedVersion,
101
CharmURL: s.charmURL,
102
State: operation.State{
103
Kind: operation.Continue,
108
op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
109
c.Assert(err, jc.ErrorIsNil)
110
c.Assert(op.String(), gc.Equals, "run install hook")
113
func (s *resolverSuite) TestHookErrorDoesNotStartRetryTimerIfShouldRetryFalse(c *gc.C) {
114
s.resolverConfig.ShouldRetryHooks = false
115
s.resolver = uniter.NewUniterResolver(s.resolverConfig)
116
s.reportHookError = func(hook.Info) error { return nil }
117
localState := resolver.LocalState{
118
CharmURL: s.charmURL,
119
State: operation.State{
120
Kind: operation.RunHook,
121
Step: operation.Pending,
125
Kind: hooks.ConfigChanged,
129
// Run the resolver; we should not attempt a hook retry
130
_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
131
c.Assert(err, gc.Equals, resolver.ErrNoOperation)
132
s.stub.CheckNoCalls(c)
135
func (s *resolverSuite) TestHookErrorStartRetryTimer(c *gc.C) {
136
s.reportHookError = func(hook.Info) error { return nil }
137
localState := resolver.LocalState{
138
CharmModifiedVersion: s.charmModifiedVersion,
139
CharmURL: s.charmURL,
140
State: operation.State{
141
Kind: operation.RunHook,
142
Step: operation.Pending,
146
Kind: hooks.ConfigChanged,
150
// Run the resolver twice; we should start the hook retry
151
// timer on the first time through, no change on the second.
152
_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
153
c.Assert(err, gc.Equals, resolver.ErrNoOperation)
154
s.stub.CheckCallNames(c, "StartRetryHookTimer")
156
_, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory)
157
c.Assert(err, gc.Equals, resolver.ErrNoOperation)
158
s.stub.CheckCallNames(c, "StartRetryHookTimer") // no change
161
func (s *resolverSuite) TestHookErrorStartRetryTimerAgain(c *gc.C) {
162
s.reportHookError = func(hook.Info) error { return nil }
163
localState := resolver.LocalState{
164
CharmModifiedVersion: s.charmModifiedVersion,
165
CharmURL: s.charmURL,
166
State: operation.State{
167
Kind: operation.RunHook,
168
Step: operation.Pending,
172
Kind: hooks.ConfigChanged,
177
_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
178
c.Assert(err, gc.Equals, resolver.ErrNoOperation)
179
s.stub.CheckCallNames(c, "StartRetryHookTimer")
181
s.remoteState.RetryHookVersion = 1
182
op, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
183
c.Assert(err, jc.ErrorIsNil)
184
c.Assert(op.String(), gc.Equals, "run config-changed hook")
185
s.stub.CheckCallNames(c, "StartRetryHookTimer") // no change
186
localState.RetryHookVersion = 1
188
_, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory)
189
c.Assert(err, gc.Equals, resolver.ErrNoOperation)
190
s.stub.CheckCallNames(c, "StartRetryHookTimer", "StartRetryHookTimer")
193
func (s *resolverSuite) TestResolvedRetryHooksStopRetryTimer(c *gc.C) {
194
// Resolving a failed hook should stop the retry timer.
195
s.testResolveHookErrorStopRetryTimer(c, params.ResolvedRetryHooks)
198
func (s *resolverSuite) TestResolvedNoHooksStopRetryTimer(c *gc.C) {
199
// Resolving a failed hook should stop the retry timer.
200
s.testResolveHookErrorStopRetryTimer(c, params.ResolvedNoHooks)
203
func (s *resolverSuite) testResolveHookErrorStopRetryTimer(c *gc.C, mode params.ResolvedMode) {
205
s.clearResolved = func() error { return nil }
206
s.reportHookError = func(hook.Info) error { return nil }
207
localState := resolver.LocalState{
208
CharmModifiedVersion: s.charmModifiedVersion,
209
CharmURL: s.charmURL,
210
State: operation.State{
211
Kind: operation.RunHook,
212
Step: operation.Pending,
216
Kind: hooks.ConfigChanged,
221
_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
222
c.Assert(err, gc.Equals, resolver.ErrNoOperation)
223
s.stub.CheckCallNames(c, "StartRetryHookTimer")
225
s.remoteState.ResolvedMode = mode
226
_, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory)
227
c.Assert(err, jc.ErrorIsNil)
228
s.stub.CheckCallNames(c, "StartRetryHookTimer", "StopRetryHookTimer")
231
func (s *resolverSuite) TestRunHookStopRetryTimer(c *gc.C) {
232
s.reportHookError = func(hook.Info) error { return nil }
233
localState := resolver.LocalState{
234
CharmModifiedVersion: s.charmModifiedVersion,
235
CharmURL: s.charmURL,
236
State: operation.State{
237
Kind: operation.RunHook,
238
Step: operation.Pending,
242
Kind: hooks.ConfigChanged,
247
_, err := s.resolver.NextOp(localState, s.remoteState, s.opFactory)
248
c.Assert(err, gc.Equals, resolver.ErrNoOperation)
249
s.stub.CheckCallNames(c, "StartRetryHookTimer")
251
localState.Kind = operation.Continue
252
_, err = s.resolver.NextOp(localState, s.remoteState, s.opFactory)
253
c.Assert(err, gc.Equals, resolver.ErrNoOperation)
254
s.stub.CheckCallNames(c, "StartRetryHookTimer", "StopRetryHookTimer")