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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiserver_test
     5  
     6  import (
     7  	"net/http"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names/v5"
    13  	"github.com/juju/pubsub/v2"
    14  	"github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	"github.com/juju/worker/v3"
    17  	"github.com/juju/worker/v3/dependency"
    18  	dt "github.com/juju/worker/v3/dependency/testing"
    19  	"github.com/juju/worker/v3/workertest"
    20  	"github.com/prometheus/client_golang/prometheus"
    21  	gc "gopkg.in/check.v1"
    22  
    23  	"github.com/juju/juju/agent"
    24  	coreapiserver "github.com/juju/juju/apiserver"
    25  	"github.com/juju/juju/apiserver/apiserverhttp"
    26  	"github.com/juju/juju/apiserver/authentication/macaroon"
    27  	"github.com/juju/juju/controller"
    28  	"github.com/juju/juju/core/auditlog"
    29  	"github.com/juju/juju/core/cache"
    30  	coredatabase "github.com/juju/juju/core/database"
    31  	corelogger "github.com/juju/juju/core/logger"
    32  	"github.com/juju/juju/core/multiwatcher"
    33  	"github.com/juju/juju/core/presence"
    34  	"github.com/juju/juju/state"
    35  	coretesting "github.com/juju/juju/testing"
    36  	"github.com/juju/juju/worker/apiserver"
    37  	"github.com/juju/juju/worker/gate"
    38  	"github.com/juju/juju/worker/lease"
    39  	"github.com/juju/juju/worker/syslogger"
    40  )
    41  
    42  type ManifoldSuite struct {
    43  	testing.IsolationSuite
    44  
    45  	manifold dependency.Manifold
    46  
    47  	agent                *mockAgent
    48  	auditConfig          stubAuditConfig
    49  	authenticator        *mockAuthenticator
    50  	clock                *testclock.Clock
    51  	context              dependency.Context
    52  	controller           *cache.Controller
    53  	hub                  pubsub.StructuredHub
    54  	leaseManager         *lease.Manager
    55  	metricsCollector     *coreapiserver.Collector
    56  	multiwatcherFactory  multiwatcher.Factory
    57  	mux                  *apiserverhttp.Mux
    58  	prometheusRegisterer stubPrometheusRegisterer
    59  	state                stubStateTracker
    60  	upgradeGate          stubGateWaiter
    61  	sysLogger            syslogger.SysLogger
    62  	charmhubHTTPClient   *http.Client
    63  	dbGetter             stubDBGetter
    64  
    65  	stub testing.Stub
    66  }
    67  
    68  var _ = gc.Suite(&ManifoldSuite{})
    69  
    70  func (s *ManifoldSuite) SetUpTest(c *gc.C) {
    71  	s.IsolationSuite.SetUpTest(c)
    72  
    73  	s.agent = &mockAgent{}
    74  	s.authenticator = &mockAuthenticator{}
    75  	s.clock = testclock.NewClock(time.Time{})
    76  	controller, err := cache.NewController(cache.ControllerConfig{
    77  		Changes: make(chan interface{}),
    78  	})
    79  	c.Assert(err, jc.ErrorIsNil)
    80  	s.controller = controller
    81  	s.mux = apiserverhttp.NewMux()
    82  	s.state = stubStateTracker{}
    83  	s.metricsCollector = coreapiserver.NewMetricsCollector()
    84  	s.upgradeGate = stubGateWaiter{}
    85  	s.auditConfig = stubAuditConfig{}
    86  	s.multiwatcherFactory = &fakeMultiwatcherFactory{}
    87  	s.leaseManager = &lease.Manager{}
    88  	s.sysLogger = &mockSysLogger{}
    89  	s.charmhubHTTPClient = &http.Client{}
    90  	s.stub.ResetCalls()
    91  
    92  	s.context = s.newContext(nil)
    93  	s.manifold = apiserver.Manifold(apiserver.ManifoldConfig{
    94  		AgentName:                         "agent",
    95  		AuthenticatorName:                 "authenticator",
    96  		ClockName:                         "clock",
    97  		MuxName:                           "mux",
    98  		ModelCacheName:                    "modelcache",
    99  		MultiwatcherName:                  "multiwatcher",
   100  		StateName:                         "state",
   101  		UpgradeGateName:                   "upgrade",
   102  		AuditConfigUpdaterName:            "auditconfig-updater",
   103  		LeaseManagerName:                  "lease-manager",
   104  		SyslogName:                        "syslog",
   105  		CharmhubHTTPClientName:            "charmhub-http-client",
   106  		DBAccessorName:                    "db-accessor",
   107  		PrometheusRegisterer:              &s.prometheusRegisterer,
   108  		RegisterIntrospectionHTTPHandlers: func(func(string, http.Handler)) {},
   109  		Hub:                               &s.hub,
   110  		Presence:                          presence.New(s.clock),
   111  		NewWorker:                         s.newWorker,
   112  		NewMetricsCollector:               s.newMetricsCollector,
   113  	})
   114  }
   115  
   116  func (s *ManifoldSuite) newContext(overlay map[string]interface{}) dependency.Context {
   117  	resources := map[string]interface{}{
   118  		"agent":                s.agent,
   119  		"authenticator":        s.authenticator,
   120  		"clock":                s.clock,
   121  		"mux":                  s.mux,
   122  		"modelcache":           s.controller,
   123  		"multiwatcher":         s.multiwatcherFactory,
   124  		"state":                &s.state,
   125  		"upgrade":              &s.upgradeGate,
   126  		"auditconfig-updater":  s.auditConfig.get,
   127  		"lease-manager":        s.leaseManager,
   128  		"syslog":               s.sysLogger,
   129  		"charmhub-http-client": s.charmhubHTTPClient,
   130  		"db-accessor":          s.dbGetter,
   131  	}
   132  	for k, v := range overlay {
   133  		resources[k] = v
   134  	}
   135  	return dt.StubContext(nil, resources)
   136  }
   137  
   138  type mockSysLogger struct {
   139  	syslogger.SysLogger
   140  }
   141  
   142  func (*mockSysLogger) Log([]corelogger.LogRecord) error {
   143  	return nil
   144  }
   145  
   146  func (s *ManifoldSuite) newWorker(config apiserver.Config) (worker.Worker, error) {
   147  	s.stub.MethodCall(s, "NewWorker", config)
   148  	if err := s.stub.NextErr(); err != nil {
   149  		return nil, err
   150  	}
   151  	return worker.NewRunner(worker.RunnerParams{}), nil
   152  }
   153  
   154  func (s *ManifoldSuite) newMetricsCollector() *coreapiserver.Collector {
   155  	return s.metricsCollector
   156  }
   157  
   158  var expectedInputs = []string{
   159  	"agent", "authenticator", "clock", "modelcache", "multiwatcher", "mux",
   160  	"state", "upgrade", "auditconfig-updater", "lease-manager",
   161  	"syslog", "charmhub-http-client", "db-accessor",
   162  }
   163  
   164  func (s *ManifoldSuite) TestInputs(c *gc.C) {
   165  	c.Assert(s.manifold.Inputs, jc.SameContents, expectedInputs)
   166  }
   167  
   168  func (s *ManifoldSuite) TestMissingInputs(c *gc.C) {
   169  	for _, input := range expectedInputs {
   170  		context := s.newContext(map[string]interface{}{
   171  			input: dependency.ErrMissing,
   172  		})
   173  		_, err := s.manifold.Start(context)
   174  		c.Assert(errors.Cause(err), gc.Equals, dependency.ErrMissing)
   175  
   176  		// The state tracker must have either no calls, or a Use and a Done.
   177  		if len(s.state.Calls()) > 0 {
   178  			s.state.CheckCallNames(c, "Use", "Done")
   179  		}
   180  		s.state.ResetCalls()
   181  	}
   182  }
   183  
   184  func (s *ManifoldSuite) TestStart(c *gc.C) {
   185  	w := s.startWorkerClean(c)
   186  	workertest.CleanKill(c, w)
   187  
   188  	s.stub.CheckCallNames(c, "NewWorker")
   189  	args := s.stub.Calls()[0].Args
   190  	c.Assert(args, gc.HasLen, 1)
   191  	c.Assert(args[0], gc.FitsTypeOf, apiserver.Config{})
   192  	config := args[0].(apiserver.Config)
   193  
   194  	c.Assert(config.GetAuditConfig, gc.NotNil)
   195  	c.Assert(config.GetAuditConfig(), gc.DeepEquals, s.auditConfig.config)
   196  	config.GetAuditConfig = nil
   197  
   198  	c.Assert(config.UpgradeComplete, gc.NotNil)
   199  	config.UpgradeComplete()
   200  	config.UpgradeComplete = nil
   201  	s.upgradeGate.CheckCallNames(c, "IsUnlocked")
   202  
   203  	c.Assert(config.RegisterIntrospectionHTTPHandlers, gc.NotNil)
   204  	config.RegisterIntrospectionHTTPHandlers = nil
   205  
   206  	c.Assert(config.Presence, gc.NotNil)
   207  	config.Presence = nil
   208  
   209  	// NewServer is hard-coded by the manifold to an internal shim.
   210  	c.Assert(config.NewServer, gc.NotNil)
   211  	config.NewServer = nil
   212  
   213  	// EmbeddedCommand is hard-coded by the manifold to an internal shim.
   214  	c.Assert(config.EmbeddedCommand, gc.NotNil)
   215  	config.EmbeddedCommand = nil
   216  
   217  	c.Assert(config, jc.DeepEquals, apiserver.Config{
   218  		AgentConfig:                &s.agent.conf,
   219  		LocalMacaroonAuthenticator: s.authenticator,
   220  		Clock:                      s.clock,
   221  		Controller:                 s.controller,
   222  		Mux:                        s.mux,
   223  		MultiwatcherFactory:        s.multiwatcherFactory,
   224  		StatePool:                  &s.state.pool,
   225  		LeaseManager:               s.leaseManager,
   226  		MetricsCollector:           s.metricsCollector,
   227  		Hub:                        &s.hub,
   228  		SysLogger:                  s.sysLogger,
   229  		CharmhubHTTPClient:         s.charmhubHTTPClient,
   230  		DBGetter:                   s.dbGetter,
   231  	})
   232  }
   233  
   234  func (s *ManifoldSuite) TestStopWorkerClosesState(c *gc.C) {
   235  	w := s.startWorkerClean(c)
   236  	defer workertest.CleanKill(c, w)
   237  
   238  	s.state.CheckCallNames(c, "Use")
   239  
   240  	workertest.CleanKill(c, w)
   241  	s.state.CheckCallNames(c, "Use", "Done")
   242  }
   243  
   244  func (s *ManifoldSuite) startWorkerClean(c *gc.C) worker.Worker {
   245  	w, err := s.manifold.Start(s.context)
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	workertest.CheckAlive(c, w)
   248  	return w
   249  }
   250  
   251  func (s *ManifoldSuite) TestAddsAndRemovesMuxClients(c *gc.C) {
   252  	waitFinished := make(chan struct{})
   253  	w := s.startWorkerClean(c)
   254  	go func() {
   255  		defer close(waitFinished)
   256  		s.mux.Wait()
   257  	}()
   258  
   259  	select {
   260  	case <-waitFinished:
   261  		c.Fatalf("didn't add clients to the mux")
   262  	case <-time.After(coretesting.ShortWait):
   263  	}
   264  
   265  	workertest.CleanKill(c, w)
   266  
   267  	select {
   268  	case <-waitFinished:
   269  	case <-time.After(coretesting.LongWait):
   270  		c.Fatalf("didn't tell the mux we were finished")
   271  	}
   272  }
   273  
   274  type mockAgent struct {
   275  	agent.Agent
   276  	conf mockAgentConfig
   277  }
   278  
   279  func (ma *mockAgent) CurrentConfig() agent.Config {
   280  	return &ma.conf
   281  }
   282  
   283  type mockAgentConfig struct {
   284  	agent.Config
   285  	dataDir string
   286  	logDir  string
   287  	info    *controller.StateServingInfo
   288  	values  map[string]string
   289  }
   290  
   291  func (c *mockAgentConfig) Tag() names.Tag {
   292  	return names.NewMachineTag("123")
   293  }
   294  
   295  func (c *mockAgentConfig) LogDir() string {
   296  	return c.logDir
   297  }
   298  
   299  func (c *mockAgentConfig) DataDir() string {
   300  	return c.dataDir
   301  }
   302  
   303  func (c *mockAgentConfig) StateServingInfo() (controller.StateServingInfo, bool) {
   304  	if c.info != nil {
   305  		return *c.info, true
   306  	}
   307  	return controller.StateServingInfo{}, false
   308  }
   309  
   310  func (c *mockAgentConfig) Value(key string) string {
   311  	return c.values[key]
   312  }
   313  
   314  type stubStateTracker struct {
   315  	testing.Stub
   316  	pool state.StatePool
   317  }
   318  
   319  func (s *stubStateTracker) Use() (*state.StatePool, error) {
   320  	s.MethodCall(s, "Use")
   321  	return &s.pool, s.NextErr()
   322  }
   323  
   324  func (s *stubStateTracker) Done() error {
   325  	s.MethodCall(s, "Done")
   326  	return s.NextErr()
   327  }
   328  
   329  func (s *stubStateTracker) Report() map[string]interface{} {
   330  	s.MethodCall(s, "Report")
   331  	return nil
   332  }
   333  
   334  type stubPrometheusRegisterer struct {
   335  	testing.Stub
   336  }
   337  
   338  func (s *stubPrometheusRegisterer) MustRegister(...prometheus.Collector) {
   339  	panic("should not be called")
   340  }
   341  
   342  func (s *stubPrometheusRegisterer) Register(c prometheus.Collector) error {
   343  	s.MethodCall(s, "Register", c)
   344  	return s.NextErr()
   345  }
   346  
   347  func (s *stubPrometheusRegisterer) Unregister(c prometheus.Collector) bool {
   348  	s.MethodCall(s, "Unregister", c)
   349  	return false
   350  }
   351  
   352  type stubGateWaiter struct {
   353  	testing.Stub
   354  	gate.Waiter
   355  }
   356  
   357  func (w *stubGateWaiter) IsUnlocked() bool {
   358  	w.MethodCall(w, "IsUnlocked")
   359  	return true
   360  }
   361  
   362  type stubAuditConfig struct {
   363  	testing.Stub
   364  	config auditlog.Config
   365  }
   366  
   367  func (c *stubAuditConfig) get() auditlog.Config {
   368  	c.MethodCall(c, "get")
   369  	return c.config
   370  }
   371  
   372  type mockAuthenticator struct {
   373  	macaroon.LocalMacaroonAuthenticator
   374  }
   375  
   376  type fakeMultiwatcherFactory struct {
   377  	multiwatcher.Factory
   378  }
   379  
   380  type stubDBGetter struct{}
   381  
   382  func (s stubDBGetter) GetDB(namespace string) (coredatabase.TrackedDB, error) {
   383  	if namespace != "controller" {
   384  		return nil, errors.Errorf(`expected a request for "controller" DB; got %q`, namespace)
   385  	}
   386  	return nil, nil
   387  }