github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/worker/dependency/engine_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package dependency_test 5 6 import ( 7 "time" 8 9 "github.com/juju/errors" 10 jc "github.com/juju/testing/checkers" 11 gc "gopkg.in/check.v1" 12 13 coretesting "github.com/juju/juju/testing" 14 "github.com/juju/juju/worker" 15 "github.com/juju/juju/worker/dependency" 16 ) 17 18 type EngineSuite struct { 19 engineFixture 20 } 21 22 var _ = gc.Suite(&EngineSuite{}) 23 24 func (s *EngineSuite) TestInstallConvenienceWrapper(c *gc.C) { 25 mh1 := newManifoldHarness() 26 mh2 := newManifoldHarness() 27 mh3 := newManifoldHarness() 28 29 err := dependency.Install(s.engine, dependency.Manifolds{ 30 "mh1": mh1.Manifold(), 31 "mh2": mh2.Manifold(), 32 "mh3": mh3.Manifold(), 33 }) 34 c.Assert(err, jc.ErrorIsNil) 35 36 mh1.AssertOneStart(c) 37 mh2.AssertOneStart(c) 38 mh3.AssertOneStart(c) 39 } 40 41 func (s *EngineSuite) TestInstallNoInputs(c *gc.C) { 42 43 // Install a worker, check it starts. 44 mh1 := newManifoldHarness() 45 err := s.engine.Install("some-task", mh1.Manifold()) 46 c.Assert(err, jc.ErrorIsNil) 47 mh1.AssertOneStart(c) 48 49 // Install a second independent worker; check the first in untouched. 50 mh2 := newManifoldHarness() 51 err = s.engine.Install("other-task", mh2.Manifold()) 52 c.Assert(err, jc.ErrorIsNil) 53 mh2.AssertOneStart(c) 54 mh1.AssertNoStart(c) 55 } 56 57 func (s *EngineSuite) TestInstallUnknownInputs(c *gc.C) { 58 59 // Install a worker with an unmet dependency, check it doesn't start 60 // (because the implementation returns ErrMissing). 61 mh1 := newManifoldHarness("later-task") 62 err := s.engine.Install("some-task", mh1.Manifold()) 63 c.Assert(err, jc.ErrorIsNil) 64 mh1.AssertNoStart(c) 65 66 // Install its dependency; check both start. 67 mh2 := newManifoldHarness() 68 err = s.engine.Install("later-task", mh2.Manifold()) 69 c.Assert(err, jc.ErrorIsNil) 70 mh2.AssertOneStart(c) 71 mh1.AssertOneStart(c) 72 } 73 74 func (s *EngineSuite) TestDoubleInstall(c *gc.C) { 75 76 // Install a worker. 77 mh := newManifoldHarness() 78 err := s.engine.Install("some-task", mh.Manifold()) 79 c.Assert(err, jc.ErrorIsNil) 80 mh.AssertOneStart(c) 81 82 // Can't install another worker with the same name. 83 err = s.engine.Install("some-task", mh.Manifold()) 84 c.Assert(err, gc.ErrorMatches, `"some-task" manifold already installed`) 85 mh.AssertNoStart(c) 86 } 87 88 func (s *EngineSuite) TestInstallCycle(c *gc.C) { 89 90 // Install a worker with an unmet dependency. 91 mh1 := newManifoldHarness("robin-hood") 92 err := s.engine.Install("friar-tuck", mh1.Manifold()) 93 c.Assert(err, jc.ErrorIsNil) 94 mh1.AssertNoStart(c) 95 96 // Can't install another worker that creates a dependency cycle. 97 mh2 := newManifoldHarness("friar-tuck") 98 err = s.engine.Install("robin-hood", mh2.Manifold()) 99 c.Assert(err, gc.ErrorMatches, `cannot install "robin-hood" manifold: cycle detected at .*`) 100 mh2.AssertNoStart(c) 101 } 102 103 func (s *EngineSuite) TestInstallAlreadyStopped(c *gc.C) { 104 105 // Shut down the engine. 106 err := worker.Stop(s.engine) 107 c.Assert(err, jc.ErrorIsNil) 108 109 // Can't start a new task. 110 mh := newManifoldHarness() 111 err = s.engine.Install("some-task", mh.Manifold()) 112 c.Assert(err, gc.ErrorMatches, "engine is shutting down") 113 mh.AssertNoStart(c) 114 } 115 116 func (s *EngineSuite) TestStartGetResourceExistenceOnly(c *gc.C) { 117 118 // Start a task with a dependency. 119 mh1 := newManifoldHarness() 120 err := s.engine.Install("some-task", mh1.Manifold()) 121 c.Assert(err, jc.ErrorIsNil) 122 mh1.AssertOneStart(c) 123 124 // Start another task that depends on it, ourselves depending on the 125 // implementation of manifoldHarness, which calls getResource(foo, nil). 126 mh2 := newManifoldHarness("some-task") 127 err = s.engine.Install("other-task", mh2.Manifold()) 128 c.Assert(err, jc.ErrorIsNil) 129 mh2.AssertOneStart(c) 130 } 131 132 func (s *EngineSuite) TestStartGetResourceUndeclaredName(c *gc.C) { 133 134 // Install a task and make sure it's started. 135 mh1 := newManifoldHarness() 136 err := s.engine.Install("some-task", mh1.Manifold()) 137 c.Assert(err, jc.ErrorIsNil) 138 mh1.AssertOneStart(c) 139 140 // Install another task with an undeclared dependency on the started task. 141 done := make(chan struct{}) 142 err = s.engine.Install("other-task", dependency.Manifold{ 143 Start: func(getResource dependency.GetResourceFunc) (worker.Worker, error) { 144 err := getResource("some-task", nil) 145 c.Check(err, gc.Equals, dependency.ErrMissing) 146 close(done) 147 // Return a real worker so we don't keep restarting and potentially double-closing. 148 return startMinimalWorker(getResource) 149 }, 150 }) 151 c.Assert(err, jc.ErrorIsNil) 152 153 // Wait for the check to complete before we stop. 154 select { 155 case <-done: 156 case <-time.After(coretesting.LongWait): 157 c.Fatalf("dependent task never started") 158 } 159 } 160 161 func (s *EngineSuite) testStartGetResource(c *gc.C, outErr error) { 162 163 // Start a task with an Output func that checks what it's passed, and wait for it to start. 164 var target interface{} 165 expectTarget := &target 166 mh1 := newManifoldHarness() 167 manifold := mh1.Manifold() 168 manifold.Output = func(worker worker.Worker, target interface{}) error { 169 // Check we got passed what we expect regardless... 170 c.Check(target, gc.DeepEquals, expectTarget) 171 // ...and return the configured error. 172 return outErr 173 } 174 err := s.engine.Install("some-task", manifold) 175 c.Assert(err, jc.ErrorIsNil) 176 mh1.AssertOneStart(c) 177 178 // Start another that tries to use the above dependency. 179 done := make(chan struct{}) 180 err = s.engine.Install("other-task", dependency.Manifold{ 181 Inputs: []string{"some-task"}, 182 Start: func(getResource dependency.GetResourceFunc) (worker.Worker, error) { 183 err := getResource("some-task", &target) 184 // Check the result from some-task's Output func matches what we expect. 185 c.Check(err, gc.Equals, outErr) 186 close(done) 187 // Return a real worker so we don't keep restarting and potentially double-closing. 188 return startMinimalWorker(getResource) 189 }, 190 }) 191 c.Check(err, jc.ErrorIsNil) 192 193 // Wait for the check to complete before we stop. 194 select { 195 case <-done: 196 case <-time.After(coretesting.LongWait): 197 c.Fatalf("other-task never started") 198 } 199 } 200 201 func (s *EngineSuite) TestStartGetResourceAccept(c *gc.C) { 202 s.testStartGetResource(c, nil) 203 } 204 205 func (s *EngineSuite) TestStartGetResourceReject(c *gc.C) { 206 s.testStartGetResource(c, errors.New("not good enough")) 207 } 208 209 func (s *EngineSuite) TestErrorRestartsDependents(c *gc.C) { 210 211 // Start two tasks, one dependent on the other. 212 mh1 := newManifoldHarness() 213 err := s.engine.Install("error-task", mh1.Manifold()) 214 c.Assert(err, jc.ErrorIsNil) 215 mh1.AssertOneStart(c) 216 217 mh2 := newManifoldHarness("error-task") 218 err = s.engine.Install("some-task", mh2.Manifold()) 219 c.Assert(err, jc.ErrorIsNil) 220 mh2.AssertOneStart(c) 221 222 // Induce an error in the dependency... 223 mh1.InjectError(c, errors.New("ZAP")) 224 225 // ...and check that each task restarts once. 226 mh1.AssertOneStart(c) 227 mh2.AssertOneStart(c) 228 } 229 230 func (s *EngineSuite) TestErrorPreservesDependencies(c *gc.C) { 231 232 // Start two tasks, one dependent on the other. 233 mh1 := newManifoldHarness() 234 err := s.engine.Install("some-task", mh1.Manifold()) 235 c.Assert(err, jc.ErrorIsNil) 236 mh1.AssertOneStart(c) 237 mh2 := newManifoldHarness("some-task") 238 err = s.engine.Install("error-task", mh2.Manifold()) 239 c.Assert(err, jc.ErrorIsNil) 240 mh2.AssertOneStart(c) 241 242 // Induce an error in the dependent... 243 mh2.InjectError(c, errors.New("BLAM")) 244 245 // ...and check that only the dependent restarts. 246 mh1.AssertNoStart(c) 247 mh2.AssertOneStart(c) 248 } 249 250 func (s *EngineSuite) TestCompletedWorkerNotRestartedOnExit(c *gc.C) { 251 252 // Start a task. 253 mh1 := newManifoldHarness() 254 err := s.engine.Install("stop-task", mh1.Manifold()) 255 c.Assert(err, jc.ErrorIsNil) 256 mh1.AssertOneStart(c) 257 258 // Stop it without error, and check it doesn't start again. 259 mh1.InjectError(c, nil) 260 mh1.AssertNoStart(c) 261 } 262 263 func (s *EngineSuite) TestCompletedWorkerRestartedByDependencyChange(c *gc.C) { 264 265 // Start a task with a dependency. 266 mh1 := newManifoldHarness() 267 err := s.engine.Install("some-task", mh1.Manifold()) 268 c.Assert(err, jc.ErrorIsNil) 269 mh1.AssertOneStart(c) 270 mh2 := newManifoldHarness("some-task") 271 err = s.engine.Install("stop-task", mh2.Manifold()) 272 c.Assert(err, jc.ErrorIsNil) 273 mh2.AssertOneStart(c) 274 275 // Complete the dependent task successfully. 276 mh2.InjectError(c, nil) 277 mh2.AssertNoStart(c) 278 279 // Bounce the dependency, and check the dependent is started again. 280 mh1.InjectError(c, errors.New("CLUNK")) 281 mh1.AssertOneStart(c) 282 mh2.AssertOneStart(c) 283 } 284 285 func (s *EngineSuite) TestRestartRestartsDependents(c *gc.C) { 286 287 // Start a dependency chain of 3 workers. 288 mh1 := newManifoldHarness() 289 err := s.engine.Install("error-task", mh1.Manifold()) 290 c.Assert(err, jc.ErrorIsNil) 291 mh1.AssertOneStart(c) 292 mh2 := newManifoldHarness("error-task") 293 err = s.engine.Install("restart-task", mh2.Manifold()) 294 c.Assert(err, jc.ErrorIsNil) 295 mh2.AssertOneStart(c) 296 mh3 := newManifoldHarness("restart-task") 297 err = s.engine.Install("consequent-restart-task", mh3.Manifold()) 298 c.Assert(err, jc.ErrorIsNil) 299 mh3.AssertOneStart(c) 300 301 // Once they're all running, induce an error at the top level, which will 302 // cause the next level to be killed cleanly.... 303 mh1.InjectError(c, errors.New("ZAP")) 304 305 // ...but should still cause all 3 workers to bounce. 306 mh1.AssertOneStart(c) 307 mh2.AssertOneStart(c) 308 mh3.AssertOneStart(c) 309 } 310 311 func (s *EngineSuite) TestIsFatal(c *gc.C) { 312 313 // Start an engine that pays attention to fatal errors. 314 fatalError := errors.New("KABOOM") 315 s.stopEngine(c) 316 s.startEngine(c, func(err error) bool { 317 return err == fatalError 318 }) 319 320 // Start two independent workers. 321 mh1 := newManifoldHarness() 322 err := s.engine.Install("some-task", mh1.Manifold()) 323 c.Assert(err, jc.ErrorIsNil) 324 mh1.AssertOneStart(c) 325 mh2 := newManifoldHarness() 326 err = s.engine.Install("other-task", mh2.Manifold()) 327 c.Assert(err, jc.ErrorIsNil) 328 mh2.AssertOneStart(c) 329 330 // Bounce one worker with Just Some Error; check that worker bounces. 331 mh1.InjectError(c, errors.New("splort")) 332 mh1.AssertOneStart(c) 333 mh2.AssertNoStart(c) 334 335 // Bounce another worker with the fatal error; check the engine exits with 336 // the right error. 337 mh2.InjectError(c, fatalError) 338 mh1.AssertNoStart(c) 339 mh2.AssertNoStart(c) 340 err = worker.Stop(s.engine) 341 c.Assert(err, gc.Equals, fatalError) 342 343 // Clear out s.engine -- lest TearDownTest freak out about the error. 344 s.engine = nil 345 } 346 347 func (s *EngineSuite) TestErrMissing(c *gc.C) { 348 349 // ErrMissing is implicitly and indirectly tested by the default 350 // manifoldHarness.start method throughout this suite, but this 351 // test explores its behaviour in pathological cases. 352 353 // Start a simple dependency. 354 mh1 := newManifoldHarness() 355 err := s.engine.Install("some-task", mh1.Manifold()) 356 c.Assert(err, jc.ErrorIsNil) 357 mh1.AssertOneStart(c) 358 359 // Start a dependent that always complains ErrMissing. 360 mh2 := newManifoldHarness("some-task") 361 manifold := mh2.Manifold() 362 manifold.Start = func(_ dependency.GetResourceFunc) (worker.Worker, error) { 363 mh2.starts <- struct{}{} 364 return nil, dependency.ErrMissing 365 } 366 err = s.engine.Install("unmet-task", manifold) 367 c.Assert(err, jc.ErrorIsNil) 368 mh2.AssertOneStart(c) 369 370 // Bounce the dependency; check the dependent bounces once or twice (it will 371 // react to both the stop and the start of the dependency, but may be lucky 372 // enough to only restart once). 373 mh1.InjectError(c, errors.New("kerrang")) 374 mh1.AssertOneStart(c) 375 startCount := 0 376 stable := false 377 for !stable { 378 select { 379 case <-mh2.starts: 380 startCount++ 381 case <-time.After(coretesting.ShortWait): 382 stable = true 383 } 384 } 385 c.Logf("saw %d starts", startCount) 386 c.Assert(startCount, jc.GreaterThan, 0) 387 c.Assert(startCount, jc.LessThan, 3) 388 389 // Stop the dependency for good; check the dependent is restarted just once. 390 mh1.InjectError(c, nil) 391 mh1.AssertNoStart(c) 392 mh2.AssertOneStart(c) 393 } 394 395 // TestWorstError starts an engine with two manifolds that always error 396 // with fatal errors. We test that the most important error is the one 397 // returned by the engine. 398 // 399 // This test uses manifolds whose workers ignore kill requests. We want 400 // this (dangerous!) behaviour so that we don't race over which fatal 401 // error is seen by the engine first. 402 func (s *EngineSuite) TestWorstError(c *gc.C) { 403 // Setup the errors, their importance, and the function 404 // that decides. 405 importantError := errors.New("an important error") 406 moreImportant := func(_, _ error) error { 407 return importantError 408 } 409 410 allFatal := func(error) bool { return true } 411 412 // Start a new engine with moreImportant configured 413 config := dependency.EngineConfig{ 414 IsFatal: allFatal, 415 WorstError: moreImportant, 416 ErrorDelay: coretesting.ShortWait / 2, 417 BounceDelay: coretesting.ShortWait / 10, 418 } 419 engine, err := dependency.NewEngine(config) 420 c.Assert(err, jc.ErrorIsNil) 421 422 mh1 := newErrorIgnoringManifoldHarness() 423 err = engine.Install("task", mh1.Manifold()) 424 c.Assert(err, jc.ErrorIsNil) 425 mh1.AssertOneStart(c) 426 427 mh2 := newErrorIgnoringManifoldHarness() 428 err = engine.Install("another task", mh2.Manifold()) 429 c.Assert(err, jc.ErrorIsNil) 430 mh2.AssertOneStart(c) 431 432 mh1.InjectError(c, errors.New("kerrang")) 433 mh2.InjectError(c, importantError) 434 435 err = engine.Wait() 436 c.Check(err, gc.ErrorMatches, importantError.Error()) 437 report := engine.Report() 438 c.Check(report["error"], gc.ErrorMatches, importantError.Error()) 439 } 440 441 func (s *EngineSuite) TestConfigValidate(c *gc.C) { 442 validIsFatal := func(error) bool { return true } 443 validWorstError := func(err0, err1 error) error { return err0 } 444 validErrorDelay := time.Second 445 validBounceDelay := time.Second 446 447 tests := []struct { 448 about string 449 config dependency.EngineConfig 450 err string 451 }{{ 452 "IsFatal invalid", 453 dependency.EngineConfig{nil, validWorstError, validErrorDelay, validBounceDelay}, 454 "IsFatal not specified", 455 }, { 456 "WorstError invalid", 457 dependency.EngineConfig{validIsFatal, nil, validErrorDelay, validBounceDelay}, 458 "WorstError not specified", 459 }, { 460 "ErrorDelay invalid", 461 dependency.EngineConfig{validIsFatal, validWorstError, -time.Second, validBounceDelay}, 462 "ErrorDelay is negative", 463 }, { 464 "BounceDelay invalid", 465 dependency.EngineConfig{validIsFatal, validWorstError, validErrorDelay, -time.Second}, 466 "BounceDelay is negative", 467 }} 468 469 for i, test := range tests { 470 c.Logf("test %d: %v", i, test.about) 471 472 c.Logf("config validation...") 473 validateErr := test.config.Validate() 474 c.Check(validateErr, gc.ErrorMatches, test.err) 475 476 c.Logf("engine creation...") 477 engine, createErr := dependency.NewEngine(test.config) 478 c.Check(engine, gc.IsNil) 479 c.Check(createErr, gc.ErrorMatches, "invalid config: "+test.err) 480 } 481 } 482 483 func (s *EngineSuite) TestValidateEmptyManifolds(c *gc.C) { 484 err := dependency.Validate(dependency.Manifolds{}) 485 c.Check(err, jc.ErrorIsNil) 486 } 487 488 func (s *EngineSuite) TestValidateTrivialCycle(c *gc.C) { 489 err := dependency.Validate(dependency.Manifolds{ 490 "a": dependency.Manifold{Inputs: []string{"a"}}, 491 }) 492 c.Check(err.Error(), gc.Equals, `cycle detected at "a" (considering: map[a:true])`) 493 } 494 495 func (s *EngineSuite) TestValidateComplexManifolds(c *gc.C) { 496 497 // Create a bunch of manifolds with tangled but acyclic dependencies; check 498 // that they pass validation. 499 manifolds := dependency.Manifolds{ 500 "root1": dependency.Manifold{}, 501 "root2": dependency.Manifold{}, 502 "mid1": dependency.Manifold{Inputs: []string{"root1"}}, 503 "mid2": dependency.Manifold{Inputs: []string{"root1", "root2"}}, 504 "leaf1": dependency.Manifold{Inputs: []string{"root2", "mid1"}}, 505 "leaf2": dependency.Manifold{Inputs: []string{"root1", "mid2"}}, 506 "leaf3": dependency.Manifold{Inputs: []string{"root1", "root2", "mid1", "mid2"}}, 507 } 508 err := dependency.Validate(manifolds) 509 c.Check(err, jc.ErrorIsNil) 510 511 // Introduce a cycle; check the manifolds no longer validate. 512 manifolds["root1"] = dependency.Manifold{Inputs: []string{"leaf1"}} 513 err = dependency.Validate(manifolds) 514 c.Check(err, gc.ErrorMatches, "cycle detected at .*") 515 } 516 517 func (s *EngineSuite) TestTracedErrMissing(c *gc.C) { 518 519 // Install a worker with an unmet dependency, check it doesn't start 520 // (because the implementation returns ErrMissing). 521 mh1 := newTracedManifoldHarness("later-task") 522 err := s.engine.Install("some-task", mh1.Manifold()) 523 c.Assert(err, jc.ErrorIsNil) 524 mh1.AssertNoStart(c) 525 526 // Install its dependency; check both start. 527 mh2 := newTracedManifoldHarness() 528 err = s.engine.Install("later-task", mh2.Manifold()) 529 c.Assert(err, jc.ErrorIsNil) 530 mh2.AssertOneStart(c) 531 mh1.AssertOneStart(c) 532 }