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  }