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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	jujutesting "github.com/juju/testing"
    11  	jc "github.com/juju/testing/checkers"
    12  	"github.com/prometheus/client_golang/prometheus"
    13  	gc "gopkg.in/check.v1"
    14  	"gopkg.in/juju/worker.v1"
    15  	"gopkg.in/juju/worker.v1/dependency"
    16  	dt "gopkg.in/juju/worker.v1/dependency/testing"
    17  	"gopkg.in/juju/worker.v1/workertest"
    18  
    19  	coreagent "github.com/juju/juju/agent"
    20  	"github.com/juju/juju/state"
    21  	statetesting "github.com/juju/juju/state/testing"
    22  	coretesting "github.com/juju/juju/testing"
    23  	workerstate "github.com/juju/juju/worker/state"
    24  )
    25  
    26  type ManifoldSuite struct {
    27  	statetesting.StateSuite
    28  	manifold          dependency.Manifold
    29  	openStateCalled   bool
    30  	openStateErr      error
    31  	config            workerstate.ManifoldConfig
    32  	agent             *mockAgent
    33  	resources         dt.StubResources
    34  	setStatePoolCalls []*state.StatePool
    35  }
    36  
    37  var _ = gc.Suite(&ManifoldSuite{})
    38  
    39  func (s *ManifoldSuite) SetUpTest(c *gc.C) {
    40  	s.StateSuite.SetUpTest(c)
    41  
    42  	s.openStateCalled = false
    43  	s.openStateErr = nil
    44  	s.setStatePoolCalls = nil
    45  
    46  	s.config = workerstate.ManifoldConfig{
    47  		AgentName:              "agent",
    48  		StateConfigWatcherName: "state-config-watcher",
    49  		OpenStatePool:          s.fakeOpenState,
    50  		PingInterval:           10 * time.Millisecond,
    51  		PrometheusRegisterer:   prometheus.NewRegistry(),
    52  		SetStatePool: func(pool *state.StatePool) {
    53  			s.setStatePoolCalls = append(s.setStatePoolCalls, pool)
    54  		},
    55  	}
    56  	s.manifold = workerstate.Manifold(s.config)
    57  	s.resources = dt.StubResources{
    58  		"agent":                dt.NewStubResource(new(mockAgent)),
    59  		"state-config-watcher": dt.NewStubResource(true),
    60  	}
    61  }
    62  
    63  func (s *ManifoldSuite) fakeOpenState(coreagent.Config) (*state.StatePool, error) {
    64  	s.openStateCalled = true
    65  	if s.openStateErr != nil {
    66  		return nil, s.openStateErr
    67  	}
    68  	// Here's one we prepared earlier...
    69  	return s.StatePool, nil
    70  }
    71  
    72  func (s *ManifoldSuite) TestInputs(c *gc.C) {
    73  	c.Assert(s.manifold.Inputs, jc.SameContents, []string{
    74  		"agent",
    75  		"state-config-watcher",
    76  	})
    77  }
    78  
    79  func (s *ManifoldSuite) TestStartAgentMissing(c *gc.C) {
    80  	s.resources["agent"] = dt.StubResource{Error: dependency.ErrMissing}
    81  	w, err := s.startManifold(c)
    82  	c.Check(w, gc.IsNil)
    83  	c.Check(err, gc.Equals, dependency.ErrMissing)
    84  }
    85  
    86  func (s *ManifoldSuite) TestStateConfigWatcherMissing(c *gc.C) {
    87  	s.resources["state-config-watcher"] = dt.StubResource{Error: dependency.ErrMissing}
    88  	w, err := s.startManifold(c)
    89  	c.Check(w, gc.IsNil)
    90  	c.Check(err, gc.Equals, dependency.ErrMissing)
    91  }
    92  
    93  func (s *ManifoldSuite) TestStartOpenStateNil(c *gc.C) {
    94  	s.config.OpenStatePool = nil
    95  	s.startManifoldInvalidConfig(c, s.config, "nil OpenStatePool not valid")
    96  }
    97  
    98  func (s *ManifoldSuite) TestStartPrometheusRegistererNil(c *gc.C) {
    99  	s.config.PrometheusRegisterer = nil
   100  	s.startManifoldInvalidConfig(c, s.config, "nil PrometheusRegisterer not valid")
   101  }
   102  
   103  func (s *ManifoldSuite) TestStartSetStatePoolNil(c *gc.C) {
   104  	s.config.SetStatePool = nil
   105  	s.startManifoldInvalidConfig(c, s.config, "nil SetStatePool not valid")
   106  }
   107  
   108  func (s *ManifoldSuite) startManifoldInvalidConfig(c *gc.C, config workerstate.ManifoldConfig, expect string) {
   109  	manifold := workerstate.Manifold(config)
   110  	w, err := manifold.Start(s.resources.Context())
   111  	c.Check(w, gc.IsNil)
   112  	c.Check(err, gc.ErrorMatches, expect)
   113  }
   114  
   115  func (s *ManifoldSuite) TestStartNotStateServer(c *gc.C) {
   116  	s.resources["state-config-watcher"] = dt.NewStubResource(false)
   117  	w, err := s.startManifold(c)
   118  	c.Check(w, gc.IsNil)
   119  	c.Check(errors.Cause(err), gc.Equals, dependency.ErrMissing)
   120  	c.Check(err, gc.ErrorMatches, "no StateServingInfo in config: dependency not available")
   121  }
   122  
   123  func (s *ManifoldSuite) TestStartOpenStateFails(c *gc.C) {
   124  	s.openStateErr = errors.New("boom")
   125  	w, err := s.startManifold(c)
   126  	c.Check(w, gc.IsNil)
   127  	c.Check(err, gc.ErrorMatches, "boom")
   128  }
   129  
   130  func (s *ManifoldSuite) TestStartSuccess(c *gc.C) {
   131  	w := s.mustStartManifold(c)
   132  	c.Check(s.openStateCalled, jc.IsTrue)
   133  	checkNotExiting(c, w)
   134  	workertest.CleanKill(c, w)
   135  }
   136  
   137  func (s *ManifoldSuite) TestStatePinging(c *gc.C) {
   138  	w := s.mustStartManifold(c)
   139  	checkNotExiting(c, w)
   140  
   141  	// Kill the mongod to cause pings to fail.
   142  	jujutesting.MgoServer.Destroy()
   143  
   144  	checkExitsWithError(c, w, "state ping failed: .+")
   145  }
   146  
   147  func (s *ManifoldSuite) TestOutputBadWorker(c *gc.C) {
   148  	var st *state.State
   149  	err := s.manifold.Output(dummyWorker{}, &st)
   150  	c.Check(st, gc.IsNil)
   151  	c.Check(err, gc.ErrorMatches, `in should be a \*state.stateWorker; .+`)
   152  }
   153  
   154  func (s *ManifoldSuite) TestOutputWrongType(c *gc.C) {
   155  	w := s.mustStartManifold(c)
   156  
   157  	var wrong int
   158  	err := s.manifold.Output(w, &wrong)
   159  	c.Check(wrong, gc.Equals, 0)
   160  	c.Check(err, gc.ErrorMatches, `out should be \*StateTracker; got .+`)
   161  }
   162  
   163  func (s *ManifoldSuite) TestOutputSuccess(c *gc.C) {
   164  	w := s.mustStartManifold(c)
   165  
   166  	var stTracker workerstate.StateTracker
   167  	err := s.manifold.Output(w, &stTracker)
   168  	c.Assert(err, jc.ErrorIsNil)
   169  
   170  	pool, err := stTracker.Use()
   171  	c.Assert(err, jc.ErrorIsNil)
   172  	c.Check(pool.SystemState(), gc.Equals, s.State)
   173  	c.Assert(stTracker.Done(), jc.ErrorIsNil)
   174  
   175  	// Ensure State is closed when the worker is done.
   176  	workertest.CleanKill(c, w)
   177  	assertStatePoolClosed(c, s.StatePool)
   178  }
   179  
   180  func (s *ManifoldSuite) TestStateStillInUse(c *gc.C) {
   181  	w := s.mustStartManifold(c)
   182  
   183  	var stTracker workerstate.StateTracker
   184  	err := s.manifold.Output(w, &stTracker)
   185  	c.Assert(err, jc.ErrorIsNil)
   186  
   187  	pool, err := stTracker.Use()
   188  	c.Assert(err, jc.ErrorIsNil)
   189  
   190  	// Close the worker while the State is still in use.
   191  	workertest.CleanKill(c, w)
   192  	assertStatePoolNotClosed(c, pool)
   193  
   194  	// Now signal that the State is no longer needed.
   195  	c.Assert(stTracker.Done(), jc.ErrorIsNil)
   196  	assertStatePoolClosed(c, pool)
   197  }
   198  
   199  func (s *ManifoldSuite) TestDeadStateRemoved(c *gc.C) {
   200  	// Create an additional state *before* we start
   201  	// the worker, so the worker's lifecycle watcher
   202  	// is guaranteed to observe it in both the Alive
   203  	// state and the Dead state.
   204  	newSt := s.Factory.MakeModel(c, nil)
   205  	defer newSt.Close()
   206  	model, err := newSt.Model()
   207  	c.Assert(err, jc.ErrorIsNil)
   208  
   209  	w := s.mustStartManifold(c)
   210  	defer workertest.CleanKill(c, w)
   211  
   212  	var stTracker workerstate.StateTracker
   213  	err = s.manifold.Output(w, &stTracker)
   214  	c.Assert(err, jc.ErrorIsNil)
   215  	pool, err := stTracker.Use()
   216  	c.Assert(err, jc.ErrorIsNil)
   217  	defer stTracker.Done()
   218  
   219  	// Get a reference to the state pool entry, so we can
   220  	// prevent it from being fully removed from the pool.
   221  	st, err := pool.Get(newSt.ModelUUID())
   222  	c.Assert(err, jc.ErrorIsNil)
   223  	defer st.Release()
   224  
   225  	// Progress the model to Dead.
   226  	c.Assert(model.Destroy(state.DestroyModelParams{}), jc.ErrorIsNil)
   227  	c.Assert(model.Refresh(), jc.ErrorIsNil)
   228  	c.Assert(model.Life(), gc.Equals, state.Dying)
   229  	c.Assert(newSt.RemoveDyingModel(), jc.ErrorIsNil)
   230  	c.Assert(model.Refresh(), jc.Satisfies, errors.IsNotFound)
   231  	s.State.StartSync()
   232  
   233  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   234  		st, err := pool.Get(newSt.ModelUUID())
   235  		if errors.IsNotFound(err) {
   236  			c.Assert(err, gc.ErrorMatches, "model .* has been removed")
   237  			return
   238  		}
   239  		c.Assert(err, jc.ErrorIsNil)
   240  		st.Release()
   241  	}
   242  	c.Fatal("timed out waiting for model state to be removed from pool")
   243  }
   244  
   245  func (s *ManifoldSuite) mustStartManifold(c *gc.C) worker.Worker {
   246  	w, err := s.startManifold(c)
   247  	c.Assert(err, jc.ErrorIsNil)
   248  	return w
   249  }
   250  
   251  func (s *ManifoldSuite) startManifold(c *gc.C) (worker.Worker, error) {
   252  	w, err := s.manifold.Start(s.resources.Context())
   253  	if w != nil {
   254  		s.AddCleanup(func(*gc.C) { worker.Stop(w) })
   255  	}
   256  	return w, err
   257  }
   258  
   259  func checkNotExiting(c *gc.C, w worker.Worker) {
   260  	exited := make(chan bool)
   261  	go func() {
   262  		w.Wait()
   263  		close(exited)
   264  	}()
   265  
   266  	select {
   267  	case <-exited:
   268  		c.Fatal("worker exited unexpectedly")
   269  	case <-time.After(coretesting.ShortWait):
   270  		// Worker didn't exit (good)
   271  	}
   272  }
   273  
   274  func checkExitsWithError(c *gc.C, w worker.Worker, expectedErr string) {
   275  	errCh := make(chan error)
   276  	go func() {
   277  		errCh <- w.Wait()
   278  	}()
   279  	select {
   280  	case err := <-errCh:
   281  		c.Check(err, gc.ErrorMatches, expectedErr)
   282  	case <-time.After(coretesting.LongWait):
   283  		c.Fatal("timed out waiting for worker to exit")
   284  	}
   285  }
   286  
   287  type mockAgent struct {
   288  	coreagent.Agent
   289  }
   290  
   291  func (ma *mockAgent) CurrentConfig() coreagent.Config {
   292  	return new(mockConfig)
   293  }
   294  
   295  type mockConfig struct {
   296  	coreagent.Config
   297  }
   298  
   299  type dummyWorker struct {
   300  	worker.Worker
   301  }