github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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 10 "github.com/juju/juju/agent" 11 "github.com/juju/juju/api" 12 "github.com/juju/juju/api/base" 13 "github.com/juju/juju/core/migration" 14 "github.com/juju/juju/network" 15 "github.com/juju/juju/watcher" 16 "github.com/juju/juju/worker" 17 "github.com/juju/juju/worker/catacomb" 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 170 // Use zero DialOpts (no retries) because the worker must stay 171 // responsive to Kill requests. We don't want it to be blocked by 172 // a long set of retry attempts. 173 conn, err := w.config.APIOpen(apiInfo, api.DialOpts{}) 174 if err != nil { 175 // Don't return this error just log it and report to the 176 // migrationmaster that things didn't work out. 177 return errors.Annotate(err, "failed to open API to target controller") 178 } 179 defer conn.Close() 180 181 // Ask the agent to confirm that things look ok. 182 err = w.config.ValidateMigration(conn) 183 return errors.Trace(err) 184 } 185 186 func (w *Worker) doSUCCESS(status watcher.MigrationStatus) error { 187 hps, err := apiAddrsToHostPorts(status.TargetAPIAddrs) 188 if err != nil { 189 return errors.Annotate(err, "converting API addresses") 190 } 191 192 // Report first because the config update that's about to happen 193 // will cause the API connection to drop. The SUCCESS phase is the 194 // point of no return anyway. 195 if err := w.report(status, true); err != nil { 196 return errors.Trace(err) 197 } 198 199 err = w.config.Agent.ChangeConfig(func(conf agent.ConfigSetter) error { 200 conf.SetAPIHostPorts(hps) 201 conf.SetCACert(status.TargetCACert) 202 return nil 203 }) 204 return errors.Annotate(err, "setting agent config") 205 } 206 207 func (w *Worker) report(status watcher.MigrationStatus, success bool) error { 208 err := w.config.Facade.Report(status.MigrationId, status.Phase, success) 209 return errors.Annotate(err, "failed to report phase progress") 210 } 211 212 func apiAddrsToHostPorts(addrs []string) ([][]network.HostPort, error) { 213 hps, err := network.ParseHostPorts(addrs...) 214 if err != nil { 215 return nil, errors.Trace(err) 216 } 217 return [][]network.HostPort{hps}, nil 218 }