github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/globalclockupdater/manifold_test.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package globalclockupdater_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/clock"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/juju/worker.v1"
    16  	"gopkg.in/juju/worker.v1/dependency"
    17  	dt "gopkg.in/juju/worker.v1/dependency/testing"
    18  	"gopkg.in/juju/worker.v1/workertest"
    19  
    20  	"github.com/juju/juju/core/globalclock"
    21  	"github.com/juju/juju/state"
    22  	coretesting "github.com/juju/juju/testing"
    23  	"github.com/juju/juju/worker/globalclockupdater"
    24  )
    25  
    26  type ManifoldSuite struct {
    27  	testing.IsolationSuite
    28  	stub         testing.Stub
    29  	config       globalclockupdater.ManifoldConfig
    30  	stateTracker stubStateTracker
    31  	worker       worker.Worker
    32  	logger       loggo.Logger
    33  }
    34  
    35  var _ = gc.Suite(&ManifoldSuite{})
    36  
    37  func (s *ManifoldSuite) SetUpTest(c *gc.C) {
    38  	s.IsolationSuite.SetUpTest(c)
    39  	s.stub.ResetCalls()
    40  	s.logger = loggo.GetLogger("globalclockupdater_test")
    41  	s.config = globalclockupdater.ManifoldConfig{
    42  		ClockName:      "clock",
    43  		StateName:      "state",
    44  		NewWorker:      s.newWorker,
    45  		UpdateInterval: time.Second,
    46  		BackoffDelay:   time.Second,
    47  		Logger:         s.logger,
    48  	}
    49  	s.stateTracker = stubStateTracker{
    50  		done: make(chan struct{}),
    51  	}
    52  	s.worker = worker.NewRunner(worker.RunnerParams{})
    53  	s.AddCleanup(func(c *gc.C) { workertest.CleanKill(c, s.worker) })
    54  }
    55  
    56  func (s *ManifoldSuite) newWorker(config globalclockupdater.Config) (worker.Worker, error) {
    57  	s.stub.AddCall("NewWorker", config)
    58  	if err := s.stub.NextErr(); err != nil {
    59  		return nil, err
    60  	}
    61  	return s.worker, nil
    62  }
    63  
    64  func (s *ManifoldSuite) TestInputs(c *gc.C) {
    65  	manifold := globalclockupdater.Manifold(s.config)
    66  	expectInputs := []string{"clock", "state"}
    67  	c.Check(manifold.Inputs, jc.SameContents, expectInputs)
    68  }
    69  
    70  func (s *ManifoldSuite) TestLeaseManagerInputs(c *gc.C) {
    71  	s.config.StateName = ""
    72  	s.config.LeaseManagerName = "lease-manager"
    73  	manifold := globalclockupdater.Manifold(s.config)
    74  	expectInputs := []string{"clock", "lease-manager"}
    75  	c.Check(manifold.Inputs, jc.SameContents, expectInputs)
    76  }
    77  
    78  func (s *ManifoldSuite) TestLeaseManagerAndRaftInputs(c *gc.C) {
    79  	s.config.StateName = ""
    80  	s.config.LeaseManagerName = "lease-manager"
    81  	s.config.RaftName = "raft"
    82  	manifold := globalclockupdater.Manifold(s.config)
    83  	expectInputs := []string{"clock", "lease-manager", "raft"}
    84  	c.Check(manifold.Inputs, jc.SameContents, expectInputs)
    85  }
    86  
    87  func (s *ManifoldSuite) TestStartValidateClockName(c *gc.C) {
    88  	s.config.ClockName = ""
    89  	s.testStartValidateConfig(c, "empty ClockName not valid")
    90  }
    91  
    92  func (s *ManifoldSuite) TestStartValidateStateName(c *gc.C) {
    93  	s.config.StateName = ""
    94  	s.testStartValidateConfig(c, "both StateName and LeaseManagerName empty not valid")
    95  }
    96  
    97  func (s *ManifoldSuite) TestStartValidateNotBoth(c *gc.C) {
    98  	s.config.LeaseManagerName = "lease-manager"
    99  	s.testStartValidateConfig(c, "only one of StateName and LeaseManagerName can be set")
   100  }
   101  
   102  func (s *ManifoldSuite) TestStartValidateNotRaftAndState(c *gc.C) {
   103  	s.config.RaftName = "raft"
   104  	s.testStartValidateConfig(c, "RaftName only valid with LeaseManagerName")
   105  }
   106  
   107  func (s *ManifoldSuite) TestStartValidateUpdateInterval(c *gc.C) {
   108  	s.config.UpdateInterval = 0
   109  	s.testStartValidateConfig(c, "non-positive UpdateInterval not valid")
   110  }
   111  
   112  func (s *ManifoldSuite) TestStartValidateBackoffDelay(c *gc.C) {
   113  	s.config.BackoffDelay = -1
   114  	s.testStartValidateConfig(c, "non-positive BackoffDelay not valid")
   115  }
   116  
   117  func (s *ManifoldSuite) testStartValidateConfig(c *gc.C, expect string) {
   118  	manifold := globalclockupdater.Manifold(s.config)
   119  	context := dt.StubContext(nil, map[string]interface{}{
   120  		"clock": nil,
   121  		"state": nil,
   122  	})
   123  	worker, err := manifold.Start(context)
   124  	c.Check(err, gc.ErrorMatches, expect)
   125  	c.Check(worker, gc.IsNil)
   126  }
   127  
   128  func (s *ManifoldSuite) TestStartMissingClock(c *gc.C) {
   129  	manifold := globalclockupdater.Manifold(s.config)
   130  	context := dt.StubContext(nil, map[string]interface{}{
   131  		"clock": dependency.ErrMissing,
   132  	})
   133  
   134  	worker, err := manifold.Start(context)
   135  	c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing)
   136  	c.Check(worker, gc.IsNil)
   137  }
   138  
   139  func (s *ManifoldSuite) TestStartMissingLeaseManager(c *gc.C) {
   140  	s.config.StateName = ""
   141  	s.config.LeaseManagerName = "lease-manager"
   142  	manifold := globalclockupdater.Manifold(s.config)
   143  	context := dt.StubContext(nil, map[string]interface{}{
   144  		"clock":         fakeClock{},
   145  		"lease-manager": dependency.ErrMissing,
   146  	})
   147  
   148  	worker, err := manifold.Start(context)
   149  	c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing)
   150  	c.Check(worker, gc.IsNil)
   151  }
   152  
   153  func (s *ManifoldSuite) TestStartMissingRaft(c *gc.C) {
   154  	updater := fakeUpdater{}
   155  	s.config.StateName = ""
   156  	s.config.LeaseManagerName = "lease-manager"
   157  	s.config.RaftName = "raft"
   158  	manifold := globalclockupdater.Manifold(s.config)
   159  	context := dt.StubContext(nil, map[string]interface{}{
   160  		"clock":         fakeClock{},
   161  		"lease-manager": &updater,
   162  		"raft":          dependency.ErrMissing,
   163  	})
   164  
   165  	worker, err := manifold.Start(context)
   166  	c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing)
   167  	c.Check(worker, gc.IsNil)
   168  }
   169  
   170  func (s *ManifoldSuite) TestStartMissingState(c *gc.C) {
   171  	manifold := globalclockupdater.Manifold(s.config)
   172  	context := dt.StubContext(nil, map[string]interface{}{
   173  		"clock": fakeClock{},
   174  		"state": dependency.ErrMissing,
   175  	})
   176  
   177  	worker, err := manifold.Start(context)
   178  	c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing)
   179  	c.Check(worker, gc.IsNil)
   180  }
   181  
   182  func (s *ManifoldSuite) TestStartStateTrackerError(c *gc.C) {
   183  	s.stateTracker.SetErrors(errors.New("phail"))
   184  	worker, err := s.startManifold(c)
   185  	c.Check(err, gc.ErrorMatches, "phail")
   186  	c.Check(worker, gc.IsNil)
   187  }
   188  
   189  func (s *ManifoldSuite) TestStartNewWorkerError(c *gc.C) {
   190  	s.stub.SetErrors(errors.New("phail"))
   191  	worker, err := s.startManifold(c)
   192  	c.Check(err, gc.ErrorMatches, "phail")
   193  	c.Check(worker, gc.IsNil)
   194  	s.stateTracker.CheckCallNames(c, "Use", "Done")
   195  }
   196  
   197  func (s *ManifoldSuite) TestStartNewWorkerSuccess(c *gc.C) {
   198  	worker, err := s.startManifold(c)
   199  	c.Check(err, jc.ErrorIsNil)
   200  	c.Check(worker, gc.Equals, s.worker)
   201  
   202  	s.stub.CheckCallNames(c, "NewWorker")
   203  	config := s.stub.Calls()[0].Args[0].(globalclockupdater.Config)
   204  	c.Assert(config.NewUpdater, gc.NotNil)
   205  	config.NewUpdater = nil
   206  	c.Assert(config, jc.DeepEquals, globalclockupdater.Config{
   207  		LocalClock:     fakeClock{},
   208  		UpdateInterval: s.config.UpdateInterval,
   209  		BackoffDelay:   s.config.BackoffDelay,
   210  		Logger:         s.logger,
   211  	})
   212  }
   213  
   214  func (s *ManifoldSuite) TestStartNewWorkerSuccessWithLeaseManager(c *gc.C) {
   215  	updater := fakeUpdater{}
   216  	s.config.StateName = ""
   217  	s.config.LeaseManagerName = "lease-manager"
   218  	s.config.RaftName = "raft"
   219  	worker, err := s.startManifoldWithContext(c, map[string]interface{}{
   220  		"clock":         fakeClock{},
   221  		"lease-manager": &updater,
   222  		"raft":          nil,
   223  	})
   224  	c.Check(err, jc.ErrorIsNil)
   225  	c.Check(worker, gc.Equals, s.worker)
   226  
   227  	s.stub.CheckCallNames(c, "NewWorker")
   228  	config := s.stub.Calls()[0].Args[0].(globalclockupdater.Config)
   229  	c.Assert(config.NewUpdater, gc.NotNil)
   230  	actualUpdater, err := config.NewUpdater()
   231  	c.Assert(err, jc.ErrorIsNil)
   232  	c.Assert(actualUpdater, gc.Equals, &updater)
   233  	config.NewUpdater = nil
   234  	c.Assert(config, jc.DeepEquals, globalclockupdater.Config{
   235  		LocalClock:     fakeClock{},
   236  		UpdateInterval: s.config.UpdateInterval,
   237  		BackoffDelay:   s.config.BackoffDelay,
   238  		Logger:         s.logger,
   239  	})
   240  }
   241  
   242  func (s *ManifoldSuite) TestStoppingWorkerReleasesState(c *gc.C) {
   243  	worker, err := s.startManifold(c)
   244  	c.Check(err, jc.ErrorIsNil)
   245  	c.Check(worker, gc.Equals, s.worker)
   246  
   247  	s.stateTracker.CheckCallNames(c, "Use")
   248  	select {
   249  	case <-s.stateTracker.done:
   250  		c.Fatal("unexpected state release")
   251  	case <-time.After(coretesting.ShortWait):
   252  	}
   253  
   254  	// Stopping the worker should cause the state to
   255  	// eventually be released.
   256  	workertest.CleanKill(c, worker)
   257  
   258  	s.stateTracker.waitDone(c)
   259  	s.stateTracker.CheckCallNames(c, "Use", "Done")
   260  }
   261  
   262  func (s *ManifoldSuite) startManifold(c *gc.C) (worker.Worker, error) {
   263  	worker, err := s.startManifoldWithContext(c, map[string]interface{}{
   264  		"clock": fakeClock{},
   265  		"state": &s.stateTracker,
   266  	})
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	// Add a cleanup to wait for the worker to be done; this
   271  	// is necessary to avoid races.
   272  	s.AddCleanup(func(c *gc.C) {
   273  		workertest.DirtyKill(c, worker)
   274  		s.stateTracker.waitDone(c)
   275  	})
   276  	return worker, err
   277  }
   278  
   279  func (s *ManifoldSuite) startManifoldWithContext(c *gc.C, data map[string]interface{}) (worker.Worker, error) {
   280  	manifold := globalclockupdater.Manifold(s.config)
   281  	context := dt.StubContext(nil, data)
   282  	worker, err := manifold.Start(context)
   283  	if err != nil {
   284  		return nil, err
   285  	}
   286  	return worker, nil
   287  }
   288  
   289  type fakeClock struct {
   290  	clock.Clock
   291  }
   292  
   293  type fakeUpdater struct {
   294  	globalclock.Updater
   295  }
   296  
   297  type stubStateTracker struct {
   298  	testing.Stub
   299  	pool state.StatePool
   300  	done chan struct{}
   301  }
   302  
   303  func (s *stubStateTracker) Use() (*state.StatePool, error) {
   304  	s.MethodCall(s, "Use")
   305  	return &s.pool, s.NextErr()
   306  }
   307  
   308  func (s *stubStateTracker) Done() error {
   309  	s.MethodCall(s, "Done")
   310  	err := s.NextErr()
   311  	// close must be the last read or write on stubStateTracker in Done
   312  	close(s.done)
   313  	return err
   314  }
   315  
   316  func (s *stubStateTracker) waitDone(c *gc.C) {
   317  	select {
   318  	case <-s.done:
   319  	case <-time.After(coretesting.LongWait):
   320  		c.Fatal("timed out waiting for state to be released")
   321  	}
   322  }
   323  
   324  func (s *stubStateTracker) Report() map[string]interface{} {
   325  	s.MethodCall(s, "Report")
   326  	return nil
   327  }