github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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 s.engine = dependency.NewEngine(isFatal, coretesting.ShortWait/2, coretesting.ShortWait/10) 38 } 39 40 func (s *EngineSuite) stopEngine(c *gc.C) { 41 if s.engine != nil { 42 err := worker.Stop(s.engine) 43 s.engine = nil 44 c.Check(err, jc.ErrorIsNil) 45 } 46 } 47 48 func (s *EngineSuite) TestInstallConvenienceWrapper(c *gc.C) { 49 mh1 := newManifoldHarness() 50 mh2 := newManifoldHarness() 51 mh3 := newManifoldHarness() 52 53 err := dependency.Install(s.engine, dependency.Manifolds{ 54 "mh1": mh1.Manifold(), 55 "mh2": mh2.Manifold(), 56 "mh3": mh3.Manifold(), 57 }) 58 c.Assert(err, jc.ErrorIsNil) 59 60 mh1.AssertOneStart(c) 61 mh2.AssertOneStart(c) 62 mh3.AssertOneStart(c) 63 } 64 65 func (s *EngineSuite) TestInstallNoInputs(c *gc.C) { 66 67 // Install a worker, check it starts. 68 mh1 := newManifoldHarness() 69 err := s.engine.Install("some-task", mh1.Manifold()) 70 c.Assert(err, jc.ErrorIsNil) 71 mh1.AssertOneStart(c) 72 73 // Install a second independent worker; check the first in untouched. 74 mh2 := newManifoldHarness() 75 err = s.engine.Install("other-task", mh2.Manifold()) 76 c.Assert(err, jc.ErrorIsNil) 77 mh2.AssertOneStart(c) 78 mh1.AssertNoStart(c) 79 } 80 81 func (s *EngineSuite) TestInstallUnknownInputs(c *gc.C) { 82 83 // Install a worker with an unmet dependency, check it doesn't start 84 // (because the implementation returns ErrMissing). 85 mh1 := newManifoldHarness("later-task") 86 err := s.engine.Install("some-task", mh1.Manifold()) 87 c.Assert(err, jc.ErrorIsNil) 88 mh1.AssertNoStart(c) 89 90 // Install its dependency; check both start. 91 mh2 := newManifoldHarness() 92 err = s.engine.Install("later-task", mh2.Manifold()) 93 c.Assert(err, jc.ErrorIsNil) 94 mh2.AssertOneStart(c) 95 mh1.AssertOneStart(c) 96 } 97 98 func (s *EngineSuite) TestDoubleInstall(c *gc.C) { 99 100 // Install a worker. 101 mh := newManifoldHarness() 102 err := s.engine.Install("some-task", mh.Manifold()) 103 c.Assert(err, jc.ErrorIsNil) 104 mh.AssertOneStart(c) 105 106 // Can't install another worker with the same name. 107 err = s.engine.Install("some-task", mh.Manifold()) 108 c.Assert(err, gc.ErrorMatches, `"some-task" manifold already installed`) 109 mh.AssertNoStart(c) 110 } 111 112 func (s *EngineSuite) TestInstallAlreadyStopped(c *gc.C) { 113 114 // Shut down the engine. 115 err := worker.Stop(s.engine) 116 c.Assert(err, jc.ErrorIsNil) 117 118 // Can't start a new task. 119 mh := newManifoldHarness() 120 err = s.engine.Install("some-task", mh.Manifold()) 121 c.Assert(err, gc.ErrorMatches, "engine is shutting down") 122 mh.AssertNoStart(c) 123 } 124 125 func (s *EngineSuite) TestStartGetResourceExistenceOnly(c *gc.C) { 126 127 // Start a task with a dependency. 128 mh1 := newManifoldHarness() 129 err := s.engine.Install("some-task", mh1.Manifold()) 130 c.Assert(err, jc.ErrorIsNil) 131 mh1.AssertOneStart(c) 132 133 // Start another task that depends on it, ourselves depending on the 134 // implementation of manifoldHarness, which calls getResource(foo, nil). 135 mh2 := newManifoldHarness("some-task") 136 err = s.engine.Install("other-task", mh2.Manifold()) 137 c.Assert(err, jc.ErrorIsNil) 138 mh2.AssertOneStart(c) 139 } 140 141 func (s *EngineSuite) TestStartGetResourceUndeclaredName(c *gc.C) { 142 143 // Install a task and make sure it's started. 144 mh1 := newManifoldHarness() 145 err := s.engine.Install("some-task", mh1.Manifold()) 146 c.Assert(err, jc.ErrorIsNil) 147 mh1.AssertOneStart(c) 148 149 // Install another task with an undeclared dependency on the started task. 150 done := make(chan struct{}) 151 err = s.engine.Install("other-task", dependency.Manifold{ 152 Start: func(getResource dependency.GetResourceFunc) (worker.Worker, error) { 153 err := getResource("some-task", nil) 154 c.Check(err, gc.Equals, dependency.ErrMissing) 155 close(done) 156 // Return a real worker so we don't keep restarting and potentially double-closing. 157 return startMinimalWorker(getResource) 158 }, 159 }) 160 c.Assert(err, jc.ErrorIsNil) 161 162 // Wait for the check to complete before we stop. 163 select { 164 case <-done: 165 case <-time.After(coretesting.LongWait): 166 c.Fatalf("dependent task never started") 167 } 168 } 169 170 func (s *EngineSuite) testStartGetResource(c *gc.C, outErr error) { 171 172 // Start a task with an Output func that checks what it's passed, and wait for it to start. 173 var target interface{} 174 expectTarget := &target 175 mh1 := newManifoldHarness() 176 manifold := mh1.Manifold() 177 manifold.Output = func(worker worker.Worker, target interface{}) error { 178 // Check we got passed what we expect regardless... 179 c.Check(target, gc.DeepEquals, expectTarget) 180 // ...and return the configured error. 181 return outErr 182 } 183 err := s.engine.Install("some-task", manifold) 184 c.Assert(err, jc.ErrorIsNil) 185 mh1.AssertOneStart(c) 186 187 // Start another that tries to use the above dependency. 188 done := make(chan struct{}) 189 err = s.engine.Install("other-task", dependency.Manifold{ 190 Inputs: []string{"some-task"}, 191 Start: func(getResource dependency.GetResourceFunc) (worker.Worker, error) { 192 err := getResource("some-task", &target) 193 // Check the result from some-task's Output func matches what we expect. 194 c.Check(err, gc.Equals, outErr) 195 close(done) 196 // Return a real worker so we don't keep restarting and potentially double-closing. 197 return startMinimalWorker(getResource) 198 }, 199 }) 200 c.Check(err, jc.ErrorIsNil) 201 202 // Wait for the check to complete before we stop. 203 select { 204 case <-done: 205 case <-time.After(coretesting.LongWait): 206 c.Fatalf("other-task never started") 207 } 208 } 209 210 func (s *EngineSuite) TestStartGetResourceAccept(c *gc.C) { 211 s.testStartGetResource(c, nil) 212 } 213 214 func (s *EngineSuite) TestStartGetResourceReject(c *gc.C) { 215 s.testStartGetResource(c, errors.New("not good enough")) 216 } 217 218 func (s *EngineSuite) TestErrorRestartsDependents(c *gc.C) { 219 220 // Start two tasks, one dependent on the other. 221 mh1 := newManifoldHarness() 222 err := s.engine.Install("error-task", mh1.Manifold()) 223 c.Assert(err, jc.ErrorIsNil) 224 mh1.AssertOneStart(c) 225 226 mh2 := newManifoldHarness("error-task") 227 err = s.engine.Install("some-task", mh2.Manifold()) 228 c.Assert(err, jc.ErrorIsNil) 229 mh2.AssertOneStart(c) 230 231 // Induce an error in the dependency... 232 mh1.InjectError(c, errors.New("ZAP")) 233 234 // ...and check that each task restarts once. 235 mh1.AssertOneStart(c) 236 mh2.AssertOneStart(c) 237 } 238 239 func (s *EngineSuite) TestErrorPreservesDependencies(c *gc.C) { 240 241 // Start two tasks, one dependent on the other. 242 mh1 := newManifoldHarness() 243 err := s.engine.Install("some-task", mh1.Manifold()) 244 c.Assert(err, jc.ErrorIsNil) 245 mh1.AssertOneStart(c) 246 mh2 := newManifoldHarness("some-task") 247 err = s.engine.Install("error-task", mh2.Manifold()) 248 c.Assert(err, jc.ErrorIsNil) 249 mh2.AssertOneStart(c) 250 251 // Induce an error in the dependent... 252 mh2.InjectError(c, errors.New("BLAM")) 253 254 // ...and check that only the dependent restarts. 255 mh1.AssertNoStart(c) 256 mh2.AssertOneStart(c) 257 } 258 259 func (s *EngineSuite) TestCompletedWorkerNotRestartedOnExit(c *gc.C) { 260 261 // Start a task. 262 mh1 := newManifoldHarness() 263 err := s.engine.Install("stop-task", mh1.Manifold()) 264 c.Assert(err, jc.ErrorIsNil) 265 mh1.AssertOneStart(c) 266 267 // Stop it without error, and check it doesn't start again. 268 mh1.InjectError(c, nil) 269 mh1.AssertNoStart(c) 270 } 271 272 func (s *EngineSuite) TestCompletedWorkerRestartedByDependencyChange(c *gc.C) { 273 274 // Start a task with a dependency. 275 mh1 := newManifoldHarness() 276 err := s.engine.Install("some-task", mh1.Manifold()) 277 c.Assert(err, jc.ErrorIsNil) 278 mh1.AssertOneStart(c) 279 mh2 := newManifoldHarness("some-task") 280 err = s.engine.Install("stop-task", mh2.Manifold()) 281 c.Assert(err, jc.ErrorIsNil) 282 mh2.AssertOneStart(c) 283 284 // Complete the dependent task successfully. 285 mh2.InjectError(c, nil) 286 mh2.AssertNoStart(c) 287 288 // Bounce the dependency, and check the dependent is started again. 289 mh1.InjectError(c, errors.New("CLUNK")) 290 mh1.AssertOneStart(c) 291 mh2.AssertOneStart(c) 292 } 293 294 func (s *EngineSuite) TestRestartRestartsDependents(c *gc.C) { 295 296 // Start a dependency chain of 3 workers. 297 mh1 := newManifoldHarness() 298 err := s.engine.Install("error-task", mh1.Manifold()) 299 c.Assert(err, jc.ErrorIsNil) 300 mh1.AssertOneStart(c) 301 mh2 := newManifoldHarness("error-task") 302 err = s.engine.Install("restart-task", mh2.Manifold()) 303 c.Assert(err, jc.ErrorIsNil) 304 mh2.AssertOneStart(c) 305 mh3 := newManifoldHarness("restart-task") 306 err = s.engine.Install("consequent-restart-task", mh3.Manifold()) 307 c.Assert(err, jc.ErrorIsNil) 308 mh3.AssertOneStart(c) 309 310 // Once they're all running, induce an error at the top level, which will 311 // cause the next level to be killed cleanly.... 312 mh1.InjectError(c, errors.New("ZAP")) 313 314 // ...but should still cause all 3 workers to bounce. 315 mh1.AssertOneStart(c) 316 mh2.AssertOneStart(c) 317 mh3.AssertOneStart(c) 318 } 319 320 func (s *EngineSuite) TestIsFatal(c *gc.C) { 321 322 // Start an engine that pays attention to fatal errors. 323 fatalError := errors.New("KABOOM") 324 s.stopEngine(c) 325 s.startEngine(c, func(err error) bool { 326 return err == fatalError 327 }) 328 329 // Start two independent workers. 330 mh1 := newManifoldHarness() 331 err := s.engine.Install("some-task", mh1.Manifold()) 332 c.Assert(err, jc.ErrorIsNil) 333 mh1.AssertOneStart(c) 334 mh2 := newManifoldHarness() 335 err = s.engine.Install("other-task", mh2.Manifold()) 336 c.Assert(err, jc.ErrorIsNil) 337 mh2.AssertOneStart(c) 338 339 // Bounce one worker with Just Some Error; check that worker bounces. 340 mh1.InjectError(c, errors.New("splort")) 341 mh1.AssertOneStart(c) 342 mh2.AssertNoStart(c) 343 344 // Bounce another worker with the fatal error; check the engine exits with 345 // the right error. 346 mh2.InjectError(c, fatalError) 347 mh1.AssertNoStart(c) 348 mh2.AssertNoStart(c) 349 err = worker.Stop(s.engine) 350 c.Assert(err, gc.Equals, fatalError) 351 352 // Clear out s.engine -- lest TearDownTest freak out about the error. 353 s.engine = nil 354 } 355 356 func (s *EngineSuite) TestErrMissing(c *gc.C) { 357 358 // ErrMissing is implicitly and indirectly tested by the default 359 // manifoldHarness.start method throughout this suite, but this 360 // test explores its behaviour in pathological cases. 361 362 // Start a simple dependency. 363 mh1 := newManifoldHarness() 364 err := s.engine.Install("some-task", mh1.Manifold()) 365 c.Assert(err, jc.ErrorIsNil) 366 mh1.AssertOneStart(c) 367 368 // Start a dependent that always complains ErrMissing. 369 mh2 := newManifoldHarness("some-task") 370 manifold := mh2.Manifold() 371 manifold.Start = func(_ dependency.GetResourceFunc) (worker.Worker, error) { 372 mh2.starts <- struct{}{} 373 return nil, dependency.ErrMissing 374 } 375 err = s.engine.Install("unmet-task", manifold) 376 c.Assert(err, jc.ErrorIsNil) 377 mh2.AssertOneStart(c) 378 379 // Bounce the dependency; check the dependent bounces once or twice (it will 380 // react to both the stop and the start of the dependency, but may be lucky 381 // enough to only restart once). 382 mh1.InjectError(c, errors.New("kerrang")) 383 mh1.AssertOneStart(c) 384 startCount := 0 385 stable := false 386 for !stable { 387 select { 388 case <-mh2.starts: 389 startCount++ 390 case <-time.After(coretesting.ShortWait): 391 stable = true 392 } 393 } 394 c.Logf("saw %d starts", startCount) 395 c.Assert(startCount, jc.GreaterThan, 0) 396 c.Assert(startCount, jc.LessThan, 3) 397 398 // Stop the dependency for good; check the dependent is restarted just once. 399 mh1.InjectError(c, nil) 400 mh1.AssertNoStart(c) 401 mh2.AssertOneStart(c) 402 }