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  }