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