github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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/utils" 13 "launchpad.net/tomb" 14 15 "github.com/juju/juju/agent" 16 agenttools "github.com/juju/juju/agent/tools" 17 "github.com/juju/juju/state/api/upgrader" 18 "github.com/juju/juju/state/watcher" 19 coretools "github.com/juju/juju/tools" 20 "github.com/juju/juju/version" 21 ) 22 23 // retryAfter returns a channel that receives a value 24 // when a failed download should be retried. 25 var retryAfter = func() <-chan time.Time { 26 return time.After(5 * time.Second) 27 } 28 29 var logger = loggo.GetLogger("juju.worker.upgrader") 30 31 // Upgrader represents a worker that watches the state for upgrade 32 // requests. 33 type Upgrader struct { 34 tomb tomb.Tomb 35 st *upgrader.State 36 dataDir string 37 tag string 38 } 39 40 // NewUpgrader returns a new upgrader worker. It watches changes to the 41 // current version of the current agent (with the given tag) and tries to 42 // download the tools for any new version into the given data directory. If 43 // an upgrade is needed, the worker will exit with an UpgradeReadyError 44 // holding details of the requested upgrade. The tools will have been 45 // downloaded and unpacked. 46 func NewUpgrader(st *upgrader.State, agentConfig agent.Config) *Upgrader { 47 u := &Upgrader{ 48 st: st, 49 dataDir: agentConfig.DataDir(), 50 tag: agentConfig.Tag(), 51 } 52 go func() { 53 defer u.tomb.Done() 54 u.tomb.Kill(u.loop()) 55 }() 56 return u 57 } 58 59 // Kill implements worker.Worker.Kill. 60 func (u *Upgrader) Kill() { 61 u.tomb.Kill(nil) 62 } 63 64 // Wait implements worker.Worker.Wait. 65 func (u *Upgrader) Wait() error { 66 return u.tomb.Wait() 67 } 68 69 // Stop stops the upgrader and returns any 70 // error it encountered when running. 71 func (u *Upgrader) Stop() error { 72 u.Kill() 73 return u.Wait() 74 } 75 76 // allowedTargetVersion checks if targetVersion is too different from 77 // curVersion to allow a downgrade. 78 func allowedTargetVersion(curVersion, targetVersion version.Number) bool { 79 if targetVersion.Major < curVersion.Major { 80 return false 81 } 82 if targetVersion.Major == curVersion.Major && targetVersion.Minor < curVersion.Minor { 83 return false 84 } 85 return true 86 } 87 88 func (u *Upgrader) loop() error { 89 currentTools := &coretools.Tools{Version: version.Current} 90 err := u.st.SetVersion(u.tag, currentTools.Version) 91 if err != nil { 92 return err 93 } 94 versionWatcher, err := u.st.WatchAPIVersion(u.tag) 95 if err != nil { 96 return err 97 } 98 changes := versionWatcher.Changes() 99 defer watcher.Stop(versionWatcher, &u.tomb) 100 var retry <-chan time.Time 101 // We don't read on the dying channel until we have received the 102 // initial event from the API version watcher, thus ensuring 103 // that we attempt an upgrade even if other workers are dying 104 // all around us. 105 var ( 106 dying <-chan struct{} 107 wantTools *coretools.Tools 108 wantVersion version.Number 109 hostnameVerification utils.SSLHostnameVerification 110 ) 111 for { 112 select { 113 case _, ok := <-changes: 114 if !ok { 115 return watcher.MustErr(versionWatcher) 116 } 117 wantVersion, err = u.st.DesiredVersion(u.tag) 118 if err != nil { 119 return err 120 } 121 logger.Infof("desired tool version: %v", wantVersion) 122 dying = u.tomb.Dying() 123 case <-retry: 124 case <-dying: 125 return nil 126 } 127 if wantVersion == currentTools.Version.Number { 128 continue 129 } else if !allowedTargetVersion(version.Current.Number, wantVersion) { 130 // See also bug #1299802 where when upgrading from 131 // 1.16 to 1.18 there is a race condition that can 132 // cause the unit agent to upgrade, and then want to 133 // downgrade when its associate machine agent has not 134 // finished upgrading. 135 logger.Infof("desired tool version: %s is older than current %s, refusing to downgrade", 136 wantVersion, version.Current) 137 continue 138 } 139 logger.Infof("upgrade requested from %v to %v", currentTools.Version, wantVersion) 140 // TODO(dimitern) 2013-10-03 bug #1234715 141 // Add a testing HTTPS storage to verify the 142 // disableSSLHostnameVerification behavior here. 143 wantTools, hostnameVerification, err = u.st.Tools(u.tag) 144 if err != nil { 145 // Not being able to lookup Tools is considered fatal 146 return err 147 } 148 // The worker cannot be stopped while we're downloading 149 // the tools - this means that even if the API is going down 150 // repeatedly (causing the agent to be stopped), as long 151 // as we have got as far as this, we will still be able to 152 // upgrade the agent. 153 err := u.ensureTools(wantTools, hostnameVerification) 154 if err == nil { 155 return &UpgradeReadyError{ 156 OldTools: version.Current, 157 NewTools: wantTools.Version, 158 AgentName: u.tag, 159 DataDir: u.dataDir, 160 } 161 } 162 logger.Errorf("failed to fetch tools from %q: %v", wantTools.URL, err) 163 retry = retryAfter() 164 } 165 } 166 167 func (u *Upgrader) ensureTools(agentTools *coretools.Tools, hostnameVerification utils.SSLHostnameVerification) error { 168 if _, err := agenttools.ReadTools(u.dataDir, agentTools.Version); err == nil { 169 // Tools have already been downloaded 170 return nil 171 } 172 logger.Infof("fetching tools from %q", agentTools.URL) 173 client := utils.GetHTTPClient(hostnameVerification) 174 resp, err := client.Get(agentTools.URL) 175 if err != nil { 176 return err 177 } 178 defer resp.Body.Close() 179 if resp.StatusCode != http.StatusOK { 180 return fmt.Errorf("bad HTTP response: %v", resp.Status) 181 } 182 err = agenttools.UnpackTools(u.dataDir, agentTools, resp.Body) 183 if err != nil { 184 return fmt.Errorf("cannot unpack tools: %v", err) 185 } 186 logger.Infof("unpacked tools %s to %s", agentTools.Version, u.dataDir) 187 return nil 188 }