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