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