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

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package meterstatus_test
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/names/v5"
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    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  	"github.com/juju/juju/agent"
    20  	msapi "github.com/juju/juju/api/agent/meterstatus"
    21  	"github.com/juju/juju/api/base"
    22  	"github.com/juju/juju/core/machinelock"
    23  	"github.com/juju/juju/core/watcher"
    24  	coretesting "github.com/juju/juju/testing"
    25  	"github.com/juju/juju/worker/meterstatus"
    26  	"github.com/juju/juju/worker/uniter/runner"
    27  )
    28  
    29  type ManifoldSuite struct {
    30  	coretesting.BaseSuite
    31  
    32  	stub *testing.Stub
    33  
    34  	dataDir string
    35  
    36  	manifoldConfig meterstatus.ManifoldConfig
    37  	manifold       dependency.Manifold
    38  	resources      dt.StubResources
    39  }
    40  
    41  var _ = gc.Suite(&ManifoldSuite{})
    42  
    43  func (s *ManifoldSuite) SetUpTest(c *gc.C) {
    44  	s.BaseSuite.SetUpTest(c)
    45  	s.stub = &testing.Stub{}
    46  
    47  	s.manifoldConfig = meterstatus.ManifoldConfig{
    48  		AgentName:               "agent-name",
    49  		APICallerName:           "apicaller-name",
    50  		MachineLock:             &fakemachinelock{},
    51  		Clock:                   testclock.NewClock(time.Now()),
    52  		NewHookRunner:           meterstatus.NewHookRunner,
    53  		NewMeterStatusAPIClient: msapi.NewClient,
    54  
    55  		NewConnectedStatusWorker: meterstatus.NewConnectedStatusWorker,
    56  		NewIsolatedStatusWorker:  meterstatus.NewIsolatedStatusWorker,
    57  	}
    58  	s.manifold = meterstatus.Manifold(s.manifoldConfig)
    59  	s.dataDir = c.MkDir()
    60  
    61  	s.resources = dt.StubResources{
    62  		"agent-name":     dt.NewStubResource(&dummyAgent{dataDir: s.dataDir}),
    63  		"apicaller-name": dt.NewStubResource(&dummyAPICaller{}),
    64  	}
    65  }
    66  
    67  // TestInputs ensures the collect manifold has the expected defined inputs.
    68  func (s *ManifoldSuite) TestInputs(c *gc.C) {
    69  	c.Check(s.manifold.Inputs, jc.DeepEquals, []string{
    70  		"agent-name", "apicaller-name",
    71  	})
    72  }
    73  
    74  // TestStartMissingDeps ensures that the manifold correctly handles a missing
    75  // resource dependency.
    76  func (s *ManifoldSuite) TestStartMissingDeps(c *gc.C) {
    77  	for _, missingDep := range []string{
    78  		"agent-name",
    79  	} {
    80  		testResources := dt.StubResources{}
    81  		for k, v := range s.resources {
    82  			if k == missingDep {
    83  				testResources[k] = dt.StubResource{Error: dependency.ErrMissing}
    84  			} else {
    85  				testResources[k] = v
    86  			}
    87  		}
    88  		worker, err := s.manifold.Start(testResources.Context())
    89  		c.Check(worker, gc.IsNil)
    90  		c.Check(err, gc.Equals, dependency.ErrMissing)
    91  	}
    92  }
    93  
    94  type PatchedManifoldSuite struct {
    95  	coretesting.BaseSuite
    96  	msClient       *stubMeterStatusClient
    97  	manifoldConfig meterstatus.ManifoldConfig
    98  	stub           *testing.Stub
    99  	resources      dt.StubResources
   100  }
   101  
   102  func (s *PatchedManifoldSuite) SetUpTest(c *gc.C) {
   103  	s.BaseSuite.SetUpTest(c)
   104  
   105  	s.stub = &testing.Stub{}
   106  	s.msClient = &stubMeterStatusClient{stub: s.stub, changes: make(chan struct{})}
   107  	newMSClient := func(_ base.APICaller, _ names.UnitTag) msapi.MeterStatusClient {
   108  		return s.msClient
   109  	}
   110  	newHookRunner := func(_ meterstatus.HookRunnerConfig) meterstatus.HookRunner {
   111  		return &stubRunner{stub: s.stub}
   112  	}
   113  
   114  	s.manifoldConfig = meterstatus.ManifoldConfig{
   115  		AgentName:               "agent-name",
   116  		APICallerName:           "apicaller-name",
   117  		MachineLock:             &fakemachinelock{},
   118  		NewHookRunner:           newHookRunner,
   119  		NewMeterStatusAPIClient: newMSClient,
   120  	}
   121  }
   122  
   123  // TestStatusWorkerStarts ensures that the manifold correctly sets up the connected worker.
   124  func (s *PatchedManifoldSuite) TestStatusWorkerStarts(c *gc.C) {
   125  	var called bool
   126  	s.manifoldConfig.NewConnectedStatusWorker = func(cfg meterstatus.ConnectedConfig) (worker.Worker, error) {
   127  		called = true
   128  		return meterstatus.NewConnectedStatusWorker(cfg)
   129  	}
   130  	manifold := meterstatus.Manifold(s.manifoldConfig)
   131  	worker, err := manifold.Start(s.resources.Context())
   132  	c.Assert(called, jc.IsTrue)
   133  	c.Assert(err, jc.ErrorIsNil)
   134  	c.Assert(worker, gc.NotNil)
   135  	worker.Kill()
   136  	err = worker.Wait()
   137  	c.Assert(err, jc.ErrorIsNil)
   138  	s.stub.CheckCallNames(c, "MeterStatus", "RunHook", "WatchMeterStatus")
   139  }
   140  
   141  // TestInactiveWorker ensures that the manifold correctly sets up the isolated worker.
   142  func (s *PatchedManifoldSuite) TestIsolatedWorker(c *gc.C) {
   143  	delete(s.resources, "apicaller-name")
   144  	var called bool
   145  	s.manifoldConfig.NewIsolatedStatusWorker = func(cfg meterstatus.IsolatedConfig) (worker.Worker, error) {
   146  		called = true
   147  		return meterstatus.NewIsolatedStatusWorker(cfg)
   148  	}
   149  	manifold := meterstatus.Manifold(s.manifoldConfig)
   150  	worker, err := manifold.Start(s.resources.Context())
   151  	c.Assert(called, jc.IsTrue)
   152  	c.Assert(err, jc.ErrorIsNil)
   153  	c.Assert(worker, gc.NotNil)
   154  	worker.Kill()
   155  	err = worker.Wait()
   156  	c.Assert(err, jc.ErrorIsNil)
   157  	s.stub.CheckCallNames(c, "MeterStatus", "RunHook", "WatchMeterStatus")
   158  }
   159  
   160  type dummyAgent struct {
   161  	agent.Agent
   162  	dataDir string
   163  }
   164  
   165  func (a dummyAgent) CurrentConfig() agent.Config {
   166  	return &dummyAgentConfig{dataDir: a.dataDir}
   167  }
   168  
   169  type dummyAgentConfig struct {
   170  	agent.Config
   171  	dataDir string
   172  }
   173  
   174  // Tag implements agent.AgentConfig.
   175  func (ac dummyAgentConfig) Tag() names.Tag {
   176  	return names.NewUnitTag("u/0")
   177  }
   178  
   179  // DataDir implements agent.AgentConfig.
   180  func (ac dummyAgentConfig) DataDir() string {
   181  	return ac.dataDir
   182  }
   183  
   184  type dummyAPICaller struct {
   185  	base.APICaller
   186  }
   187  
   188  func (dummyAPICaller) BestFacadeVersion(facade string) int {
   189  	return 42
   190  }
   191  
   192  type stubMeterStatusClient struct {
   193  	sync.RWMutex
   194  	stub    *testing.Stub
   195  	changes chan struct{}
   196  	code    string
   197  }
   198  
   199  func newStubMeterStatusClient(stub *testing.Stub) *stubMeterStatusClient {
   200  	changes := make(chan struct{})
   201  	return &stubMeterStatusClient{stub: stub, changes: changes}
   202  }
   203  
   204  func (s *stubMeterStatusClient) SignalStatus(codes ...string) {
   205  	if len(codes) == 0 {
   206  		codes = []string{s.code}
   207  	}
   208  	for _, code := range codes {
   209  		s.SetStatus(code)
   210  		select {
   211  		case s.changes <- struct{}{}:
   212  		case <-time.After(coretesting.LongWait):
   213  			panic("timed out signaling meter status change")
   214  		}
   215  	}
   216  }
   217  
   218  func (s *stubMeterStatusClient) SetStatus(code string) {
   219  	s.Lock()
   220  	defer s.Unlock()
   221  	s.code = code
   222  }
   223  
   224  func (s *stubMeterStatusClient) MeterStatus() (string, string, error) {
   225  	s.RLock()
   226  	defer s.RUnlock()
   227  	s.stub.MethodCall(s, "MeterStatus")
   228  	if s.code == "" {
   229  		return "GREEN", "", nil
   230  	} else {
   231  		return s.code, "", nil
   232  	}
   233  
   234  }
   235  
   236  func (s *stubMeterStatusClient) WatchMeterStatus() (watcher.NotifyWatcher, error) {
   237  	s.stub.MethodCall(s, "WatchMeterStatus")
   238  	return s, nil
   239  }
   240  
   241  func (s *stubMeterStatusClient) Changes() watcher.NotifyChannel {
   242  	return s.changes
   243  }
   244  
   245  func (s *stubMeterStatusClient) Kill() {
   246  }
   247  
   248  func (s *stubMeterStatusClient) Wait() error {
   249  	return nil
   250  }
   251  
   252  type stubRunner struct {
   253  	runner.Runner
   254  	stub *testing.Stub
   255  	ran  chan struct{}
   256  }
   257  
   258  func (r *stubRunner) RunHook(code, info string, abort <-chan struct{}) {
   259  	r.stub.MethodCall(r, "RunHook", code, info)
   260  	if r.ran != nil {
   261  		select {
   262  		case r.ran <- struct{}{}:
   263  		case <-time.After(coretesting.LongWait):
   264  			panic("timed out signaling hook run")
   265  		}
   266  	}
   267  }
   268  
   269  type fakemachinelock struct {
   270  	mu sync.Mutex
   271  }
   272  
   273  func (f *fakemachinelock) Acquire(spec machinelock.Spec) (func(), error) {
   274  	f.mu.Lock()
   275  	return func() {
   276  		f.mu.Unlock()
   277  	}, nil
   278  }
   279  func (f *fakemachinelock) Report(opts ...machinelock.ReportOption) (string, error) {
   280  	return "", nil
   281  }