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