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