github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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 "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 // 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 func (u *Upgrader) loop() error { 77 currentTools := &coretools.Tools{Version: version.Current} 78 err := u.st.SetVersion(u.tag, currentTools.Version) 79 if err != nil { 80 return err 81 } 82 versionWatcher, err := u.st.WatchAPIVersion(u.tag) 83 if err != nil { 84 return err 85 } 86 changes := versionWatcher.Changes() 87 defer watcher.Stop(versionWatcher, &u.tomb) 88 var retry <-chan time.Time 89 // We don't read on the dying channel until we have received the 90 // initial event from the API version watcher, thus ensuring 91 // that we attempt an upgrade even if other workers are dying 92 // all around us. 93 var ( 94 dying <-chan struct{} 95 wantTools *coretools.Tools 96 wantVersion version.Number 97 disableSSLHostnameVerification bool 98 ) 99 for { 100 select { 101 case _, ok := <-changes: 102 if !ok { 103 return watcher.MustErr(versionWatcher) 104 } 105 wantVersion, err = u.st.DesiredVersion(u.tag) 106 if err != nil { 107 return err 108 } 109 logger.Infof("desired tool version: %v", wantVersion) 110 dying = u.tomb.Dying() 111 case <-retry: 112 case <-dying: 113 return nil 114 } 115 if wantVersion != currentTools.Version.Number { 116 logger.Infof("upgrade requested from %v to %v", currentTools.Version, wantVersion) 117 // TODO(dimitern) 2013-10-03 bug #1234715 118 // Add a testing HTTPS storage to verify the 119 // disableSSLHostnameVerification behavior here. 120 wantTools, disableSSLHostnameVerification, err = u.st.Tools(u.tag) 121 if err != nil { 122 // Not being able to lookup Tools is considered fatal 123 return err 124 } 125 // The worker cannot be stopped while we're downloading 126 // the tools - this means that even if the API is going down 127 // repeatedly (causing the agent to be stopped), as long 128 // as we have got as far as this, we will still be able to 129 // upgrade the agent. 130 err := u.ensureTools(wantTools, disableSSLHostnameVerification) 131 if err == nil { 132 return &UpgradeReadyError{ 133 OldTools: version.Current, 134 NewTools: wantTools.Version, 135 AgentName: u.tag, 136 DataDir: u.dataDir, 137 } 138 } 139 logger.Errorf("failed to fetch tools from %q: %v", wantTools.URL, err) 140 retry = retryAfter() 141 } 142 } 143 } 144 145 func (u *Upgrader) ensureTools(agentTools *coretools.Tools, disableSSLHostnameVerification bool) error { 146 if _, err := agenttools.ReadTools(u.dataDir, agentTools.Version); err == nil { 147 // Tools have already been downloaded 148 return nil 149 } 150 client := http.DefaultClient 151 logger.Infof("fetching tools from %q", agentTools.URL) 152 if disableSSLHostnameVerification { 153 logger.Infof("hostname SSL verification disabled") 154 client = utils.GetNonValidatingHTTPClient() 155 } 156 resp, err := client.Get(agentTools.URL) 157 if err != nil { 158 return err 159 } 160 defer resp.Body.Close() 161 if resp.StatusCode != http.StatusOK { 162 return fmt.Errorf("bad HTTP response: %v", resp.Status) 163 } 164 err = agenttools.UnpackTools(u.dataDir, agentTools, resp.Body) 165 if err != nil { 166 return fmt.Errorf("cannot unpack tools: %v", err) 167 } 168 logger.Infof("unpacked tools %s to %s", agentTools.Version, u.dataDir) 169 return nil 170 }