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