github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/migrationminion/worker.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package migrationminion 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/loggo" 9 "gopkg.in/juju/worker.v1" 10 "gopkg.in/juju/worker.v1/catacomb" 11 12 "github.com/juju/juju/agent" 13 "github.com/juju/juju/api" 14 "github.com/juju/juju/api/base" 15 "github.com/juju/juju/core/migration" 16 "github.com/juju/juju/core/watcher" 17 "github.com/juju/juju/network" 18 "github.com/juju/juju/worker/fortress" 19 ) 20 21 var logger = loggo.GetLogger("juju.worker.migrationminion") 22 23 // Facade exposes controller functionality to a Worker. 24 type Facade interface { 25 Watch() (watcher.MigrationStatusWatcher, error) 26 Report(migrationId string, phase migration.Phase, success bool) error 27 } 28 29 // Config defines the operation of a Worker. 30 type Config struct { 31 Agent agent.Agent 32 Facade Facade 33 Guard fortress.Guard 34 APIOpen func(*api.Info, api.DialOpts) (api.Connection, error) 35 ValidateMigration func(base.APICaller) error 36 } 37 38 // Validate returns an error if config cannot drive a Worker. 39 func (config Config) Validate() error { 40 if config.Agent == nil { 41 return errors.NotValidf("nil Agent") 42 } 43 if config.Facade == nil { 44 return errors.NotValidf("nil Facade") 45 } 46 if config.Guard == nil { 47 return errors.NotValidf("nil Guard") 48 } 49 if config.APIOpen == nil { 50 return errors.NotValidf("nil APIOpen") 51 } 52 if config.ValidateMigration == nil { 53 return errors.NotValidf("nil ValidateMigration") 54 } 55 return nil 56 } 57 58 // New returns a Worker backed by config, or an error. 59 func New(config Config) (worker.Worker, error) { 60 if err := config.Validate(); err != nil { 61 return nil, errors.Trace(err) 62 } 63 w := &Worker{config: config} 64 err := catacomb.Invoke(catacomb.Plan{ 65 Site: &w.catacomb, 66 Work: w.loop, 67 }) 68 if err != nil { 69 return nil, errors.Trace(err) 70 } 71 return w, nil 72 } 73 74 // Worker waits for a model migration to be active, then locks down the 75 // configured fortress and implements the migration. 76 type Worker struct { 77 catacomb catacomb.Catacomb 78 config Config 79 } 80 81 // Kill implements worker.Worker. 82 func (w *Worker) Kill() { 83 w.catacomb.Kill(nil) 84 } 85 86 // Wait implements worker.Worker. 87 func (w *Worker) Wait() error { 88 return w.catacomb.Wait() 89 } 90 91 func (w *Worker) loop() error { 92 watcher, err := w.config.Facade.Watch() 93 if err != nil { 94 return errors.Annotate(err, "setting up watcher") 95 } 96 if err := w.catacomb.Add(watcher); err != nil { 97 return errors.Trace(err) 98 } 99 100 for { 101 select { 102 case <-w.catacomb.Dying(): 103 return w.catacomb.ErrDying() 104 case status, ok := <-watcher.Changes(): 105 if !ok { 106 return errors.New("watcher channel closed") 107 } 108 if err := w.handle(status); err != nil { 109 return errors.Trace(err) 110 } 111 } 112 } 113 } 114 115 func (w *Worker) handle(status watcher.MigrationStatus) error { 116 logger.Infof("migration phase is now: %s", status.Phase) 117 118 if !status.Phase.IsRunning() { 119 return w.config.Guard.Unlock() 120 } 121 122 // Ensure that all workers related to migration fortress have 123 // stopped and aren't allowed to restart. 124 err := w.config.Guard.Lockdown(w.catacomb.Dying()) 125 if errors.Cause(err) == fortress.ErrAborted { 126 return w.catacomb.ErrDying() 127 } else if err != nil { 128 return errors.Trace(err) 129 } 130 131 switch status.Phase { 132 case migration.QUIESCE: 133 err = w.doQUIESCE(status) 134 case migration.VALIDATION: 135 err = w.doVALIDATION(status) 136 case migration.SUCCESS: 137 err = w.doSUCCESS(status) 138 default: 139 // The minion doesn't need to do anything for other 140 // migration phases. 141 } 142 return errors.Trace(err) 143 } 144 145 func (w *Worker) doQUIESCE(status watcher.MigrationStatus) error { 146 // Report that the minion is ready and that all workers that 147 // should be shut down have done so. 148 return w.report(status, true) 149 } 150 151 func (w *Worker) doVALIDATION(status watcher.MigrationStatus) error { 152 err := w.validate(status) 153 if err != nil { 154 // Don't return this error just log it and report to the 155 // migrationmaster that things didn't work out. 156 logger.Errorf("validation failed: %v", err) 157 } 158 return w.report(status, err == nil) 159 } 160 161 func (w *Worker) validate(status watcher.MigrationStatus) error { 162 agentConf := w.config.Agent.CurrentConfig() 163 apiInfo, ok := agentConf.APIInfo() 164 if !ok { 165 return errors.New("no API connection details") 166 } 167 apiInfo.Addrs = status.TargetAPIAddrs 168 apiInfo.CACert = status.TargetCACert 169 // Application agents (k8s) use old password. 170 if apiInfo.Password == "" { 171 apiInfo.Password = agentConf.OldPassword() 172 } 173 174 // Use zero DialOpts (no retries) because the worker must stay 175 // responsive to Kill requests. We don't want it to be blocked by 176 // a long set of retry attempts. 177 conn, err := w.config.APIOpen(apiInfo, api.DialOpts{}) 178 if err != nil { 179 // Don't return this error just log it and report to the 180 // migrationmaster that things didn't work out. 181 return errors.Annotate(err, "failed to open API to target controller") 182 } 183 defer conn.Close() 184 185 // Ask the agent to confirm that things look ok. 186 err = w.config.ValidateMigration(conn) 187 return errors.Trace(err) 188 } 189 190 func (w *Worker) doSUCCESS(status watcher.MigrationStatus) error { 191 hps, err := apiAddrsToHostPorts(status.TargetAPIAddrs) 192 if err != nil { 193 return errors.Annotate(err, "converting API addresses") 194 } 195 196 // Report first because the config update that's about to happen 197 // will cause the API connection to drop. The SUCCESS phase is the 198 // point of no return anyway. 199 if err := w.report(status, true); err != nil { 200 return errors.Trace(err) 201 } 202 203 err = w.config.Agent.ChangeConfig(func(conf agent.ConfigSetter) error { 204 conf.SetAPIHostPorts(hps) 205 conf.SetCACert(status.TargetCACert) 206 return nil 207 }) 208 return errors.Annotate(err, "setting agent config") 209 } 210 211 func (w *Worker) report(status watcher.MigrationStatus, success bool) error { 212 logger.Debugf("reporting back for phase %s: %v", status.Phase, success) 213 err := w.config.Facade.Report(status.MigrationId, status.Phase, success) 214 return errors.Annotate(err, "failed to report phase progress") 215 } 216 217 func apiAddrsToHostPorts(addrs []string) ([][]network.HostPort, error) { 218 hps, err := network.ParseHostPorts(addrs...) 219 if err != nil { 220 return nil, errors.Trace(err) 221 } 222 return [][]network.HostPort{hps}, nil 223 }