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  }