github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/operation/executor_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package operation_test 5 6 import ( 7 "path/filepath" 8 9 "github.com/juju/errors" 10 "github.com/juju/testing" 11 jc "github.com/juju/testing/checkers" 12 ft "github.com/juju/testing/filetesting" 13 gc "gopkg.in/check.v1" 14 "gopkg.in/juju/charm.v6/hooks" 15 16 "github.com/juju/juju/worker/uniter/hook" 17 "github.com/juju/juju/worker/uniter/operation" 18 ) 19 20 type NewExecutorSuite struct { 21 testing.IsolationSuite 22 basePath string 23 } 24 25 var _ = gc.Suite(&NewExecutorSuite{}) 26 27 func failAcquireLock(_ string) (func(), error) { 28 return nil, errors.New("wat") 29 } 30 31 func (s *NewExecutorSuite) SetUpTest(c *gc.C) { 32 s.IsolationSuite.SetUpTest(c) 33 s.basePath = c.MkDir() 34 } 35 36 func (s *NewExecutorSuite) path(path string) string { 37 return filepath.Join(s.basePath, path) 38 } 39 40 func (s *NewExecutorSuite) TestNewExecutorInvalidFile(c *gc.C) { 41 ft.File{"existing", "", 0666}.Create(c, s.basePath) 42 executor, err := operation.NewExecutor(s.path("existing"), operation.State{}, failAcquireLock) 43 c.Assert(executor, gc.IsNil) 44 c.Assert(err, gc.ErrorMatches, `cannot read ".*": invalid operation state: .*`) 45 } 46 47 func (s *NewExecutorSuite) TestNewExecutorNoFile(c *gc.C) { 48 initialState := operation.State{} 49 executor, err := operation.NewExecutor(s.path("missing"), initialState, failAcquireLock) 50 c.Assert(err, jc.ErrorIsNil) 51 c.Assert(executor.State(), gc.DeepEquals, initialState) 52 ft.Removed{"missing"}.Check(c, s.basePath) 53 } 54 55 func (s *NewExecutorSuite) TestNewExecutorValidFile(c *gc.C) { 56 // note: this content matches valid persistent state as of 1.21; we expect 57 // that "hook" will have to become "last-hook" to enable action execution 58 // during hook error states. If you do this, please leave at least one test 59 // with this form of the yaml in place. 60 ft.File{"existing", ` 61 started: true 62 op: continue 63 opstep: pending 64 `[1:], 0666}.Create(c, s.basePath) 65 executor, err := operation.NewExecutor(s.path("existing"), operation.State{}, failAcquireLock) 66 c.Assert(err, jc.ErrorIsNil) 67 c.Assert(executor.State(), gc.DeepEquals, operation.State{ 68 Kind: operation.Continue, 69 Step: operation.Pending, 70 Started: true, 71 }) 72 } 73 74 type ExecutorSuite struct { 75 testing.IsolationSuite 76 } 77 78 var _ = gc.Suite(&ExecutorSuite{}) 79 80 func assertWroteState(c *gc.C, path string, expect operation.State) { 81 actual, err := operation.NewStateFile(path).Read() 82 c.Assert(err, jc.ErrorIsNil) 83 c.Assert(*actual, gc.DeepEquals, expect) 84 } 85 86 func newExecutor(c *gc.C, st *operation.State) (operation.Executor, string) { 87 path := filepath.Join(c.MkDir(), "state") 88 err := operation.NewStateFile(path).Write(st) 89 c.Assert(err, jc.ErrorIsNil) 90 executor, err := operation.NewExecutor(path, operation.State{}, failAcquireLock) 91 c.Assert(err, jc.ErrorIsNil) 92 return executor, path 93 } 94 95 func justInstalledState() operation.State { 96 return operation.State{ 97 Kind: operation.Continue, 98 Step: operation.Pending, 99 } 100 } 101 102 func (s *ExecutorSuite) TestSucceedNoStateChanges(c *gc.C) { 103 initialState := justInstalledState() 104 executor, statePath := newExecutor(c, &initialState) 105 106 op := &mockOperation{ 107 prepare: newStep(nil, nil), 108 execute: newStep(nil, nil), 109 commit: newStep(nil, nil), 110 } 111 112 err := executor.Run(op) 113 c.Assert(err, jc.ErrorIsNil) 114 115 c.Assert(op.prepare.gotState, gc.DeepEquals, initialState) 116 c.Assert(op.execute.gotState, gc.DeepEquals, initialState) 117 c.Assert(op.commit.gotState, gc.DeepEquals, initialState) 118 assertWroteState(c, statePath, initialState) 119 c.Assert(executor.State(), gc.DeepEquals, initialState) 120 } 121 122 func (s *ExecutorSuite) TestSucceedWithStateChanges(c *gc.C) { 123 initialState := justInstalledState() 124 executor, statePath := newExecutor(c, &initialState) 125 op := &mockOperation{ 126 prepare: newStep(&operation.State{ 127 Kind: operation.RunHook, 128 Step: operation.Pending, 129 Hook: &hook.Info{Kind: hooks.ConfigChanged}, 130 }, nil), 131 execute: newStep(&operation.State{ 132 Kind: operation.RunHook, 133 Step: operation.Done, 134 Hook: &hook.Info{Kind: hooks.ConfigChanged}, 135 }, nil), 136 commit: newStep(&operation.State{ 137 Kind: operation.RunHook, 138 Step: operation.Queued, 139 Hook: &hook.Info{Kind: hooks.Start}, 140 }, nil), 141 } 142 143 err := executor.Run(op) 144 c.Assert(err, jc.ErrorIsNil) 145 146 c.Assert(op.prepare.gotState, gc.DeepEquals, initialState) 147 c.Assert(op.execute.gotState, gc.DeepEquals, *op.prepare.newState) 148 c.Assert(op.commit.gotState, gc.DeepEquals, *op.execute.newState) 149 assertWroteState(c, statePath, *op.commit.newState) 150 c.Assert(executor.State(), gc.DeepEquals, *op.commit.newState) 151 } 152 153 func (s *ExecutorSuite) TestErrSkipExecute(c *gc.C) { 154 initialState := justInstalledState() 155 executor, statePath := newExecutor(c, &initialState) 156 op := &mockOperation{ 157 prepare: newStep(&operation.State{ 158 Kind: operation.RunHook, 159 Step: operation.Pending, 160 Hook: &hook.Info{Kind: hooks.ConfigChanged}, 161 }, operation.ErrSkipExecute), 162 commit: newStep(&operation.State{ 163 Kind: operation.RunHook, 164 Step: operation.Queued, 165 Hook: &hook.Info{Kind: hooks.Start}, 166 }, nil), 167 } 168 169 err := executor.Run(op) 170 c.Assert(err, jc.ErrorIsNil) 171 172 c.Assert(op.prepare.gotState, gc.DeepEquals, initialState) 173 c.Assert(op.commit.gotState, gc.DeepEquals, *op.prepare.newState) 174 assertWroteState(c, statePath, *op.commit.newState) 175 c.Assert(executor.State(), gc.DeepEquals, *op.commit.newState) 176 } 177 178 func (s *ExecutorSuite) TestValidateStateChange(c *gc.C) { 179 initialState := justInstalledState() 180 executor, statePath := newExecutor(c, &initialState) 181 op := &mockOperation{ 182 prepare: newStep(&operation.State{ 183 Kind: operation.RunHook, 184 Step: operation.Pending, 185 }, nil), 186 } 187 188 err := executor.Run(op) 189 c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation": invalid operation state: missing hook info with Kind RunHook`) 190 c.Assert(errors.Cause(err), gc.ErrorMatches, "missing hook info with Kind RunHook") 191 192 assertWroteState(c, statePath, initialState) 193 c.Assert(executor.State(), gc.DeepEquals, initialState) 194 } 195 196 func (s *ExecutorSuite) TestFailPrepareNoStateChange(c *gc.C) { 197 initialState := justInstalledState() 198 executor, statePath := newExecutor(c, &initialState) 199 op := &mockOperation{ 200 prepare: newStep(nil, errors.New("pow")), 201 } 202 203 err := executor.Run(op) 204 c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation": pow`) 205 c.Assert(errors.Cause(err), gc.ErrorMatches, "pow") 206 207 c.Assert(op.prepare.gotState, gc.DeepEquals, initialState) 208 assertWroteState(c, statePath, initialState) 209 c.Assert(executor.State(), gc.DeepEquals, initialState) 210 } 211 212 func (s *ExecutorSuite) TestFailPrepareWithStateChange(c *gc.C) { 213 initialState := justInstalledState() 214 executor, statePath := newExecutor(c, &initialState) 215 op := &mockOperation{ 216 prepare: newStep(&operation.State{ 217 Kind: operation.RunHook, 218 Step: operation.Pending, 219 Hook: &hook.Info{Kind: hooks.Start}, 220 }, errors.New("blam")), 221 } 222 223 err := executor.Run(op) 224 c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation": blam`) 225 c.Assert(errors.Cause(err), gc.ErrorMatches, "blam") 226 227 c.Assert(op.prepare.gotState, gc.DeepEquals, initialState) 228 assertWroteState(c, statePath, *op.prepare.newState) 229 c.Assert(executor.State(), gc.DeepEquals, *op.prepare.newState) 230 } 231 232 func (s *ExecutorSuite) TestFailExecuteNoStateChange(c *gc.C) { 233 initialState := justInstalledState() 234 executor, statePath := newExecutor(c, &initialState) 235 op := &mockOperation{ 236 prepare: newStep(nil, nil), 237 execute: newStep(nil, errors.New("splat")), 238 } 239 240 err := executor.Run(op) 241 c.Assert(err, gc.ErrorMatches, `executing operation "mock operation": splat`) 242 c.Assert(errors.Cause(err), gc.ErrorMatches, "splat") 243 244 c.Assert(op.prepare.gotState, gc.DeepEquals, initialState) 245 assertWroteState(c, statePath, initialState) 246 c.Assert(executor.State(), gc.DeepEquals, initialState) 247 } 248 249 func (s *ExecutorSuite) TestFailExecuteWithStateChange(c *gc.C) { 250 initialState := justInstalledState() 251 executor, statePath := newExecutor(c, &initialState) 252 op := &mockOperation{ 253 prepare: newStep(nil, nil), 254 execute: newStep(&operation.State{ 255 Kind: operation.RunHook, 256 Step: operation.Pending, 257 Hook: &hook.Info{Kind: hooks.Start}, 258 }, errors.New("kerblooie")), 259 } 260 261 err := executor.Run(op) 262 c.Assert(err, gc.ErrorMatches, `executing operation "mock operation": kerblooie`) 263 c.Assert(errors.Cause(err), gc.ErrorMatches, "kerblooie") 264 265 c.Assert(op.prepare.gotState, gc.DeepEquals, initialState) 266 assertWroteState(c, statePath, *op.execute.newState) 267 c.Assert(executor.State(), gc.DeepEquals, *op.execute.newState) 268 } 269 270 func (s *ExecutorSuite) TestFailCommitNoStateChange(c *gc.C) { 271 initialState := justInstalledState() 272 executor, statePath := newExecutor(c, &initialState) 273 op := &mockOperation{ 274 prepare: newStep(nil, nil), 275 execute: newStep(nil, nil), 276 commit: newStep(nil, errors.New("whack")), 277 } 278 279 err := executor.Run(op) 280 c.Assert(err, gc.ErrorMatches, `committing operation "mock operation": whack`) 281 c.Assert(errors.Cause(err), gc.ErrorMatches, "whack") 282 283 c.Assert(op.prepare.gotState, gc.DeepEquals, initialState) 284 assertWroteState(c, statePath, initialState) 285 c.Assert(executor.State(), gc.DeepEquals, initialState) 286 } 287 288 func (s *ExecutorSuite) TestFailCommitWithStateChange(c *gc.C) { 289 initialState := justInstalledState() 290 executor, statePath := newExecutor(c, &initialState) 291 op := &mockOperation{ 292 prepare: newStep(nil, nil), 293 execute: newStep(nil, nil), 294 commit: newStep(&operation.State{ 295 Kind: operation.RunHook, 296 Step: operation.Pending, 297 Hook: &hook.Info{Kind: hooks.Start}, 298 }, errors.New("take that you bandit")), 299 } 300 301 err := executor.Run(op) 302 c.Assert(err, gc.ErrorMatches, `committing operation "mock operation": take that you bandit`) 303 c.Assert(errors.Cause(err), gc.ErrorMatches, "take that you bandit") 304 305 c.Assert(op.prepare.gotState, gc.DeepEquals, initialState) 306 assertWroteState(c, statePath, *op.commit.newState) 307 c.Assert(executor.State(), gc.DeepEquals, *op.commit.newState) 308 } 309 310 func (s *ExecutorSuite) initLockTest(c *gc.C, lockFunc func(string) (func(), error)) operation.Executor { 311 initialState := justInstalledState() 312 statePath := filepath.Join(c.MkDir(), "state") 313 err := operation.NewStateFile(statePath).Write(&initialState) 314 c.Assert(err, jc.ErrorIsNil) 315 executor, err := operation.NewExecutor(statePath, operation.State{}, lockFunc) 316 c.Assert(err, jc.ErrorIsNil) 317 318 return executor 319 } 320 321 func (s *ExecutorSuite) TestLockSucceedsStepsCalled(c *gc.C) { 322 op := &mockOperation{ 323 needsLock: true, 324 prepare: newStep(nil, nil), 325 execute: newStep(nil, nil), 326 commit: newStep(nil, nil), 327 } 328 329 mockLock := &mockLockFunc{op: op} 330 lockFunc := mockLock.newSucceedingLock() 331 executor := s.initLockTest(c, lockFunc) 332 333 err := executor.Run(op) 334 c.Assert(err, jc.ErrorIsNil) 335 336 c.Assert(mockLock.calledLock, jc.IsTrue) 337 c.Assert(mockLock.calledUnlock, jc.IsTrue) 338 c.Assert(mockLock.noStepsCalledOnLock, jc.IsTrue) 339 340 expectedStepsOnUnlock := []bool{true, true, true} 341 c.Assert(mockLock.stepsCalledOnUnlock, gc.DeepEquals, expectedStepsOnUnlock) 342 } 343 344 func (s *ExecutorSuite) TestLockFailsOpsStepsNotCalled(c *gc.C) { 345 op := &mockOperation{ 346 needsLock: true, 347 prepare: newStep(nil, nil), 348 execute: newStep(nil, nil), 349 commit: newStep(nil, nil), 350 } 351 352 mockLock := &mockLockFunc{op: op} 353 lockFunc := mockLock.newFailingLock() 354 executor := s.initLockTest(c, lockFunc) 355 356 err := executor.Run(op) 357 c.Assert(err, gc.ErrorMatches, "could not acquire lock: wat") 358 359 c.Assert(mockLock.calledLock, jc.IsFalse) 360 c.Assert(mockLock.calledUnlock, jc.IsFalse) 361 c.Assert(mockLock.noStepsCalledOnLock, jc.IsTrue) 362 363 c.Assert(op.prepare.called, jc.IsFalse) 364 c.Assert(op.execute.called, jc.IsFalse) 365 c.Assert(op.commit.called, jc.IsFalse) 366 } 367 368 func (s *ExecutorSuite) testLockUnlocksOnError(c *gc.C, op *mockOperation) (error, *mockLockFunc) { 369 mockLock := &mockLockFunc{op: op} 370 lockFunc := mockLock.newSucceedingLock() 371 executor := s.initLockTest(c, lockFunc) 372 373 err := executor.Run(op) 374 375 c.Assert(mockLock.calledLock, jc.IsTrue) 376 c.Assert(mockLock.calledUnlock, jc.IsTrue) 377 c.Assert(mockLock.noStepsCalledOnLock, jc.IsTrue) 378 379 return err, mockLock 380 } 381 382 func (s *ExecutorSuite) TestLockUnlocksOnError_Prepare(c *gc.C) { 383 op := &mockOperation{ 384 needsLock: true, 385 prepare: newStep(nil, errors.New("kerblooie")), 386 execute: newStep(nil, nil), 387 commit: newStep(nil, nil), 388 } 389 390 err, mockLock := s.testLockUnlocksOnError(c, op) 391 c.Assert(err, gc.ErrorMatches, `preparing operation "mock operation": kerblooie`) 392 c.Assert(errors.Cause(err), gc.ErrorMatches, "kerblooie") 393 394 expectedStepsOnUnlock := []bool{true, false, false} 395 c.Assert(mockLock.stepsCalledOnUnlock, gc.DeepEquals, expectedStepsOnUnlock) 396 } 397 398 func (s *ExecutorSuite) TestLockUnlocksOnError_Execute(c *gc.C) { 399 op := &mockOperation{ 400 needsLock: true, 401 prepare: newStep(nil, nil), 402 execute: newStep(nil, errors.New("you asked for it")), 403 commit: newStep(nil, nil), 404 } 405 406 err, mockLock := s.testLockUnlocksOnError(c, op) 407 c.Assert(err, gc.ErrorMatches, `executing operation "mock operation": you asked for it`) 408 c.Assert(errors.Cause(err), gc.ErrorMatches, "you asked for it") 409 410 expectedStepsOnUnlock := []bool{true, true, false} 411 c.Assert(mockLock.stepsCalledOnUnlock, gc.DeepEquals, expectedStepsOnUnlock) 412 } 413 414 func (s *ExecutorSuite) TestLockUnlocksOnError_Commit(c *gc.C) { 415 op := &mockOperation{ 416 needsLock: true, 417 prepare: newStep(nil, nil), 418 execute: newStep(nil, nil), 419 commit: newStep(nil, errors.New("well, shit")), 420 } 421 422 err, mockLock := s.testLockUnlocksOnError(c, op) 423 c.Assert(err, gc.ErrorMatches, `committing operation "mock operation": well, shit`) 424 c.Assert(errors.Cause(err), gc.ErrorMatches, "well, shit") 425 426 expectedStepsOnUnlock := []bool{true, true, true} 427 c.Assert(mockLock.stepsCalledOnUnlock, gc.DeepEquals, expectedStepsOnUnlock) 428 } 429 430 type mockLockFunc struct { 431 noStepsCalledOnLock bool 432 stepsCalledOnUnlock []bool 433 calledLock bool 434 calledUnlock bool 435 op *mockOperation 436 } 437 438 func (mock *mockLockFunc) newFailingLock() func(string) (func(), error) { 439 return func(string) (func(), error) { 440 mock.noStepsCalledOnLock = mock.op.prepare.called == false && 441 mock.op.commit.called == false 442 return nil, errors.New("wat") 443 } 444 } 445 446 func (mock *mockLockFunc) newSucceedingLock() func(string) (func(), error) { 447 return func(string) (func(), error) { 448 mock.calledLock = true 449 // Ensure that when we lock no operation has been called 450 mock.noStepsCalledOnLock = mock.op.prepare.called == false && 451 mock.op.commit.called == false 452 return func() { 453 // Record steps called when unlocking 454 mock.stepsCalledOnUnlock = []bool{mock.op.prepare.called, 455 mock.op.execute.called, 456 mock.op.commit.called} 457 mock.calledUnlock = true 458 }, nil 459 } 460 } 461 462 type mockStep struct { 463 gotState operation.State 464 newState *operation.State 465 err error 466 called bool 467 } 468 469 func newStep(newState *operation.State, err error) *mockStep { 470 return &mockStep{newState: newState, err: err} 471 } 472 473 func (step *mockStep) run(state operation.State) (*operation.State, error) { 474 step.called = true 475 step.gotState = state 476 return step.newState, step.err 477 } 478 479 type mockOperation struct { 480 needsLock bool 481 prepare *mockStep 482 execute *mockStep 483 commit *mockStep 484 } 485 486 func (op *mockOperation) String() string { 487 return "mock operation" 488 } 489 490 func (op *mockOperation) NeedsGlobalMachineLock() bool { 491 return op.needsLock 492 } 493 494 func (op *mockOperation) Prepare(state operation.State) (*operation.State, error) { 495 return op.prepare.run(state) 496 } 497 498 func (op *mockOperation) Execute(state operation.State) (*operation.State, error) { 499 return op.execute.run(state) 500 } 501 502 func (op *mockOperation) Commit(state operation.State) (*operation.State, error) { 503 return op.commit.run(state) 504 }