github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/worker/upgrader/upgrader.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package upgrader 5 6 import ( 7 "fmt" 8 "net/http" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/names" 14 "github.com/juju/utils" 15 "launchpad.net/tomb" 16 17 "github.com/juju/juju/agent" 18 agenttools "github.com/juju/juju/agent/tools" 19 "github.com/juju/juju/api/upgrader" 20 "github.com/juju/juju/state/watcher" 21 coretools "github.com/juju/juju/tools" 22 "github.com/juju/juju/version" 23 ) 24 25 // retryAfter returns a channel that receives a value 26 // when a failed download should be retried. 27 var retryAfter = func() <-chan time.Time { 28 return time.After(5 * time.Second) 29 } 30 31 var logger = loggo.GetLogger("juju.worker.upgrader") 32 33 // Upgrader represents a worker that watches the state for upgrade 34 // requests. 35 type Upgrader struct { 36 tomb tomb.Tomb 37 st *upgrader.State 38 dataDir string 39 tag names.Tag 40 origAgentVersion version.Number 41 areUpgradeStepsRunning func() bool 42 agentUpgradeComplete chan struct{} 43 } 44 45 // NewAgentUpgrader returns a new upgrader worker. It watches changes to the 46 // current version of the current agent (with the given tag) and tries to 47 // download the tools for any new version into the given data directory. If 48 // an upgrade is needed, the worker will exit with an UpgradeReadyError 49 // holding details of the requested upgrade. The tools will have been 50 // downloaded and unpacked. 51 func NewAgentUpgrader( 52 st *upgrader.State, 53 agentConfig agent.Config, 54 origAgentVersion version.Number, 55 areUpgradeStepsRunning func() bool, 56 agentUpgradeComplete chan struct{}, 57 ) *Upgrader { 58 u := &Upgrader{ 59 st: st, 60 dataDir: agentConfig.DataDir(), 61 tag: agentConfig.Tag(), 62 origAgentVersion: origAgentVersion, 63 areUpgradeStepsRunning: areUpgradeStepsRunning, 64 agentUpgradeComplete: agentUpgradeComplete, 65 } 66 go func() { 67 defer u.tomb.Done() 68 u.tomb.Kill(u.loop()) 69 }() 70 return u 71 } 72 73 // Kill implements worker.Worker.Kill. 74 func (u *Upgrader) Kill() { 75 u.tomb.Kill(nil) 76 } 77 78 // Wait implements worker.Worker.Wait. 79 func (u *Upgrader) Wait() error { 80 return u.tomb.Wait() 81 } 82 83 // Stop stops the upgrader and returns any 84 // error it encountered when running. 85 func (u *Upgrader) Stop() error { 86 u.Kill() 87 return u.Wait() 88 } 89 90 // allowedTargetVersion checks if targetVersion is too different from 91 // curVersion to allow a downgrade. 92 func allowedTargetVersion( 93 origAgentVersion version.Number, 94 curVersion version.Number, 95 upgradeRunning bool, 96 targetVersion version.Number, 97 ) bool { 98 if upgradeRunning && targetVersion == origAgentVersion { 99 return true 100 } 101 if targetVersion.Major < curVersion.Major { 102 return false 103 } 104 if targetVersion.Major == curVersion.Major && targetVersion.Minor < curVersion.Minor { 105 return false 106 } 107 return true 108 } 109 110 // closeChannel can be called multiple times to 111 // close the channel without panicing. 112 func closeChannel(ch chan struct{}) { 113 select { 114 case <-ch: 115 return 116 default: 117 close(ch) 118 } 119 } 120 121 func (u *Upgrader) loop() error { 122 // Start by reporting current tools (which includes arch/series, and is 123 // used by the state server in communicating the desired version below). 124 if err := u.st.SetVersion(u.tag.String(), version.Current); err != nil { 125 return errors.Annotate(err, "cannot set agent version") 126 } 127 versionWatcher, err := u.st.WatchAPIVersion(u.tag.String()) 128 if err != nil { 129 return err 130 } 131 changes := versionWatcher.Changes() 132 defer watcher.Stop(versionWatcher, &u.tomb) 133 var retry <-chan time.Time 134 135 // We don't read on the dying channel until we have received the 136 // initial event from the API version watcher, thus ensuring 137 // that we attempt an upgrade even if other workers are dying 138 // all around us. 139 var ( 140 dying <-chan struct{} 141 wantTools *coretools.Tools 142 wantVersion version.Number 143 ) 144 for { 145 select { 146 case _, ok := <-changes: 147 if !ok { 148 return watcher.EnsureErr(versionWatcher) 149 } 150 wantVersion, err = u.st.DesiredVersion(u.tag.String()) 151 if err != nil { 152 return err 153 } 154 logger.Infof("desired tool version: %v", wantVersion) 155 dying = u.tomb.Dying() 156 case <-retry: 157 case <-dying: 158 return nil 159 } 160 if wantVersion == version.Current.Number { 161 closeChannel(u.agentUpgradeComplete) 162 continue 163 } else if !allowedTargetVersion(u.origAgentVersion, version.Current.Number, 164 u.areUpgradeStepsRunning(), wantVersion) { 165 // See also bug #1299802 where when upgrading from 166 // 1.16 to 1.18 there is a race condition that can 167 // cause the unit agent to upgrade, and then want to 168 // downgrade when its associate machine agent has not 169 // finished upgrading. 170 logger.Infof("desired tool version: %s is older than current %s, refusing to downgrade", 171 wantVersion, version.Current) 172 closeChannel(u.agentUpgradeComplete) 173 continue 174 } 175 logger.Infof("upgrade requested from %v to %v", version.Current, wantVersion) 176 177 // Check if tools have already been downloaded. 178 wantVersionBinary := toBinaryVersion(wantVersion) 179 if u.toolsAlreadyDownloaded(wantVersionBinary) { 180 return u.newUpgradeReadyError(wantVersionBinary) 181 } 182 183 // Check if tools are available for download. 184 wantTools, err = u.st.Tools(u.tag.String()) 185 if err != nil { 186 // Not being able to lookup Tools is considered fatal 187 return err 188 } 189 // The worker cannot be stopped while we're downloading 190 // the tools - this means that even if the API is going down 191 // repeatedly (causing the agent to be stopped), as long 192 // as we have got as far as this, we will still be able to 193 // upgrade the agent. 194 err := u.ensureTools(wantTools) 195 if err == nil { 196 return u.newUpgradeReadyError(wantTools.Version) 197 } 198 logger.Errorf("failed to fetch tools from %q: %v", wantTools.URL, err) 199 retry = retryAfter() 200 } 201 } 202 203 func toBinaryVersion(vers version.Number) version.Binary { 204 outVers := version.Current 205 outVers.Number = vers 206 return outVers 207 } 208 209 func (u *Upgrader) toolsAlreadyDownloaded(wantVersion version.Binary) bool { 210 _, err := agenttools.ReadTools(u.dataDir, wantVersion) 211 return err == nil 212 } 213 214 func (u *Upgrader) newUpgradeReadyError(newVersion version.Binary) *UpgradeReadyError { 215 return &UpgradeReadyError{ 216 OldTools: version.Current, 217 NewTools: newVersion, 218 AgentName: u.tag.String(), 219 DataDir: u.dataDir, 220 } 221 } 222 223 func (u *Upgrader) ensureTools(agentTools *coretools.Tools) error { 224 logger.Infof("fetching tools from %q", agentTools.URL) 225 // The reader MUST verify the tools' hash, so there is no 226 // need to validate the peer. We cannot anyway: see http://pad.lv/1261780. 227 resp, err := utils.GetNonValidatingHTTPClient().Get(agentTools.URL) 228 if err != nil { 229 return err 230 } 231 defer resp.Body.Close() 232 if resp.StatusCode != http.StatusOK { 233 return fmt.Errorf("bad HTTP response: %v", resp.Status) 234 } 235 err = agenttools.UnpackTools(u.dataDir, agentTools, resp.Body) 236 if err != nil { 237 return fmt.Errorf("cannot unpack tools: %v", err) 238 } 239 logger.Infof("unpacked tools %s to %s", agentTools.Version, u.dataDir) 240 return nil 241 }