github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/modelworkermanager/modelworkermanager_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package modelworkermanager_test 5 6 import ( 7 "time" 8 9 "github.com/juju/clock" 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "github.com/juju/names/v5" 13 "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/worker/v3" 16 "github.com/juju/worker/v3/dependency" 17 "github.com/juju/worker/v3/workertest" 18 gc "gopkg.in/check.v1" 19 "gopkg.in/tomb.v2" 20 21 "github.com/juju/juju/cmd/jujud/agent/engine" 22 "github.com/juju/juju/controller" 23 "github.com/juju/juju/pki" 24 pkitest "github.com/juju/juju/pki/test" 25 "github.com/juju/juju/state" 26 coretesting "github.com/juju/juju/testing" 27 "github.com/juju/juju/worker/modelworkermanager" 28 ) 29 30 var _ = gc.Suite(&suite{}) 31 32 type suite struct { 33 authority pki.Authority 34 testing.IsolationSuite 35 workerC chan *mockWorker 36 } 37 38 func (s *suite) SetUpTest(c *gc.C) { 39 authority, err := pkitest.NewTestAuthority() 40 c.Assert(err, jc.ErrorIsNil) 41 s.authority = authority 42 s.IsolationSuite.SetUpTest(c) 43 s.workerC = make(chan *mockWorker, 100) 44 } 45 46 func (s *suite) TestStartEmpty(c *gc.C) { 47 s.runTest(c, func(_ worker.Worker, w *mockModelWatcher, _ *mockController) { 48 w.sendModelChange() 49 50 s.assertNoWorkers(c) 51 }) 52 } 53 54 func (s *suite) TestStartsInitialWorker(c *gc.C) { 55 s.runTest(c, func(_ worker.Worker, w *mockModelWatcher, _ *mockController) { 56 w.sendModelChange("uuid") 57 58 s.assertStarts(c, "uuid") 59 }) 60 } 61 62 func (s *suite) TestStartsLaterWorker(c *gc.C) { 63 s.runTest(c, func(_ worker.Worker, w *mockModelWatcher, _ *mockController) { 64 w.sendModelChange() 65 w.sendModelChange("uuid") 66 67 s.assertStarts(c, "uuid") 68 }) 69 } 70 71 func (s *suite) TestStartsMultiple(c *gc.C) { 72 s.runTest(c, func(_ worker.Worker, w *mockModelWatcher, _ *mockController) { 73 w.sendModelChange("uuid1") 74 w.sendModelChange("uuid2", "uuid3") 75 w.sendModelChange("uuid4") 76 77 s.assertStarts(c, "uuid1", "uuid2", "uuid3", "uuid4") 78 }) 79 } 80 81 func (s *suite) TestIgnoresRepetition(c *gc.C) { 82 s.runTest(c, func(_ worker.Worker, w *mockModelWatcher, _ *mockController) { 83 w.sendModelChange("uuid") 84 w.sendModelChange("uuid", "uuid") 85 w.sendModelChange("uuid") 86 87 s.assertStarts(c, "uuid") 88 }) 89 } 90 91 func (s *suite) TestRestartsErrorWorker(c *gc.C) { 92 s.runTest(c, func(w worker.Worker, mw *mockModelWatcher, _ *mockController) { 93 mw.sendModelChange("uuid") 94 workers := s.waitWorkers(c, 1) 95 workers[0].tomb.Kill(errors.New("blaf")) 96 97 s.assertStarts(c, "uuid") 98 workertest.CheckAlive(c, w) 99 }) 100 } 101 102 func (s *suite) TestRestartsFinishedWorker(c *gc.C) { 103 // It must be possible to restart the workers for a model due to 104 // model migrations: a model can be migrated away from a 105 // controller and then migrated back later. 106 s.runTest(c, func(w worker.Worker, mw *mockModelWatcher, _ *mockController) { 107 mw.sendModelChange("uuid") 108 workers := s.waitWorkers(c, 1) 109 workertest.CleanKill(c, workers[0]) 110 111 s.assertNoWorkers(c) 112 113 mw.sendModelChange("uuid") 114 workertest.CheckAlive(c, w) 115 s.waitWorkers(c, 1) 116 }) 117 } 118 119 func (s *suite) TestKillsManagers(c *gc.C) { 120 s.runTest(c, func(w worker.Worker, mw *mockModelWatcher, _ *mockController) { 121 mw.sendModelChange("uuid1", "uuid2") 122 workers := s.waitWorkers(c, 2) 123 124 workertest.CleanKill(c, w) 125 for _, worker := range workers { 126 workertest.CheckKilled(c, worker) 127 } 128 s.assertNoWorkers(c) 129 }) 130 } 131 132 func (s *suite) TestClosedChangesChannel(c *gc.C) { 133 s.runDirtyTest(c, func(w worker.Worker, mw *mockModelWatcher, _ *mockController) { 134 mw.sendModelChange("uuid1", "uuid2") 135 workers := s.waitWorkers(c, 2) 136 137 close(mw.envWatcher.changes) 138 err := workertest.CheckKilled(c, w) 139 c.Check(err, gc.ErrorMatches, "changes stopped") 140 for _, worker := range workers { 141 workertest.CheckKilled(c, worker) 142 } 143 s.assertNoWorkers(c) 144 }) 145 } 146 147 func (s *suite) TestNoStartingWorkersForImportingModel(c *gc.C) { 148 // We shouldn't start workers while the model is importing, 149 // otherwise the migrationmaster gets very confused. 150 // https://bugs.launchpad.net/juju/+bug/1646310 151 s.runTest(c, func(_ worker.Worker, w *mockModelWatcher, g *mockController) { 152 g.model.migrationMode = state.MigrationModeImporting 153 w.sendModelChange("uuid1") 154 155 s.assertNoWorkers(c) 156 }) 157 } 158 159 func (s *suite) TestReport(c *gc.C) { 160 s.runTest(c, func(w worker.Worker, mw *mockModelWatcher, _ *mockController) { 161 mw.sendModelChange("uuid") 162 s.assertStarts(c, "uuid") 163 164 reporter, ok := w.(worker.Reporter) 165 c.Assert(ok, jc.IsTrue) 166 report := reporter.Report() 167 c.Assert(report, gc.NotNil) 168 // TODO: pass a clock through in the worker config so it can be passed 169 // to the worker.Runner used in the model to control time. 170 // For now, we just look at the started state. 171 workers := report["workers"].(map[string]interface{}) 172 modelWorker := workers["uuid"].(map[string]interface{}) 173 c.Assert(modelWorker["state"], gc.Equals, "started") 174 }) 175 } 176 177 type testFunc func(worker.Worker, *mockModelWatcher, *mockController) 178 type killFunc func(*gc.C, worker.Worker) 179 180 func (s *suite) runTest(c *gc.C, test testFunc) { 181 s.runKillTest(c, workertest.CleanKill, test) 182 } 183 184 func (s *suite) runDirtyTest(c *gc.C, test testFunc) { 185 s.runKillTest(c, workertest.DirtyKill, test) 186 } 187 188 func (s *suite) runKillTest(c *gc.C, kill killFunc, test testFunc) { 189 watcher := newMockModelWatcher() 190 controller := newMockController() 191 config := modelworkermanager.Config{ 192 Authority: s.authority, 193 Clock: clock.WallClock, 194 Logger: loggo.GetLogger("test"), 195 MachineID: "1", 196 ModelWatcher: watcher, 197 Controller: controller, 198 NewModelWorker: s.startModelWorker, 199 ModelMetrics: dummyModelMetrics{}, 200 ErrorDelay: time.Millisecond, 201 } 202 w, err := modelworkermanager.New(config) 203 c.Assert(err, jc.ErrorIsNil) 204 defer kill(c, w) 205 test(w, watcher, controller) 206 } 207 208 type dummyModelMetrics struct{} 209 210 func (dummyModelMetrics) ForModel(model names.ModelTag) engine.MetricSink { 211 return dummyMetricSink{ 212 Metrics: dependency.DefaultMetrics(), 213 } 214 } 215 216 type dummyMetricSink struct { 217 dependency.Metrics 218 } 219 220 func (dummyMetricSink) Unregister() bool { 221 return true 222 } 223 224 func (s *suite) startModelWorker(config modelworkermanager.NewModelConfig) (worker.Worker, error) { 225 worker := newMockWorker(config) 226 s.workerC <- worker 227 return worker, nil 228 } 229 230 func (s *suite) assertStarts(c *gc.C, expect ...string) { 231 count := len(expect) 232 actual := make([]string, count) 233 workers := s.waitWorkers(c, count) 234 for i, worker := range workers { 235 actual[i] = worker.config.ModelUUID 236 c.Assert(worker.config.ModelType, gc.Equals, state.ModelTypeIAAS) 237 } 238 c.Assert(actual, jc.SameContents, expect) 239 } 240 241 func (s *suite) waitWorkers(c *gc.C, expectedCount int) []*mockWorker { 242 if expectedCount < 1 { 243 c.Fatal("expectedCount must be >= 1") 244 } 245 workers := make([]*mockWorker, 0, expectedCount) 246 for { 247 select { 248 case worker := <-s.workerC: 249 workers = append(workers, worker) 250 if len(workers) == expectedCount { 251 s.assertNoWorkers(c) 252 return workers 253 } 254 case <-time.After(coretesting.LongWait): 255 c.Fatal("timed out waiting for workers to be started") 256 } 257 } 258 } 259 260 func (s *suite) assertNoWorkers(c *gc.C) { 261 select { 262 case worker := <-s.workerC: 263 c.Fatalf("saw unexpected worker: %s", worker.config.ModelUUID) 264 case <-time.After(coretesting.ShortWait): 265 } 266 } 267 268 func newMockWorker(config modelworkermanager.NewModelConfig) *mockWorker { 269 w := &mockWorker{config: config} 270 w.tomb.Go(func() error { 271 <-w.tomb.Dying() 272 return nil 273 }) 274 return w 275 } 276 277 type mockWorker struct { 278 tomb tomb.Tomb 279 config modelworkermanager.NewModelConfig 280 } 281 282 func (mock *mockWorker) Kill() { 283 mock.tomb.Kill(nil) 284 } 285 286 func (mock *mockWorker) Wait() error { 287 return mock.tomb.Wait() 288 } 289 290 func newMockModelWatcher() *mockModelWatcher { 291 return &mockModelWatcher{ 292 envWatcher: &mockEnvWatcher{ 293 Worker: workertest.NewErrorWorker(nil), 294 changes: make(chan []string), 295 }, 296 } 297 } 298 299 type mockModelWatcher struct { 300 envWatcher *mockEnvWatcher 301 } 302 303 func (mock *mockModelWatcher) WatchModels() state.StringsWatcher { 304 return mock.envWatcher 305 } 306 307 func (mock *mockModelWatcher) sendModelChange(uuids ...string) { 308 mock.envWatcher.changes <- uuids 309 } 310 311 type mockController struct { 312 testing.Stub 313 model mockModel 314 } 315 316 func newMockController() *mockController { 317 return &mockController{ 318 model: mockModel{ 319 migrationMode: state.MigrationModeNone, 320 modelType: state.ModelTypeIAAS, 321 }, 322 } 323 } 324 325 func (mock *mockController) Config() (controller.Config, error) { 326 mock.MethodCall(mock, "Config") 327 return make(controller.Config), nil 328 } 329 330 func (mock *mockController) Model(uuid string) (modelworkermanager.Model, func(), error) { 331 mock.MethodCall(mock, "Model", uuid) 332 if err := mock.NextErr(); err != nil { 333 return nil, nil, err 334 } 335 release := func() { 336 mock.MethodCall(mock, "release") 337 } 338 return &mock.model, release, nil 339 } 340 341 type fakeLogger struct { 342 modelworkermanager.RecordLogger 343 } 344 345 func (mock *mockController) RecordLogger(uuid string) (modelworkermanager.RecordLogger, error) { 346 mock.MethodCall(mock, "RecordLogger", uuid) 347 return &fakeLogger{}, nil 348 } 349 350 type mockModel struct { 351 migrationMode state.MigrationMode 352 modelType state.ModelType 353 } 354 355 func (m *mockModel) MigrationMode() state.MigrationMode { 356 return m.migrationMode 357 } 358 359 func (m *mockModel) Type() state.ModelType { 360 return m.modelType 361 } 362 363 func (m *mockModel) Name() string { 364 return "doesn't matter for this test" 365 } 366 367 func (m *mockModel) Owner() names.UserTag { 368 return names.NewUserTag("anyone-is-fine") 369 } 370 371 type mockEnvWatcher struct { 372 worker.Worker 373 changes chan []string 374 } 375 376 func (w *mockEnvWatcher) Err() error { 377 panic("not used") 378 } 379 380 func (w *mockEnvWatcher) Stop() error { 381 return worker.Stop(w) 382 } 383 384 func (w *mockEnvWatcher) Changes() <-chan []string { 385 return w.changes 386 }