github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/environupgrader/worker.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package environupgrader 5 6 import ( 7 stdcontext "context" 8 "fmt" 9 10 "github.com/juju/errors" 11 "github.com/juju/names/v5" 12 "github.com/juju/worker/v3" 13 "github.com/juju/worker/v3/catacomb" 14 15 "github.com/juju/juju/core/status" 16 "github.com/juju/juju/core/watcher" 17 "github.com/juju/juju/environs" 18 "github.com/juju/juju/rpc/params" 19 jujuworker "github.com/juju/juju/worker" 20 "github.com/juju/juju/worker/common" 21 "github.com/juju/juju/worker/gate" 22 "github.com/juju/juju/wrench" 23 ) 24 25 // Logger is here to stop the desire of creating a package level Logger. 26 // Don't do this, instead use the one passed as manifold config. 27 type logger interface{} 28 29 var _ logger = struct{}{} 30 31 // ErrModelRemoved indicates that this worker was operating on the model that is no longer found. 32 var ErrModelRemoved = errors.New("model has been removed") 33 34 // Facade exposes capabilities required by the worker. 35 type Facade interface { 36 ModelEnvironVersion(tag names.ModelTag) (int, error) 37 ModelTargetEnvironVersion(tag names.ModelTag) (int, error) 38 SetModelEnvironVersion(tag names.ModelTag, v int) error 39 SetModelStatus(names.ModelTag, status.Status, string, map[string]interface{}) error 40 WatchModelEnvironVersion(tag names.ModelTag) (watcher.NotifyWatcher, error) 41 } 42 43 // Config holds the configuration and dependencies for a worker. 44 type Config struct { 45 // Facade holds the API facade used by this worker for getting, 46 // setting and watching the model's environ version. 47 Facade Facade 48 49 // GateUnlocker holds a gate.Unlocker that the worker must call 50 // after the model has been successfully upgraded. 51 GateUnlocker gate.Unlocker 52 53 // ControllerTag holds the tag of the controller that runs this 54 // worker. 55 ControllerTag names.ControllerTag 56 57 // ModelTag holds the tag of the model to which this worker is 58 // scoped. 59 ModelTag names.ModelTag 60 61 // Environ holds the Environ used to run upgrade steps, or nil 62 // if the worker should wait for upgrade steps to be run by 63 // another agent. 64 Environ environs.Environ 65 66 // CredentialAPI holds the API facade used to invalidate credential 67 // whenever the worker makes cloud calls if credential for this model 68 // becomes invalid. 69 CredentialAPI common.CredentialAPI 70 71 Logger Logger 72 } 73 74 // Validate returns an error if the config cannot be expected 75 // to drive a functional worker. 76 func (config Config) Validate() error { 77 if config.Facade == nil { 78 return errors.NotValidf("nil Facade") 79 } 80 if config.GateUnlocker == nil { 81 return errors.NotValidf("nil GateUnlocker") 82 } 83 if config.ControllerTag == (names.ControllerTag{}) { 84 return errors.NotValidf("empty ControllerTag") 85 } 86 if config.ModelTag == (names.ModelTag{}) { 87 return errors.NotValidf("empty ModelTag") 88 } 89 if config.CredentialAPI == nil { 90 return errors.NotValidf("nil CredentialAPI") 91 } 92 if config.Logger == nil { 93 return errors.NotValidf("nil Logger") 94 } 95 return nil 96 } 97 98 // NewWorker returns a worker that ensures that environ/provider schema upgrades 99 // are run when the model is first loaded by a controller of a new version. The 100 // worker either runs the upgrades or waits for another controller unit to run 101 // them, depending on the configuration. 102 func NewWorker(config Config) (worker.Worker, error) { 103 if err := config.Validate(); err != nil { 104 return nil, errors.Trace(err) 105 } 106 targetVersion, err := config.Facade.ModelTargetEnvironVersion(config.ModelTag) 107 if err != nil { 108 if params.IsCodeNotFound(err) { 109 return nil, ErrModelRemoved 110 } 111 return nil, errors.Trace(err) 112 } 113 if config.Environ != nil { 114 return newUpgradeWorker(config, targetVersion) 115 } 116 return newWaitWorker(config, targetVersion) 117 } 118 119 // newWaitWorker returns a worker that waits for the controller leader to run 120 // the upgrade steps and update the model's environ version, and then unlocks 121 // the gate. 122 func newWaitWorker(config Config, targetVersion int) (worker.Worker, error) { 123 watcher, err := config.Facade.WatchModelEnvironVersion(config.ModelTag) 124 if err != nil { 125 return nil, errors.Trace(err) 126 } 127 ww := waitWorker{ 128 watcher: watcher, 129 facade: config.Facade, 130 modelTag: config.ModelTag, 131 gate: config.GateUnlocker, 132 targetVersion: targetVersion, 133 } 134 if err := catacomb.Invoke(catacomb.Plan{ 135 Site: &ww.catacomb, 136 Init: []worker.Worker{watcher}, 137 Work: ww.loop, 138 }); err != nil { 139 return nil, errors.Trace(err) 140 } 141 return &ww, nil 142 } 143 144 type waitWorker struct { 145 catacomb catacomb.Catacomb 146 watcher watcher.NotifyWatcher 147 facade Facade 148 modelTag names.ModelTag 149 gate gate.Unlocker 150 targetVersion int 151 } 152 153 func (ww *waitWorker) Kill() { 154 ww.catacomb.Kill(nil) 155 } 156 157 func (ww *waitWorker) Wait() error { 158 return ww.catacomb.Wait() 159 } 160 161 func (ww *waitWorker) loop() error { 162 for { 163 select { 164 case <-ww.catacomb.Dying(): 165 return ww.catacomb.ErrDying() 166 case _, ok := <-ww.watcher.Changes(): 167 if !ok { 168 return ww.catacomb.ErrDying() 169 } 170 currentVersion, err := ww.facade.ModelEnvironVersion(ww.modelTag) 171 if err != nil { 172 if params.IsCodeNotFound(err) { 173 return ErrModelRemoved 174 } 175 return errors.Trace(err) 176 } 177 if currentVersion >= ww.targetVersion { 178 ww.gate.Unlock() 179 return nil 180 } 181 } 182 } 183 } 184 185 // newUpgradeWorker returns a worker that runs the upgrade steps, updates the 186 // model's environ version, and unlocks the gate. 187 func newUpgradeWorker(config Config, targetVersion int) (worker.Worker, error) { 188 currentVersion, err := config.Facade.ModelEnvironVersion(config.ModelTag) 189 if err != nil { 190 return nil, errors.Trace(err) 191 } 192 193 return jujuworker.NewSimpleWorker(func(<-chan struct{}) error { 194 // NOTE(axw) the abort channel is ignored, because upgrade 195 // steps are not interruptible. If we find they need to be 196 // interruptible, we should consider passing through a 197 // context.Context for cancellation, and cancelling it if 198 // the abort channel is signalled. 199 setVersion := func(v int) error { 200 return config.Facade.SetModelEnvironVersion(config.ModelTag, v) 201 } 202 setStatus := func(s status.Status, info string) error { 203 return config.Facade.SetModelStatus(config.ModelTag, s, info, nil) 204 } 205 if targetVersion > currentVersion { 206 if err := setStatus(status.Busy, fmt.Sprintf( 207 "upgrading environ from version %d to %d", 208 currentVersion, targetVersion, 209 )); err != nil { 210 return errors.Trace(err) 211 } 212 } 213 if err := runEnvironUpgradeSteps( 214 config.Environ, 215 config.ControllerTag, 216 config.ModelTag, 217 currentVersion, 218 targetVersion, 219 setVersion, 220 common.NewCloudCallContextFunc(config.CredentialAPI), 221 config.Logger, 222 ); err != nil { 223 info := fmt.Sprintf("failed to upgrade environ: %s", err) 224 if err := setStatus(status.Error, info); err != nil { 225 config.Logger.Warningf("failed to update model status: %v", err) 226 } 227 return errors.Annotate(err, "upgrading environ") 228 } 229 if err := setStatus(status.Available, ""); err != nil { 230 return errors.Trace(err) 231 } 232 config.GateUnlocker.Unlock() 233 return nil 234 }), nil 235 } 236 237 func runEnvironUpgradeSteps( 238 env environs.Environ, 239 controllerTag names.ControllerTag, 240 modelTag names.ModelTag, 241 currentVersion int, 242 targetVersion int, 243 setVersion func(int) error, 244 callCtxFunc common.CloudCallContextFunc, 245 logger Logger, 246 ) error { 247 if wrench.IsActive("environupgrader", "fail-all") || 248 wrench.IsActive("environupgrader", "fail-model-"+modelTag.Id()) { 249 return errors.New("wrench active") 250 } 251 upgrader, ok := env.(environs.Upgrader) 252 if !ok { 253 logger.Debugf("%T does not support environs.Upgrader", env) 254 return nil 255 } 256 args := environs.UpgradeOperationsParams{ 257 ControllerUUID: controllerTag.Id(), 258 } 259 callCtx := callCtxFunc(stdcontext.Background()) 260 for _, op := range upgrader.UpgradeOperations(callCtx, args) { 261 if op.TargetVersion <= currentVersion { 262 // The operation is for the same as or older 263 // than the current environ version. 264 logger.Tracef( 265 "ignoring upgrade operation for version %v", 266 op.TargetVersion, 267 ) 268 continue 269 } 270 if op.TargetVersion > targetVersion { 271 // The operation is for a version newer than 272 // the provider's current version. This will 273 // only happen for an improperly written provider. 274 logger.Debugf( 275 "ignoring upgrade operation for version %v", 276 op.TargetVersion, 277 ) 278 continue 279 } 280 logger.Debugf( 281 "running upgrade operation for version %v", 282 op.TargetVersion, 283 ) 284 for _, step := range op.Steps { 285 logger.Debugf("running step %q", step.Description()) 286 if err := step.Run(callCtx); err != nil { 287 return errors.Trace(err) 288 } 289 } 290 // Record the new version as we go, so we minimise the number 291 // of operations we'll re-run in the case of failure. 292 if err := setVersion(op.TargetVersion); err != nil { 293 return errors.Trace(err) 294 } 295 } 296 return nil 297 }