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