func (s *suite) TestDownloadError(c *C) { testing.Server.Response(404, nil, nil) d := downloader.New(s.URL("/archive.tgz"), c.MkDir()) status := <-d.Done() c.Assert(status.File, IsNil) c.Assert(status.Err, ErrorMatches, `cannot download ".*": bad http response: 404 Not Found`) }
func (s *suite) TestStopDownload(c *C) { tmp := c.MkDir() d := downloader.New(s.URL("/x.tgz"), tmp) d.Stop() select { case status := <-d.Done(): c.Fatalf("received status %#v after stop", status) case <-time.After(testing.ShortWait): } infos, err := ioutil.ReadDir(tmp) c.Assert(err, IsNil) c.Assert(infos, HasLen, 0) }
func (s *suite) TestDownload(c *C) { tmp := c.MkDir() testing.Server.Response(200, nil, []byte("archive")) d := downloader.New(s.URL("/archive.tgz"), tmp) status := <-d.Done() c.Assert(status.Err, IsNil) c.Assert(status.File, NotNil) defer os.Remove(status.File.Name()) defer status.File.Close() dir, _ := filepath.Split(status.File.Name()) c.Assert(filepath.Clean(dir), Equals, tmp) assertFileContents(c, status.File, "archive") }
// download fetches the supplied charm and checks that it has the correct sha256 // hash, then copies it into the directory. If a value is received on abort, the // download will be stopped. func (d *BundlesDir) download(sch *state.Charm, abort <-chan struct{}) (err error) { defer trivial.ErrorContextf(&err, "failed to download charm %q from %q", sch.URL(), sch.BundleURL()) dir := d.downloadsPath() if err := os.MkdirAll(dir, 0755); err != nil { return err } burl := sch.BundleURL().String() log.Printf("worker/uniter/charm: downloading %s from %s", sch.URL(), burl) dl := downloader.New(burl, dir) defer dl.Stop() for { select { case <-abort: log.Printf("worker/uniter/charm: download aborted") return fmt.Errorf("aborted") case st := <-dl.Done(): if st.Err != nil { return st.Err } log.Printf("worker/uniter/charm: download complete") defer st.File.Close() hash := sha256.New() if _, err = io.Copy(hash, st.File); err != nil { return err } actualSha256 := hex.EncodeToString(hash.Sum(nil)) if actualSha256 != sch.BundleSha256() { return fmt.Errorf( "expected sha256 %q, got %q", sch.BundleSha256(), actualSha256, ) } log.Printf("worker/uniter/charm: download verified") if err := os.MkdirAll(d.path, 0755); err != nil { return err } return os.Rename(st.File.Name(), d.bundlePath(sch)) } } panic("unreachable") }
func (u *Upgrader) run() error { // Let the state know the version that is currently running. currentTools, err := tools.ReadTools(u.dataDir, version.Current) if err != nil { // Don't abort everything because we can't find the tools directory. // The problem should sort itself out as we will immediately // download some more tools and upgrade. log.Warningf("upgrader cannot read current tools: %v", err) currentTools = &tools.Tools{ Binary: version.Current, } } err = u.agentState.SetAgentTools(currentTools) if err != nil { return err } // TODO(fwereade): this whole package should be ignorant of environs, // so it shouldn't be watching environ config (and it shouldn't be // looking in storage): we should be able to find out what to download // from state, exactly as we do for charms. w := u.st.WatchEnvironConfig() defer watcher.Stop(w, &u.tomb) // Rather than using worker.WaitForEnviron, invalid environments are // managed explicitly so that all configuration changes are observed // by the loop below. var environ environs.Environ // TODO(rog) retry downloads when they fail. var ( download *downloader.Download downloadTools *tools.Tools downloadDone <-chan downloader.Status ) // If we're killed early on (probably as a result of some other // task dying) we allow ourselves some time to try to connect to // the state and download a new version. We return to normal // undelayed behaviour when: // 1) We find there's no upgrade to do. // 2) A download fails. tomb := delayedTomb(&u.tomb, upgraderKillDelay) noDelay := func() { if tomb != &u.tomb { tomb.Kill(nil) tomb = &u.tomb } } for { // We wait for the tools to change while we're downloading // so that if something goes wrong (for instance a bad URL // hangs up) another change to the proposed tools can // potentially fix things. select { case cfg, ok := <-w.Changes(): if !ok { return watcher.MustErr(w) } var err error if environ == nil { environ, err = environs.New(cfg) if err != nil { log.Errorf("upgrader loaded invalid initial environment configuration: %v", err) break } } else { err = environ.SetConfig(cfg) if err != nil { log.Warningf("upgrader loaded invalid environment configuration: %v", err) // continue on, because the version number is still significant. } } proposed, ok := cfg.AgentVersion() if !ok { // This shouldn't be possible; but if it happens it's no reason // to kill this task. Just wait for the config to change again. continue } if download != nil { // There's a download in progress, stop it if we need to. if downloadTools.Number == proposed { // We are already downloading the requested tools. break } // Tools changed. We need to stop and restart. download.Stop() download, downloadTools, downloadDone = nil, nil, nil } // TODO: major version upgrades. if proposed.Major != version.Current.Major { log.Errorf("major version upgrades are not supported yet") noDelay() break } if proposed == version.Current.Number { noDelay() break } required := version.Binary{ Number: proposed, Series: version.Current.Series, Arch: version.Current.Arch, } if tools, err := tools.ReadTools(u.dataDir, required); err == nil { // The exact tools have already been downloaded, so use them. return u.upgradeReady(currentTools, tools) } tools, err := environs.FindExactTools(environ, required) if err != nil { log.Errorf("upgrader error finding tools for %v: %v", required, err) if !errors.IsNotFoundError(err) { return err } noDelay() // TODO(rog): poll until tools become available. break } log.Infof("upgrader downloading %q", tools.URL) download = downloader.New(tools.URL, "") downloadTools = tools downloadDone = download.Done() case status := <-downloadDone: newTools := downloadTools download, downloadTools, downloadDone = nil, nil, nil if status.Err != nil { log.Errorf("upgrader download of %v failed: %v", newTools.Binary, status.Err) noDelay() break } err := tools.UnpackTools(u.dataDir, newTools, status.File) status.File.Close() if err := os.Remove(status.File.Name()); err != nil { log.Warningf("upgrader cannot remove temporary download file: %v", err) } if err != nil { log.Errorf("upgrader cannot unpack %v tools: %v", newTools.Binary, err) noDelay() break } return u.upgradeReady(currentTools, newTools) case <-tomb.Dying(): if download != nil { return fmt.Errorf("upgrader aborted download of %q", downloadTools.URL) } return nil } } panic("not reached") }
func (u *Upgrader) run() error { // Let the state know the version that is currently running. currentTools, err := environs.ReadTools(u.dataDir, version.Current) if err != nil { // Don't abort everything because we can't find the tools directory. // The problem should sort itself out as we will immediately // download some more tools and upgrade. log.Printf("cmd/jujud: upgrader cannot read current tools: %v", err) currentTools = &state.Tools{ Binary: version.Current, } } err = u.agentState.SetAgentTools(currentTools) if err != nil { return err } w := u.st.WatchEnvironConfig() defer watcher.Stop(w, &u.tomb) // Rather than using worker.WaitForEnviron, invalid environments are // managed explicitly so that all configuration changes are observed // by the loop below. var environ environs.Environ // TODO(rog) retry downloads when they fail. var ( download *downloader.Download downloadTools *state.Tools downloadDone <-chan downloader.Status ) // If we're killed early on (probably as a result of some other // task dying) we allow ourselves some time to try to connect to // the state and download a new version. We return to normal // undelayed behaviour when: // 1) We find there's no upgrade to do. // 2) A download fails. tomb := delayedTomb(&u.tomb, upgraderKillDelay) noDelay := func() { if tomb != &u.tomb { tomb.Kill(nil) tomb = &u.tomb } } for { // We wait for the tools to change while we're downloading // so that if something goes wrong (for instance a bad URL // hangs up) another change to the proposed tools can // potentially fix things. select { case cfg, ok := <-w.Changes(): if !ok { return watcher.MustErr(w) } var err error if environ == nil { environ, err = environs.New(cfg) if err != nil { log.Printf("cmd/jujud: upgrader loaded invalid initial environment configuration: %v", err) break } } else { err = environ.SetConfig(cfg) if err != nil { log.Printf("cmd/jujud: upgrader loaded invalid environment configuration: %v", err) // continue on, because the version number is still significant. } } vers := cfg.AgentVersion() if download != nil { // There's a download in progress, stop it if we need to. if vers == downloadTools.Number { // We are already downloading the requested tools. break } // Tools changed. We need to stop and restart. download.Stop() download, downloadTools, downloadDone = nil, nil, nil } // Ignore the proposed tools if we're already running the // proposed version. if vers == version.Current.Number { noDelay() break } binary := version.Current binary.Number = vers if tools, err := environs.ReadTools(u.dataDir, binary); err == nil { // The tools have already been downloaded, so use them. return u.upgradeReady(currentTools, tools) } flags := environs.CompatVersion if cfg.Development() { flags |= environs.DevVersion } tools, err := environs.FindTools(environ, binary, flags) if err != nil { log.Printf("cmd/jujud: upgrader error finding tools for %v: %v", binary, err) noDelay() // TODO(rog): poll until tools become available. break } if tools.Binary != binary { if tools.Number == version.Current.Number { // TODO(rog): poll until tools become available. log.Printf("cmd/jujud: upgrader: version %v requested but found only current version: %v", binary, tools.Number) noDelay() break } log.Printf("cmd/jujud: upgrader cannot find exact tools match for %s; using %s instead", binary, tools.Binary) } log.Printf("cmd/jujud: upgrader downloading %q", tools.URL) download = downloader.New(tools.URL, "") downloadTools = tools downloadDone = download.Done() case status := <-downloadDone: tools := downloadTools download, downloadTools, downloadDone = nil, nil, nil if status.Err != nil { log.Printf("cmd/jujud: upgrader download of %v failed: %v", tools.Binary, status.Err) noDelay() break } err := environs.UnpackTools(u.dataDir, tools, status.File) status.File.Close() if err := os.Remove(status.File.Name()); err != nil { log.Printf("cmd/jujud: upgrader cannot remove temporary download file: %v", err) } if err != nil { log.Printf("cmd/jujud: upgrader cannot unpack %v tools: %v", tools.Binary, err) noDelay() break } return u.upgradeReady(currentTools, tools) case <-tomb.Dying(): if download != nil { return fmt.Errorf("upgrader aborted download of %q", downloadTools.URL) } return nil } } panic("not reached") }