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 }