github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/stateconfigwatcher/manifold_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package stateconfigwatcher_test
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/juju/names/v5"
    11  	"github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils/v3/voyeur"
    14  	"github.com/juju/worker/v3"
    15  	"github.com/juju/worker/v3/dependency"
    16  	dt "github.com/juju/worker/v3/dependency/testing"
    17  	gc "gopkg.in/check.v1"
    18  
    19  	coreagent "github.com/juju/juju/agent"
    20  	"github.com/juju/juju/controller"
    21  	coretesting "github.com/juju/juju/testing"
    22  	"github.com/juju/juju/worker/stateconfigwatcher"
    23  )
    24  
    25  type ManifoldSuite struct {
    26  	testing.IsolationSuite
    27  	agent              *mockAgent
    28  	goodContext        dependency.Context
    29  	agentConfigChanged *voyeur.Value
    30  	manifold           dependency.Manifold
    31  }
    32  
    33  var _ = gc.Suite(&ManifoldSuite{})
    34  
    35  func (s *ManifoldSuite) SetUpTest(c *gc.C) {
    36  	s.IsolationSuite.SetUpTest(c)
    37  
    38  	s.agent = new(mockAgent)
    39  	s.agent.conf.setStateServingInfo(true)
    40  	s.agent.conf.tag = names.NewMachineTag("99")
    41  
    42  	s.goodContext = dt.StubContext(nil, map[string]interface{}{
    43  		"agent": s.agent,
    44  	})
    45  
    46  	s.agentConfigChanged = voyeur.NewValue(0)
    47  	s.manifold = stateconfigwatcher.Manifold(stateconfigwatcher.ManifoldConfig{
    48  		AgentName:          "agent",
    49  		AgentConfigChanged: s.agentConfigChanged,
    50  	})
    51  }
    52  
    53  func (s *ManifoldSuite) TestInputs(c *gc.C) {
    54  	c.Assert(s.manifold.Inputs, jc.SameContents, []string{"agent"})
    55  }
    56  
    57  func (s *ManifoldSuite) TestNoAgent(c *gc.C) {
    58  	context := dt.StubContext(nil, map[string]interface{}{
    59  		"agent": dependency.ErrMissing,
    60  	})
    61  	_, err := s.manifold.Start(context)
    62  	c.Assert(err, gc.Equals, dependency.ErrMissing)
    63  }
    64  
    65  func (s *ManifoldSuite) TestNilAgentConfigChanged(c *gc.C) {
    66  	manifold := stateconfigwatcher.Manifold(stateconfigwatcher.ManifoldConfig{
    67  		AgentName: "agent",
    68  	})
    69  	_, err := manifold.Start(s.goodContext)
    70  	c.Assert(err, gc.ErrorMatches, "nil AgentConfigChanged .+")
    71  }
    72  
    73  func (s *ManifoldSuite) TestNotMachineAgent(c *gc.C) {
    74  	s.agent.conf.tag = names.NewUnitTag("foo/0")
    75  	_, err := s.manifold.Start(s.goodContext)
    76  	c.Assert(err, gc.ErrorMatches, "manifold can only be used with a machine or controller agent")
    77  }
    78  
    79  func (s *ManifoldSuite) TestStart(c *gc.C) {
    80  	w, err := s.manifold.Start(s.goodContext)
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	checkStop(c, w)
    83  }
    84  
    85  func (s *ManifoldSuite) TestOutputBadWorker(c *gc.C) {
    86  	var out bool
    87  	err := s.manifold.Output(dummyWorker{}, &out)
    88  	c.Check(err, gc.ErrorMatches, `in should be a \*stateconfigwatcher.stateConfigWatcher; .+`)
    89  }
    90  
    91  func (s *ManifoldSuite) TestOutputWrongType(c *gc.C) {
    92  	w, err := s.manifold.Start(s.goodContext)
    93  	c.Assert(err, jc.ErrorIsNil)
    94  	defer checkStop(c, w)
    95  
    96  	var out int
    97  	err = s.manifold.Output(w, &out)
    98  	c.Check(err, gc.ErrorMatches, `out should be \*bool; got .+`)
    99  }
   100  
   101  func (s *ManifoldSuite) TestOutputSuccessNotStateServer(c *gc.C) {
   102  	s.agent.conf.setStateServingInfo(false)
   103  	w, err := s.manifold.Start(s.goodContext)
   104  	c.Assert(err, jc.ErrorIsNil)
   105  	defer checkStop(c, w)
   106  
   107  	var out bool
   108  	err = s.manifold.Output(w, &out)
   109  	c.Check(err, jc.ErrorIsNil)
   110  	c.Check(out, jc.IsFalse)
   111  }
   112  
   113  func (s *ManifoldSuite) TestOutputSuccessStateServer(c *gc.C) {
   114  	s.agent.conf.setStateServingInfo(true)
   115  	w, err := s.manifold.Start(s.goodContext)
   116  	c.Assert(err, jc.ErrorIsNil)
   117  	defer checkStop(c, w)
   118  
   119  	var out bool
   120  	err = s.manifold.Output(w, &out)
   121  	c.Check(err, jc.ErrorIsNil)
   122  	c.Check(out, jc.IsTrue)
   123  }
   124  
   125  func (s *ManifoldSuite) TestBounceOnChange(c *gc.C) {
   126  	s.agent.conf.setStateServingInfo(false)
   127  	w, err := s.manifold.Start(s.goodContext)
   128  	c.Assert(err, jc.ErrorIsNil)
   129  	checkNotExiting(c, w)
   130  
   131  	checkOutput := func(expected bool) {
   132  		var out bool
   133  		err = s.manifold.Output(w, &out)
   134  		c.Assert(err, jc.ErrorIsNil)
   135  		c.Check(out, gc.Equals, expected)
   136  	}
   137  
   138  	// Not a state server yet, initial output should be False.
   139  	checkOutput(false)
   140  
   141  	// Changing the config without changing the state server status -
   142  	// worker should keep running and output should remain false.
   143  	s.agentConfigChanged.Set(0)
   144  	checkNotExiting(c, w)
   145  	checkOutput(false)
   146  
   147  	// Now change the config to include state serving info, worker
   148  	// should bounce.
   149  	s.agent.conf.setStateServingInfo(true)
   150  	s.agentConfigChanged.Set(0)
   151  	checkExitsWithError(c, w, dependency.ErrBounce)
   152  
   153  	// Restart the worker, the output should now be true.
   154  	w, err = s.manifold.Start(s.goodContext)
   155  	c.Assert(err, jc.ErrorIsNil)
   156  	checkNotExiting(c, w)
   157  	checkOutput(true)
   158  
   159  	// Changing the config again without changing the state serving
   160  	// info shouldn't cause the agent to exit.
   161  	s.agentConfigChanged.Set(0)
   162  	checkNotExiting(c, w)
   163  	checkOutput(true)
   164  
   165  	// Now remove the state serving info, the agent should bounce.
   166  	s.agent.conf.setStateServingInfo(false)
   167  	s.agentConfigChanged.Set(0)
   168  	checkExitsWithError(c, w, dependency.ErrBounce)
   169  }
   170  
   171  func (s *ManifoldSuite) TestClosedVoyeur(c *gc.C) {
   172  	w, err := s.manifold.Start(s.goodContext)
   173  	c.Assert(err, jc.ErrorIsNil)
   174  	checkNotExiting(c, w)
   175  
   176  	s.agentConfigChanged.Close()
   177  
   178  	c.Check(waitForExit(c, w), gc.ErrorMatches, "config changed value closed")
   179  }
   180  
   181  func checkStop(c *gc.C, w worker.Worker) {
   182  	err := worker.Stop(w)
   183  	c.Check(err, jc.ErrorIsNil)
   184  }
   185  
   186  func checkNotExiting(c *gc.C, w worker.Worker) {
   187  	exited := make(chan bool)
   188  	go func() {
   189  		w.Wait()
   190  		close(exited)
   191  	}()
   192  
   193  	select {
   194  	case <-exited:
   195  		c.Fatal("worker exited unexpectedly")
   196  	case <-time.After(coretesting.ShortWait):
   197  		// Worker didn't exit (good)
   198  	}
   199  }
   200  
   201  func checkExitsWithError(c *gc.C, w worker.Worker, expectedErr error) {
   202  	c.Check(waitForExit(c, w), gc.Equals, expectedErr)
   203  }
   204  
   205  func waitForExit(c *gc.C, w worker.Worker) error {
   206  	errCh := make(chan error)
   207  	go func() {
   208  		errCh <- w.Wait()
   209  	}()
   210  	select {
   211  	case err := <-errCh:
   212  		return err
   213  	case <-time.After(coretesting.LongWait):
   214  		c.Fatal("timed out waiting for worker to exit")
   215  	}
   216  	panic("can't get here")
   217  }
   218  
   219  type mockAgent struct {
   220  	coreagent.Agent
   221  	conf mockConfig
   222  }
   223  
   224  func (ma *mockAgent) CurrentConfig() coreagent.Config {
   225  	return &ma.conf
   226  }
   227  
   228  type mockConfig struct {
   229  	coreagent.ConfigSetter
   230  	tag         names.Tag
   231  	mu          sync.Mutex
   232  	ssInfoIsSet bool
   233  }
   234  
   235  func (mc *mockConfig) Tag() names.Tag {
   236  	return mc.tag
   237  }
   238  
   239  func (mc *mockConfig) setStateServingInfo(isSet bool) {
   240  	mc.mu.Lock()
   241  	defer mc.mu.Unlock()
   242  	mc.ssInfoIsSet = isSet
   243  }
   244  
   245  func (mc *mockConfig) StateServingInfo() (controller.StateServingInfo, bool) {
   246  	mc.mu.Lock()
   247  	defer mc.mu.Unlock()
   248  	return controller.StateServingInfo{}, mc.ssInfoIsSet
   249  }
   250  
   251  type dummyWorker struct {
   252  	worker.Worker
   253  }