github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/modelworkermanager/modelworkermanager.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package modelworkermanager
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"time"
    10  
    11  	"github.com/juju/clock"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/names/v5"
    15  	"github.com/juju/worker/v3"
    16  	"github.com/juju/worker/v3/catacomb"
    17  
    18  	"github.com/juju/juju/apiserver/apiserverhttp"
    19  	"github.com/juju/juju/cmd/jujud/agent/engine"
    20  	"github.com/juju/juju/controller"
    21  	corelogger "github.com/juju/juju/core/logger"
    22  	"github.com/juju/juju/pki"
    23  	"github.com/juju/juju/state"
    24  )
    25  
    26  // ModelWatcher provides an interface for watching the additiona and
    27  // removal of models.
    28  type ModelWatcher interface {
    29  	WatchModels() state.StringsWatcher
    30  }
    31  
    32  // Controller provides an interface for getting models by UUID,
    33  // and other details needed to pass into the function to start workers for a model.
    34  // Once a model is no longer required, the returned function must
    35  // be called to dispose of the model.
    36  type Controller interface {
    37  	Config() (controller.Config, error)
    38  	Model(modelUUID string) (Model, func(), error)
    39  	RecordLogger(modelUUID string) (RecordLogger, error)
    40  }
    41  
    42  // Model represents a model.
    43  type Model interface {
    44  	MigrationMode() state.MigrationMode
    45  	Type() state.ModelType
    46  	Name() string
    47  	Owner() names.UserTag
    48  }
    49  
    50  // RecordLogger writes logs to backing store.
    51  type RecordLogger interface {
    52  	io.Closer
    53  	// Log writes the given log records to the logger's storage.
    54  	Log([]corelogger.LogRecord) error
    55  }
    56  
    57  // ModelLogger is a database backed loggo Writer.
    58  type ModelLogger interface {
    59  	loggo.Writer
    60  	Close() error
    61  }
    62  
    63  // ModelMetrics defines a way to create metrics for a model.
    64  type ModelMetrics interface {
    65  	ForModel(names.ModelTag) engine.MetricSink
    66  }
    67  
    68  // NewModelConfig holds the information required by the NewModelWorkerFunc
    69  // to start the workers for the specified model
    70  type NewModelConfig struct {
    71  	Authority        pki.Authority
    72  	ModelName        string // Use a fully qualified name "<namespace>-<name>"
    73  	ModelUUID        string
    74  	ModelType        state.ModelType
    75  	ModelLogger      ModelLogger
    76  	ModelMetrics     engine.MetricSink
    77  	Mux              *apiserverhttp.Mux
    78  	ControllerConfig controller.Config
    79  }
    80  
    81  // NewModelWorkerFunc should return a worker responsible for running
    82  // all a model's required workers; and for returning nil when there's
    83  // no more model to manage.
    84  type NewModelWorkerFunc func(config NewModelConfig) (worker.Worker, error)
    85  
    86  // Config holds the dependencies and configuration necessary to run
    87  // a model worker manager.
    88  type Config struct {
    89  	Authority      pki.Authority
    90  	Clock          clock.Clock
    91  	Logger         Logger
    92  	MachineID      string
    93  	ModelWatcher   ModelWatcher
    94  	ModelMetrics   ModelMetrics
    95  	Mux            *apiserverhttp.Mux
    96  	Controller     Controller
    97  	NewModelWorker NewModelWorkerFunc
    98  	ErrorDelay     time.Duration
    99  }
   100  
   101  // Validate returns an error if config cannot be expected to drive
   102  // a functional model worker manager.
   103  func (config Config) Validate() error {
   104  	if config.Authority == nil {
   105  		return errors.NotValidf("nil authority")
   106  	}
   107  	if config.Clock == nil {
   108  		return errors.NotValidf("nil Clock")
   109  	}
   110  	if config.Logger == nil {
   111  		return errors.NotValidf("nil Logger")
   112  	}
   113  	if config.MachineID == "" {
   114  		return errors.NotValidf("empty MachineID")
   115  	}
   116  	if config.ModelWatcher == nil {
   117  		return errors.NotValidf("nil ModelWatcher")
   118  	}
   119  	if config.ModelMetrics == nil {
   120  		return errors.NotValidf("nil ModelMetrics")
   121  	}
   122  	if config.Controller == nil {
   123  		return errors.NotValidf("nil Controller")
   124  	}
   125  	if config.NewModelWorker == nil {
   126  		return errors.NotValidf("nil NewModelWorker")
   127  	}
   128  	if config.ErrorDelay <= 0 {
   129  		return errors.NotValidf("non-positive ErrorDelay")
   130  	}
   131  	return nil
   132  }
   133  
   134  // New starts a new model worker manager.
   135  func New(config Config) (worker.Worker, error) {
   136  	if err := config.Validate(); err != nil {
   137  		return nil, errors.Trace(err)
   138  	}
   139  	m := &modelWorkerManager{
   140  		config: config,
   141  	}
   142  
   143  	err := catacomb.Invoke(catacomb.Plan{
   144  		Site: &m.catacomb,
   145  		Work: m.loop,
   146  	})
   147  	if err != nil {
   148  		return nil, errors.Trace(err)
   149  	}
   150  	return m, nil
   151  }
   152  
   153  type modelWorkerManager struct {
   154  	catacomb catacomb.Catacomb
   155  	config   Config
   156  	runner   *worker.Runner
   157  }
   158  
   159  // Kill satisfies the Worker interface.
   160  func (m *modelWorkerManager) Kill() {
   161  	m.catacomb.Kill(nil)
   162  }
   163  
   164  // Wait satisfies the Worker interface.
   165  func (m *modelWorkerManager) Wait() error {
   166  	return m.catacomb.Wait()
   167  }
   168  
   169  func (m *modelWorkerManager) loop() error {
   170  	controllerConfig, err := m.config.Controller.Config()
   171  	if err != nil {
   172  		return errors.Annotate(err, "unable to get controller config")
   173  	}
   174  	m.runner = worker.NewRunner(worker.RunnerParams{
   175  		IsFatal:       neverFatal,
   176  		MoreImportant: neverImportant,
   177  		RestartDelay:  m.config.ErrorDelay,
   178  		Logger:        m.config.Logger,
   179  	})
   180  	if err := m.catacomb.Add(m.runner); err != nil {
   181  		return errors.Trace(err)
   182  	}
   183  	watcher := m.config.ModelWatcher.WatchModels()
   184  	if err := m.catacomb.Add(watcher); err != nil {
   185  		return errors.Trace(err)
   186  	}
   187  
   188  	modelChanged := func(modelUUID string) error {
   189  		model, release, err := m.config.Controller.Model(modelUUID)
   190  		if errors.IsNotFound(err) {
   191  			// Model was removed, ignore it.
   192  			// The reason we ignore it here is that one of the embedded
   193  			// workers is also responding to the model life changes and
   194  			// when it returns a NotFound error, which is determined as a
   195  			// fatal error for the model worker engine. This causes it to be
   196  			// removed from the runner above. However since the runner itself
   197  			// has neverFatal as an error handler, the runner itself doesn't
   198  			// propagate the error.
   199  			return nil
   200  		} else if err != nil {
   201  			return errors.Trace(err)
   202  		}
   203  		defer release()
   204  
   205  		if !isModelActive(model) {
   206  			// Ignore this model until it's activated - we
   207  			// never want to run workers for an importing
   208  			// model.
   209  			// https://bugs.launchpad.net/juju/+bug/1646310
   210  			return nil
   211  		}
   212  
   213  		cfg := NewModelConfig{
   214  			Authority:        m.config.Authority,
   215  			ModelName:        fmt.Sprintf("%s-%s", model.Owner().Id(), model.Name()),
   216  			ModelUUID:        modelUUID,
   217  			ModelType:        model.Type(),
   218  			ModelMetrics:     m.config.ModelMetrics.ForModel(names.NewModelTag(modelUUID)),
   219  			Mux:              m.config.Mux,
   220  			ControllerConfig: controllerConfig,
   221  		}
   222  		return errors.Trace(m.ensure(cfg))
   223  	}
   224  
   225  	for {
   226  		select {
   227  		case <-m.catacomb.Dying():
   228  			return m.catacomb.ErrDying()
   229  		case uuids, ok := <-watcher.Changes():
   230  			if !ok {
   231  				return errors.New("changes stopped")
   232  			}
   233  			for _, modelUUID := range uuids {
   234  				if err := modelChanged(modelUUID); err != nil {
   235  					return errors.Trace(err)
   236  				}
   237  			}
   238  		}
   239  	}
   240  }
   241  
   242  func (m *modelWorkerManager) ensure(cfg NewModelConfig) error {
   243  	starter := m.starter(cfg)
   244  	if err := m.runner.StartWorker(cfg.ModelUUID, starter); !errors.IsAlreadyExists(err) {
   245  		return errors.Trace(err)
   246  	}
   247  	return nil
   248  }
   249  
   250  func (m *modelWorkerManager) starter(cfg NewModelConfig) func() (worker.Worker, error) {
   251  	return func() (worker.Worker, error) {
   252  		modelUUID := cfg.ModelUUID
   253  		modelName := fmt.Sprintf("%q (%s)", cfg.ModelName, cfg.ModelUUID)
   254  		m.config.Logger.Debugf("starting workers for model %s", modelName)
   255  
   256  		recordLogger, err := m.config.Controller.RecordLogger(modelUUID)
   257  		if err != nil {
   258  			return nil, errors.Annotatef(err, "unable to create db logger for %s", modelName)
   259  		}
   260  
   261  		cfg.ModelLogger = newModelLogger(
   262  			"controller-"+m.config.MachineID,
   263  			modelUUID,
   264  			recordLogger,
   265  			m.config.Clock,
   266  			m.config.Logger,
   267  		)
   268  		worker, err := m.config.NewModelWorker(cfg)
   269  		if err != nil {
   270  			cfg.ModelLogger.Close()
   271  			return nil, errors.Annotatef(err, "cannot manage model %s", modelName)
   272  		}
   273  		return worker, nil
   274  	}
   275  }
   276  
   277  func neverFatal(error) bool {
   278  	return false
   279  }
   280  
   281  func neverImportant(error, error) bool {
   282  	return false
   283  }
   284  
   285  func isModelActive(m Model) bool {
   286  	return m.MigrationMode() != state.MigrationModeImporting
   287  }
   288  
   289  // Report shows up in the dependency engine report.
   290  func (m *modelWorkerManager) Report() map[string]interface{} {
   291  	if m.runner == nil {
   292  		return nil
   293  	}
   294  	return m.runner.Report()
   295  }