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  }