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 }