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  }