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  }