github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/actions/resolver_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package actions_test 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/loggo" 9 "github.com/juju/testing" 10 jc "github.com/juju/testing/checkers" 11 gc "gopkg.in/check.v1" 12 13 "github.com/juju/juju/worker/common/charmrunner" 14 "github.com/juju/juju/worker/uniter/actions" 15 "github.com/juju/juju/worker/uniter/hook" 16 "github.com/juju/juju/worker/uniter/operation" 17 "github.com/juju/juju/worker/uniter/remotestate" 18 "github.com/juju/juju/worker/uniter/resolver" 19 ) 20 21 type actionsSuite struct { 22 testing.IsolationSuite 23 } 24 25 var _ = gc.Suite(&actionsSuite{}) 26 27 func (s *actionsSuite) newResolver() resolver.Resolver { 28 return actions.NewResolver(loggo.GetLogger("test")) 29 } 30 31 func (s *actionsSuite) TestNoActions(c *gc.C) { 32 actionResolver := s.newResolver() 33 localState := resolver.LocalState{} 34 remoteState := remotestate.Snapshot{} 35 _, err := actionResolver.NextOp(localState, remoteState, &mockOperations{}) 36 c.Assert(err, gc.DeepEquals, resolver.ErrNoOperation) 37 } 38 39 func (s *actionsSuite) TestActionStateKindContinue(c *gc.C) { 40 actionResolver := s.newResolver() 41 localState := resolver.LocalState{ 42 State: operation.State{ 43 Kind: operation.Continue, 44 }, 45 } 46 remoteState := remotestate.Snapshot{ 47 ActionsPending: []string{"actionA", "actionB"}, 48 } 49 op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{}) 50 c.Assert(err, jc.ErrorIsNil) 51 c.Assert(op, jc.DeepEquals, mockOp("actionA")) 52 } 53 54 func (s *actionsSuite) TestActionRunHook(c *gc.C) { 55 actionResolver := s.newResolver() 56 localState := resolver.LocalState{ 57 State: operation.State{ 58 Kind: operation.RunHook, 59 Step: operation.Pending, 60 }, 61 } 62 remoteState := remotestate.Snapshot{ 63 ActionsPending: []string{"actionA", "actionB"}, 64 } 65 op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{}) 66 c.Assert(err, jc.ErrorIsNil) 67 c.Assert(op, jc.DeepEquals, mockOp("actionA")) 68 } 69 70 func (s *actionsSuite) TestNextAction(c *gc.C) { 71 actionResolver := s.newResolver() 72 localState := resolver.LocalState{ 73 State: operation.State{ 74 Kind: operation.Continue, 75 }, 76 CompletedActions: map[string]struct{}{"actionA": {}}, 77 } 78 remoteState := remotestate.Snapshot{ 79 ActionsPending: []string{"actionA", "actionB"}, 80 } 81 op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{}) 82 c.Assert(err, jc.ErrorIsNil) 83 c.Assert(op, jc.DeepEquals, mockOp("actionB")) 84 } 85 86 func (s *actionsSuite) TestNextActionBlocked(c *gc.C) { 87 actionResolver := s.newResolver() 88 localState := resolver.LocalState{ 89 State: operation.State{ 90 Kind: operation.Continue, 91 }, 92 CompletedActions: map[string]struct{}{"actionA": {}}, 93 } 94 remoteState := remotestate.Snapshot{ 95 ActionsPending: []string{"actionA", "actionB"}, 96 ActionsBlocked: true, 97 } 98 op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{}) 99 c.Assert(err, gc.DeepEquals, resolver.ErrNoOperation) 100 c.Assert(op, gc.IsNil) 101 } 102 103 func (s *actionsSuite) TestNextActionNotAvailable(c *gc.C) { 104 actionResolver := s.newResolver() 105 localState := resolver.LocalState{ 106 State: operation.State{ 107 Kind: operation.Continue, 108 }, 109 CompletedActions: map[string]struct{}{"actionA": {}}, 110 } 111 remoteState := remotestate.Snapshot{ 112 ActionsPending: []string{"actionA", "actionB"}, 113 } 114 op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{err: charmrunner.ErrActionNotAvailable}) 115 c.Assert(err, jc.ErrorIsNil) 116 c.Assert(op, jc.DeepEquals, mockFailAction("actionB")) 117 } 118 119 func (s *actionsSuite) TestNextActionBlockedRemoteInit(c *gc.C) { 120 actionResolver := s.newResolver() 121 localState := resolver.LocalState{ 122 State: operation.State{ 123 Kind: operation.Continue, 124 }, 125 CompletedActions: map[string]struct{}{"actionA": {}}, 126 OutdatedRemoteCharm: true, 127 } 128 remoteState := remotestate.Snapshot{ 129 ActionsPending: []string{"actionA", "actionB"}, 130 ActionsBlocked: false, 131 } 132 op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{}) 133 c.Assert(err, gc.DeepEquals, resolver.ErrNoOperation) 134 c.Assert(op, gc.IsNil) 135 } 136 137 func (s *actionsSuite) TestNextActionBlockedRemoteInitInProgress(c *gc.C) { 138 actionResolver := s.newResolver() 139 actionId := "actionB" 140 localState := resolver.LocalState{ 141 State: operation.State{ 142 Kind: operation.RunAction, 143 ActionId: &actionId, 144 }, 145 CompletedActions: map[string]struct{}{"actionA": {}}, 146 OutdatedRemoteCharm: true, 147 } 148 remoteState := remotestate.Snapshot{ 149 ActionsPending: []string{"actionA", "actionB"}, 150 ActionsBlocked: false, 151 } 152 op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{}) 153 c.Assert(err, jc.ErrorIsNil) 154 c.Assert(op, gc.DeepEquals, mockFailAction("actionB")) 155 } 156 157 func (s *actionsSuite) TestNextActionBlockedRemoteInitSkipHook(c *gc.C) { 158 actionResolver := s.newResolver() 159 actionId := "actionBad" 160 localState := resolver.LocalState{ 161 State: operation.State{ 162 Kind: operation.RunAction, 163 ActionId: &actionId, 164 Hook: &hook.Info{Kind: "test"}, 165 }, 166 CompletedActions: map[string]struct{}{"actionA": {}}, 167 OutdatedRemoteCharm: false, 168 } 169 remoteState := remotestate.Snapshot{ 170 ActionsPending: []string{"actionA", "actionB"}, 171 ActionsBlocked: true, 172 } 173 op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{}) 174 c.Assert(err, jc.ErrorIsNil) 175 c.Assert(op, gc.DeepEquals, mockSkipHook(*localState.Hook)) 176 } 177 178 func (s *actionsSuite) TestActionStateKindRunAction(c *gc.C) { 179 actionResolver := s.newResolver() 180 actionA := "actionA" 181 182 localState := resolver.LocalState{ 183 State: operation.State{ 184 Kind: operation.RunAction, 185 ActionId: &actionA, 186 }, 187 CompletedActions: map[string]struct{}{}, 188 } 189 remoteState := remotestate.Snapshot{ 190 ActionsPending: []string{}, 191 } 192 op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{}) 193 c.Assert(err, jc.ErrorIsNil) 194 c.Assert(op, jc.DeepEquals, mockOp(actionA)) 195 } 196 197 func (s *actionsSuite) TestActionStateKindRunActionSkipHook(c *gc.C) { 198 actionResolver := s.newResolver() 199 actionA := "actionA" 200 201 localState := resolver.LocalState{ 202 State: operation.State{ 203 Kind: operation.RunAction, 204 ActionId: &actionA, 205 Hook: &hook.Info{Kind: "test"}, 206 }, 207 CompletedActions: map[string]struct{}{}, 208 } 209 remoteState := remotestate.Snapshot{ 210 ActionsPending: []string{}, 211 } 212 op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{}) 213 c.Assert(err, jc.ErrorIsNil) 214 c.Assert(op, jc.DeepEquals, mockSkipHook(*localState.Hook)) 215 } 216 217 func (s *actionsSuite) TestActionStateKindRunActionPendingRemote(c *gc.C) { 218 actionResolver := s.newResolver() 219 actionA := "actionA" 220 221 localState := resolver.LocalState{ 222 State: operation.State{ 223 Kind: operation.RunAction, 224 ActionId: &actionA, 225 }, 226 CompletedActions: map[string]struct{}{}, 227 } 228 remoteState := remotestate.Snapshot{ 229 ActionsPending: []string{actionA, "actionB"}, 230 } 231 op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{}) 232 c.Assert(err, jc.ErrorIsNil) 233 c.Assert(op, jc.DeepEquals, mockFailAction(actionA)) 234 } 235 236 func (s *actionsSuite) TestPendingActionNotAvailable(c *gc.C) { 237 actionResolver := s.newResolver() 238 actionA := "666" 239 240 localState := resolver.LocalState{ 241 State: operation.State{ 242 Kind: operation.RunAction, 243 Step: operation.Pending, 244 ActionId: &actionA, 245 }, 246 CompletedActions: map[string]struct{}{}, 247 } 248 remoteState := remotestate.Snapshot{ 249 ActionsPending: []string{"666"}, 250 } 251 op, err := actionResolver.NextOp(localState, remoteState, &mockOperations{}) 252 c.Assert(err, jc.ErrorIsNil) 253 c.Assert(op, jc.DeepEquals, mockFailAction(actionA)) 254 } 255 256 type mockOperations struct { 257 operation.Factory 258 err error 259 } 260 261 func (m *mockOperations) NewAction(id string) (operation.Operation, error) { 262 if m.err != nil { 263 return nil, errors.Annotate(m.err, "action error") 264 } 265 if id == "666" { 266 return nil, charmrunner.ErrActionNotAvailable 267 } 268 return mockOp(id), nil 269 } 270 271 func (m *mockOperations) NewFailAction(id string) (operation.Operation, error) { 272 return mockFailAction(id), nil 273 } 274 275 func (m *mockOperations) NewSkipHook(hookInfo hook.Info) (operation.Operation, error) { 276 return mockSkipHook(hookInfo), nil 277 } 278 279 func mockOp(name string) operation.Operation { 280 return &mockOperation{name: name} 281 } 282 283 func mockFailAction(name string) operation.Operation { 284 return &mockFailOp{name: name} 285 } 286 287 func mockSkipHook(hookInfo hook.Info) operation.Operation { 288 return &mockSkipHookOp{hookInfo: hookInfo} 289 } 290 291 type mockOperation struct { 292 operation.Operation 293 name string 294 } 295 296 func (op *mockOperation) String() string { 297 return op.name 298 } 299 300 type mockFailOp struct { 301 operation.Operation 302 name string 303 } 304 305 func (op *mockFailOp) String() string { 306 return op.name 307 } 308 309 type mockSkipHookOp struct { 310 operation.Operation 311 hookInfo hook.Info 312 } 313 314 func (op *mockSkipHookOp) String() string { 315 return string(op.hookInfo.Kind) 316 }