launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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 "net/http" 8 "time" 9 10 "github.com/loggo/loggo" 11 "launchpad.net/errgo/errors" 12 "launchpad.net/tomb" 13 14 "launchpad.net/juju-core/agent" 15 agenttools "launchpad.net/juju-core/agent/tools" 16 "launchpad.net/juju-core/state/api/upgrader" 17 "launchpad.net/juju-core/state/watcher" 18 coretools "launchpad.net/juju-core/tools" 19 "launchpad.net/juju-core/utils" 20 "launchpad.net/juju-core/version" 21 ) 22 23 var mask = errors.Mask 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 // UpgradeReadyError is returned by an Upgrader to report that 32 // an upgrade is ready to be performed and a restart is due. 33 type UpgradeReadyError struct { 34 AgentName string 35 OldTools *coretools.Tools 36 NewTools *coretools.Tools 37 DataDir string 38 } 39 40 func (e *UpgradeReadyError) Error() string { 41 return "must restart: an agent upgrade is available" 42 } 43 44 // ChangeAgentTools does the actual agent upgrade. 45 // It should be called just before an agent exits, so that 46 // it will restart running the new tools. 47 func (e *UpgradeReadyError) ChangeAgentTools() error { 48 tools, err := agenttools.ChangeAgentTools(e.DataDir, e.AgentName, e.NewTools.Version) 49 if err != nil { 50 return mask(err) 51 } 52 logger.Infof("upgraded from %v to %v (%q)", e.OldTools.Version, tools.Version, tools.URL) 53 return nil 54 } 55 56 var logger = loggo.GetLogger("juju.worker.upgrader") 57 58 // Upgrader represents a worker that watches the state for upgrade 59 // requests. 60 type Upgrader struct { 61 tomb tomb.Tomb 62 st *upgrader.State 63 dataDir string 64 tag string 65 } 66 67 // NewUpgrader returns a new upgrader worker. It watches changes to the 68 // current version of the current agent (with the given tag) and tries to 69 // download the tools for any new version into the given data directory. If 70 // an upgrade is needed, the worker will exit with an UpgradeReadyError 71 // holding details of the requested upgrade. The tools will have been 72 // downloaded and unpacked. 73 func NewUpgrader(st *upgrader.State, agentConfig agent.Config) *Upgrader { 74 u := &Upgrader{ 75 st: st, 76 dataDir: agentConfig.DataDir(), 77 tag: agentConfig.Tag(), 78 } 79 go func() { 80 defer u.tomb.Done() 81 u.tomb.Kill(u.loop()) 82 }() 83 return u 84 } 85 86 // Kill implements worker.Worker.Kill. 87 func (u *Upgrader) Kill() { 88 u.tomb.Kill(nil) 89 } 90 91 // Wait implements worker.Worker.Wait. 92 func (u *Upgrader) Wait() error { 93 return u.tomb.Wait() 94 } 95 96 // Stop stops the upgrader and returns any 97 // error it encountered when running. 98 func (u *Upgrader) Stop() error { 99 u.Kill() 100 return u.Wait() 101 } 102 103 func (u *Upgrader) loop() error { 104 currentTools := &coretools.Tools{Version: version.Current} 105 err := u.st.SetVersion(u.tag, currentTools.Version) 106 if err != nil { 107 return mask(err) 108 } 109 versionWatcher, err := u.st.WatchAPIVersion(u.tag) 110 if err != nil { 111 return mask(err) 112 } 113 changes := versionWatcher.Changes() 114 defer watcher.Stop(versionWatcher, &u.tomb) 115 var retry <-chan time.Time 116 // We don't read on the dying channel until we have received the 117 // initial event from the API version watcher, thus ensuring 118 // that we attempt an upgrade even if other workers are dying 119 // all around us. 120 var ( 121 dying <-chan struct{} 122 wantTools *coretools.Tools 123 wantVersion version.Number 124 disableSSLHostnameVerification bool 125 ) 126 for { 127 select { 128 case _, ok := <-changes: 129 if !ok { 130 return watcher.MustErr(versionWatcher) 131 } 132 wantVersion, err = u.st.DesiredVersion(u.tag) 133 if err != nil { 134 return mask(err) 135 } 136 logger.Infof("desired tool version: %v", wantVersion) 137 dying = u.tomb.Dying() 138 case <-retry: 139 case <-dying: 140 return nil 141 } 142 if wantVersion != currentTools.Version.Number { 143 logger.Infof("upgrade requested from %v to %v", currentTools.Version, wantVersion) 144 // TODO(dimitern) 2013-10-03 bug #1234715 145 // Add a testing HTTPS storage to verify the 146 // disableSSLHostnameVerification behavior here. 147 wantTools, disableSSLHostnameVerification, err = u.st.Tools(u.tag) 148 if err != nil { 149 // Not being able to lookup Tools is considered fatal 150 return mask(err) 151 } 152 // The worker cannot be stopped while we're downloading 153 // the tools - this means that even if the API is going down 154 // repeatedly (causing the agent to be stopped), as long 155 // as we have got as far as this, we will still be able to 156 // upgrade the agent. 157 err := u.ensureTools(wantTools, disableSSLHostnameVerification) 158 if err == nil { 159 return &UpgradeReadyError{ 160 OldTools: currentTools, 161 NewTools: wantTools, 162 AgentName: u.tag, 163 DataDir: u.dataDir, 164 } 165 } 166 logger.Errorf("failed to fetch tools from %q: %v", wantTools.URL, err) 167 retry = retryAfter() 168 } 169 } 170 } 171 172 func (u *Upgrader) ensureTools(agentTools *coretools.Tools, disableSSLHostnameVerification bool) error { 173 if _, err := agenttools.ReadTools(u.dataDir, agentTools.Version); errors.Cause( 174 // Tools have already been downloaded 175 err) == nil { 176 177 return nil 178 } 179 client := http.DefaultClient 180 logger.Infof("fetching tools from %q", agentTools.URL) 181 if disableSSLHostnameVerification { 182 logger.Infof("hostname SSL verification disabled") 183 client = utils.GetNonValidatingHTTPClient() 184 } 185 resp, err := client.Get(agentTools.URL) 186 if err != nil { 187 return mask(err) 188 } 189 defer resp.Body.Close() 190 if resp.StatusCode != http.StatusOK { 191 return errors.Newf("bad HTTP response: %v", resp.Status) 192 } 193 err = agenttools.UnpackTools(u.dataDir, agentTools, resp.Body) 194 if err != nil { 195 return errors.Notef(err, "cannot unpack tools") 196 } 197 return nil 198 }