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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package manifold_test
     5  
     6  import (
     7  	"io"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/pubsub"
    14  	"github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	"github.com/prometheus/client_golang/prometheus"
    17  	gc "gopkg.in/check.v1"
    18  	"gopkg.in/juju/names.v2"
    19  	"gopkg.in/juju/worker.v1"
    20  	"gopkg.in/juju/worker.v1/dependency"
    21  	dt "gopkg.in/juju/worker.v1/dependency/testing"
    22  	"gopkg.in/juju/worker.v1/workertest"
    23  	"gopkg.in/mgo.v2/txn"
    24  
    25  	"github.com/juju/juju/agent"
    26  	"github.com/juju/juju/core/globalclock"
    27  	corelease "github.com/juju/juju/core/lease"
    28  	"github.com/juju/juju/core/raftlease"
    29  	"github.com/juju/juju/state"
    30  	coretesting "github.com/juju/juju/testing"
    31  	"github.com/juju/juju/worker/common"
    32  	"github.com/juju/juju/worker/lease"
    33  	leasemanager "github.com/juju/juju/worker/lease/manifold"
    34  )
    35  
    36  type manifoldSuite struct {
    37  	testing.IsolationSuite
    38  
    39  	context  dependency.Context
    40  	manifold dependency.Manifold
    41  
    42  	agent        *mockAgent
    43  	clock        *testclock.Clock
    44  	hub          *pubsub.StructuredHub
    45  	stateTracker *stubStateTracker
    46  
    47  	fsm     *raftlease.FSM
    48  	logger  loggo.Logger
    49  	metrics prometheus.Registerer
    50  
    51  	worker worker.Worker
    52  	store  *raftlease.Store
    53  
    54  	stub testing.Stub
    55  }
    56  
    57  var _ = gc.Suite(&manifoldSuite{})
    58  
    59  func (s *manifoldSuite) SetUpTest(c *gc.C) {
    60  	s.IsolationSuite.SetUpTest(c)
    61  
    62  	s.stub.ResetCalls()
    63  
    64  	s.agent = &mockAgent{conf: mockAgentConfig{
    65  		uuid: "controller-uuid",
    66  	}}
    67  	s.clock = testclock.NewClock(time.Now())
    68  	s.hub = pubsub.NewStructuredHub(nil)
    69  	s.stateTracker = &stubStateTracker{
    70  		done: make(chan struct{}),
    71  	}
    72  
    73  	s.fsm = raftlease.NewFSM()
    74  	s.logger = loggo.GetLogger("lease.manifold_test")
    75  	registerer := struct{ prometheus.Registerer }{}
    76  	s.metrics = &registerer
    77  
    78  	s.worker = &mockWorker{}
    79  	s.store = &raftlease.Store{}
    80  
    81  	s.context = s.newContext(nil)
    82  	s.manifold = leasemanager.Manifold(leasemanager.ManifoldConfig{
    83  		AgentName:            "agent",
    84  		ClockName:            "clock",
    85  		CentralHubName:       "hub",
    86  		StateName:            "state",
    87  		FSM:                  s.fsm,
    88  		RequestTopic:         "lease.manifold_test",
    89  		Logger:               &s.logger,
    90  		PrometheusRegisterer: s.metrics,
    91  		NewWorker:            s.newWorker,
    92  		NewStore:             s.newStore,
    93  	})
    94  }
    95  
    96  func (s *manifoldSuite) newContext(overlay map[string]interface{}) dependency.Context {
    97  	resources := map[string]interface{}{
    98  		"agent": s.agent,
    99  		"clock": s.clock,
   100  		"hub":   s.hub,
   101  		"state": s.stateTracker,
   102  	}
   103  	for k, v := range overlay {
   104  		resources[k] = v
   105  	}
   106  	return dt.StubContext(nil, resources)
   107  }
   108  
   109  func (s *manifoldSuite) newWorker(config lease.ManagerConfig) (worker.Worker, error) {
   110  	s.stub.MethodCall(s, "NewWorker", config)
   111  	if err := s.stub.NextErr(); err != nil {
   112  		return nil, err
   113  	}
   114  	return s.worker, nil
   115  }
   116  
   117  func (s *manifoldSuite) newStore(config raftlease.StoreConfig) *raftlease.Store {
   118  	s.stub.MethodCall(s, "NewStore", config)
   119  	return s.store
   120  }
   121  
   122  var expectedInputs = []string{
   123  	"agent", "clock", "hub", "state",
   124  }
   125  
   126  func (s *manifoldSuite) TestInputs(c *gc.C) {
   127  	c.Assert(s.manifold.Inputs, jc.SameContents, expectedInputs)
   128  }
   129  
   130  func (s *manifoldSuite) TestMissingInputs(c *gc.C) {
   131  	for _, input := range expectedInputs {
   132  		context := s.newContext(map[string]interface{}{
   133  			input: dependency.ErrMissing,
   134  		})
   135  		_, err := s.manifold.Start(context)
   136  		c.Assert(errors.Cause(err), gc.Equals, dependency.ErrMissing)
   137  	}
   138  }
   139  
   140  func (s *manifoldSuite) TestStart(c *gc.C) {
   141  	w, err := s.manifold.Start(s.context)
   142  	c.Assert(err, jc.ErrorIsNil)
   143  	underlying, ok := w.(*common.CleanupWorker)
   144  	c.Assert(ok, gc.Equals, true)
   145  	c.Assert(underlying.Worker, gc.Equals, s.worker)
   146  
   147  	s.stub.CheckCallNames(c, "NewStore", "NewWorker")
   148  
   149  	args := s.stub.Calls()[0].Args
   150  	c.Assert(args, gc.HasLen, 1)
   151  	c.Assert(args[0], gc.FitsTypeOf, raftlease.StoreConfig{})
   152  	storeConfig := args[0].(raftlease.StoreConfig)
   153  	c.Assert(storeConfig.ResponseTopic(1234), gc.Matches, "lease.manifold_test.[0-9a-f]{8}.1234")
   154  	storeConfig.ResponseTopic = nil
   155  	assertTrapdoorFuncsEqual(c, storeConfig.Trapdoor, s.stateTracker.pool.SystemState().LeaseTrapdoorFunc())
   156  	storeConfig.Trapdoor = nil
   157  	c.Assert(storeConfig, gc.DeepEquals, raftlease.StoreConfig{
   158  		FSM:            s.fsm,
   159  		Hub:            s.hub,
   160  		RequestTopic:   "lease.manifold_test",
   161  		Clock:          s.clock,
   162  		ForwardTimeout: 5 * time.Second,
   163  	})
   164  
   165  	args = s.stub.Calls()[1].Args
   166  	c.Assert(args, gc.HasLen, 1)
   167  	c.Assert(args[0], gc.FitsTypeOf, lease.ManagerConfig{})
   168  	config := args[0].(lease.ManagerConfig)
   169  
   170  	secretary, err := config.Secretary(corelease.SingularControllerNamespace)
   171  	c.Assert(err, jc.ErrorIsNil)
   172  	// Check that this secretary knows the controller uuid.
   173  	err = secretary.CheckLease(corelease.Key{"", "", "controller-uuid"})
   174  	c.Assert(err, jc.ErrorIsNil)
   175  	config.Secretary = nil
   176  
   177  	c.Assert(config, jc.DeepEquals, lease.ManagerConfig{
   178  		Store:                s.store,
   179  		Clock:                s.clock,
   180  		Logger:               &s.logger,
   181  		MaxSleep:             time.Minute,
   182  		EntityUUID:           "controller-uuid",
   183  		PrometheusRegisterer: s.metrics,
   184  	})
   185  }
   186  
   187  func (s *manifoldSuite) TestOutput(c *gc.C) {
   188  	s.worker = &lease.Manager{}
   189  	w, err := s.manifold.Start(s.context)
   190  	c.Assert(err, jc.ErrorIsNil)
   191  
   192  	var updater globalclock.Updater
   193  	err = s.manifold.Output(w, &updater)
   194  	c.Assert(err, jc.ErrorIsNil)
   195  	c.Assert(updater, gc.Equals, s.store)
   196  
   197  	var manager corelease.Manager
   198  	err = s.manifold.Output(w, &manager)
   199  	c.Assert(err, jc.ErrorIsNil)
   200  	c.Assert(manager, gc.Equals, s.worker)
   201  
   202  	var other io.Writer
   203  	err = s.manifold.Output(w, &other)
   204  	c.Assert(err, gc.ErrorMatches, `expected output of type \*globalclock.Updater or \*core/lease.Manager, got \*io.Writer`)
   205  }
   206  
   207  func (s *manifoldSuite) TestStoppingWorkerReleasesState(c *gc.C) {
   208  	w, err := s.manifold.Start(s.context)
   209  	c.Assert(err, jc.ErrorIsNil)
   210  
   211  	s.stateTracker.CheckCallNames(c, "Use")
   212  	select {
   213  	case <-s.stateTracker.done:
   214  		c.Fatal("unexpected state release")
   215  	case <-time.After(coretesting.ShortWait):
   216  	}
   217  
   218  	// Stopping the worker should cause the state to
   219  	// eventually be released.
   220  	workertest.CleanKill(c, w)
   221  
   222  	s.stateTracker.waitDone(c)
   223  	s.stateTracker.CheckCallNames(c, "Use", "Done")
   224  }
   225  
   226  func assertTrapdoorFuncsEqual(c *gc.C, actual, expected raftlease.TrapdoorFunc) {
   227  	if actual == nil {
   228  		c.Assert(expected, gc.Equals, nil)
   229  		return
   230  	}
   231  	var actualOps, expectedOps []txn.Op
   232  	err := actual(corelease.Key{"ns", "model", "lease"}, "holder")(0, &actualOps)
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	err = expected(corelease.Key{"ns", "model", "lease"}, "holder")(0, &expectedOps)
   235  	c.Assert(err, jc.ErrorIsNil)
   236  	c.Assert(actualOps, gc.DeepEquals, expectedOps)
   237  }
   238  
   239  type mockAgent struct {
   240  	agent.Agent
   241  	conf mockAgentConfig
   242  }
   243  
   244  func (ma *mockAgent) CurrentConfig() agent.Config {
   245  	return &ma.conf
   246  }
   247  
   248  type mockAgentConfig struct {
   249  	agent.Config
   250  	uuid string
   251  }
   252  
   253  func (c *mockAgentConfig) Controller() names.ControllerTag {
   254  	return names.NewControllerTag(c.uuid)
   255  }
   256  
   257  type mockWorker struct{}
   258  
   259  func (w *mockWorker) Kill() {}
   260  func (w *mockWorker) Wait() error {
   261  	return nil
   262  }
   263  
   264  type stubStateTracker struct {
   265  	testing.Stub
   266  	pool state.StatePool
   267  	done chan struct{}
   268  }
   269  
   270  func (s *stubStateTracker) Use() (*state.StatePool, error) {
   271  	s.MethodCall(s, "Use")
   272  	return &s.pool, s.NextErr()
   273  }
   274  
   275  func (s *stubStateTracker) Done() error {
   276  	s.MethodCall(s, "Done")
   277  	err := s.NextErr()
   278  	close(s.done)
   279  	return err
   280  }
   281  
   282  func (s *stubStateTracker) Report() map[string]interface{} {
   283  	return map[string]interface{}{"hey": "mum"}
   284  }
   285  
   286  func (s *stubStateTracker) waitDone(c *gc.C) {
   287  	select {
   288  	case <-s.done:
   289  	case <-time.After(coretesting.LongWait):
   290  		c.Fatal("timed out waiting for state to be released")
   291  	}
   292  }