github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/undertaker/undertaker.go (about) 1 // Copyright 2015-2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package undertaker 5 6 import ( 7 "fmt" 8 "sync" 9 10 "github.com/juju/errors" 11 "gopkg.in/juju/worker.v1/catacomb" 12 13 "github.com/juju/juju/apiserver/params" 14 "github.com/juju/juju/core/status" 15 "github.com/juju/juju/core/watcher" 16 "github.com/juju/juju/environs" 17 "github.com/juju/juju/environs/context" 18 "github.com/juju/juju/worker/common" 19 ) 20 21 // Facade covers the parts of the api/undertaker.UndertakerClient that we 22 // need for the worker. It's more than a little raw, but we'll survive. 23 type Facade interface { 24 ModelInfo() (params.UndertakerModelInfoResult, error) 25 WatchModelResources() (watcher.NotifyWatcher, error) 26 ProcessDyingModel() error 27 RemoveModel() error 28 SetStatus(status status.Status, message string, data map[string]interface{}) error 29 } 30 31 // Config holds the resources and configuration necessary to run an 32 // undertaker worker. 33 type Config struct { 34 Facade Facade 35 Destroyer environs.CloudDestroyer 36 CredentialAPI common.CredentialAPI 37 } 38 39 // Validate returns an error if the config cannot be expected to drive 40 // a functional undertaker worker. 41 func (config Config) Validate() error { 42 if config.Facade == nil { 43 return errors.NotValidf("nil Facade") 44 } 45 if config.CredentialAPI == nil { 46 return errors.NotValidf("nil CredentialAPI") 47 } 48 if config.Destroyer == nil { 49 return errors.NotValidf("nil Destroyer") 50 } 51 return nil 52 } 53 54 // NewUndertaker returns a worker which processes a dying model. 55 func NewUndertaker(config Config) (*Undertaker, error) { 56 if err := config.Validate(); err != nil { 57 return nil, errors.Trace(err) 58 } 59 60 u := &Undertaker{ 61 config: config, 62 } 63 err := catacomb.Invoke(catacomb.Plan{ 64 Site: &u.catacomb, 65 Work: u.run, 66 }) 67 if err != nil { 68 return nil, errors.Trace(err) 69 } 70 u.setCallCtx(common.NewCloudCallContext(config.CredentialAPI, u.catacomb.Dying)) 71 return u, nil 72 } 73 74 type Undertaker struct { 75 lock sync.Mutex 76 catacomb catacomb.Catacomb 77 config Config 78 79 callCtx context.ProviderCallContext 80 } 81 82 func (u *Undertaker) getCallCtx() context.ProviderCallContext { 83 u.lock.Lock() 84 defer u.lock.Unlock() 85 ctx := u.callCtx 86 return ctx 87 } 88 89 func (u *Undertaker) setCallCtx(ctx context.ProviderCallContext) { 90 u.lock.Lock() 91 defer u.lock.Unlock() 92 u.callCtx = ctx 93 } 94 95 // Kill is part of the worker.Worker interface. 96 func (u *Undertaker) Kill() { 97 u.catacomb.Kill(nil) 98 } 99 100 // Wait is part of the worker.Worker interface. 101 func (u *Undertaker) Wait() error { 102 return u.catacomb.Wait() 103 } 104 105 func (u *Undertaker) run() error { 106 result, err := u.config.Facade.ModelInfo() 107 if err != nil { 108 return errors.Trace(err) 109 } 110 if result.Error != nil { 111 return errors.Trace(result.Error) 112 } 113 modelInfo := result.Result 114 115 if modelInfo.Life == params.Alive { 116 return errors.Errorf("model still alive") 117 } 118 if modelInfo.Life == params.Dying { 119 // TODO(axw) 2016-04-14 #1570285 120 // We should update status with information 121 // about the remaining resources here, and 122 // also make the worker responsible for 123 // checking the emptiness criteria before 124 // attempting to remove the model. 125 if err := u.setStatus( 126 status.Destroying, 127 "cleaning up cloud resources", 128 ); err != nil { 129 return errors.Trace(err) 130 } 131 // Process the dying model. This blocks until the model 132 // is dead or the worker is stopped. 133 if err := u.processDyingModel(); err != nil { 134 return errors.Trace(err) 135 } 136 } 137 138 if modelInfo.IsSystem { 139 // Nothing to do. We don't destroy environ resources or 140 // delete model docs for a controller model, because we're 141 // running inside that controller and can't safely clean up 142 // our own infrastructure. (That'll be the client's job in 143 // the end, once we've reported that we've tidied up what we 144 // can, by returning nil here, indicating that we've set it 145 // to Dead -- implied by processDyingModel succeeding.) 146 return nil 147 } 148 149 // Now the model is known to be hosted and dying, we can tidy up any 150 // provider resources it might have used. 151 if err := u.setStatus( 152 status.Destroying, "tearing down cloud environment", 153 ); err != nil { 154 return errors.Trace(err) 155 } 156 if err := u.config.Destroyer.Destroy(u.getCallCtx()); err != nil { 157 return errors.Trace(err) 158 } 159 // Finally, the model is going to be dead, and be removed. 160 if err := u.config.Facade.RemoveModel(); err != nil { 161 return errors.Annotate(err, "cannot remove model") 162 } 163 return nil 164 } 165 166 func (u *Undertaker) setStatus(modelStatus status.Status, message string) error { 167 return u.config.Facade.SetStatus(modelStatus, message, nil) 168 } 169 170 func (u *Undertaker) processDyingModel() error { 171 watcher, err := u.config.Facade.WatchModelResources() 172 if err != nil { 173 return errors.Trace(err) 174 } 175 if err := u.catacomb.Add(watcher); err != nil { 176 return errors.Trace(err) 177 } 178 defer watcher.Kill() 179 attempt := 1 180 for { 181 select { 182 case <-u.catacomb.Dying(): 183 return u.catacomb.ErrDying() 184 case <-watcher.Changes(): 185 err := u.config.Facade.ProcessDyingModel() 186 if err == nil { 187 // ProcessDyingModel succeeded. We're free to 188 // destroy any remaining environ resources. 189 return nil 190 } 191 if !params.IsCodeModelNotEmpty(err) && !params.IsCodeHasHostedModels(err) { 192 return errors.Trace(err) 193 } 194 // Retry once there are changes to the model's resources. 195 u.setStatus( 196 status.Destroying, 197 fmt.Sprintf("attempt %d to destroy model failed (will retry): %v", attempt, err), 198 ) 199 } 200 attempt++ 201 } 202 }