launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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  	"net/http"
     8  	"time"
     9  
    10  	"github.com/loggo/loggo"
    11  	"launchpad.net/errgo/errors"
    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  var mask = errors.Mask
    24  
    25  // retryAfter returns a channel that receives a value
    26  // when a failed download should be retried.
    27  var retryAfter = func() <-chan time.Time {
    28  	return time.After(5 * time.Second)
    29  }
    30  
    31  // UpgradeReadyError is returned by an Upgrader to report that
    32  // an upgrade is ready to be performed and a restart is due.
    33  type UpgradeReadyError struct {
    34  	AgentName string
    35  	OldTools  *coretools.Tools
    36  	NewTools  *coretools.Tools
    37  	DataDir   string
    38  }
    39  
    40  func (e *UpgradeReadyError) Error() string {
    41  	return "must restart: an agent upgrade is available"
    42  }
    43  
    44  // ChangeAgentTools does the actual agent upgrade.
    45  // It should be called just before an agent exits, so that
    46  // it will restart running the new tools.
    47  func (e *UpgradeReadyError) ChangeAgentTools() error {
    48  	tools, err := agenttools.ChangeAgentTools(e.DataDir, e.AgentName, e.NewTools.Version)
    49  	if err != nil {
    50  		return mask(err)
    51  	}
    52  	logger.Infof("upgraded from %v to %v (%q)", e.OldTools.Version, tools.Version, tools.URL)
    53  	return nil
    54  }
    55  
    56  var logger = loggo.GetLogger("juju.worker.upgrader")
    57  
    58  // Upgrader represents a worker that watches the state for upgrade
    59  // requests.
    60  type Upgrader struct {
    61  	tomb    tomb.Tomb
    62  	st      *upgrader.State
    63  	dataDir string
    64  	tag     string
    65  }
    66  
    67  // NewUpgrader returns a new upgrader worker. It watches changes to the
    68  // current version of the current agent (with the given tag) and tries to
    69  // download the tools for any new version into the given data directory.  If
    70  // an upgrade is needed, the worker will exit with an UpgradeReadyError
    71  // holding details of the requested upgrade. The tools will have been
    72  // downloaded and unpacked.
    73  func NewUpgrader(st *upgrader.State, agentConfig agent.Config) *Upgrader {
    74  	u := &Upgrader{
    75  		st:      st,
    76  		dataDir: agentConfig.DataDir(),
    77  		tag:     agentConfig.Tag(),
    78  	}
    79  	go func() {
    80  		defer u.tomb.Done()
    81  		u.tomb.Kill(u.loop())
    82  	}()
    83  	return u
    84  }
    85  
    86  // Kill implements worker.Worker.Kill.
    87  func (u *Upgrader) Kill() {
    88  	u.tomb.Kill(nil)
    89  }
    90  
    91  // Wait implements worker.Worker.Wait.
    92  func (u *Upgrader) Wait() error {
    93  	return u.tomb.Wait()
    94  }
    95  
    96  // Stop stops the upgrader and returns any
    97  // error it encountered when running.
    98  func (u *Upgrader) Stop() error {
    99  	u.Kill()
   100  	return u.Wait()
   101  }
   102  
   103  func (u *Upgrader) loop() error {
   104  	currentTools := &coretools.Tools{Version: version.Current}
   105  	err := u.st.SetVersion(u.tag, currentTools.Version)
   106  	if err != nil {
   107  		return mask(err)
   108  	}
   109  	versionWatcher, err := u.st.WatchAPIVersion(u.tag)
   110  	if err != nil {
   111  		return mask(err)
   112  	}
   113  	changes := versionWatcher.Changes()
   114  	defer watcher.Stop(versionWatcher, &u.tomb)
   115  	var retry <-chan time.Time
   116  	// We don't read on the dying channel until we have received the
   117  	// initial event from the API version watcher, thus ensuring
   118  	// that we attempt an upgrade even if other workers are dying
   119  	// all around us.
   120  	var (
   121  		dying                          <-chan struct{}
   122  		wantTools                      *coretools.Tools
   123  		wantVersion                    version.Number
   124  		disableSSLHostnameVerification bool
   125  	)
   126  	for {
   127  		select {
   128  		case _, ok := <-changes:
   129  			if !ok {
   130  				return watcher.MustErr(versionWatcher)
   131  			}
   132  			wantVersion, err = u.st.DesiredVersion(u.tag)
   133  			if err != nil {
   134  				return mask(err)
   135  			}
   136  			logger.Infof("desired tool version: %v", wantVersion)
   137  			dying = u.tomb.Dying()
   138  		case <-retry:
   139  		case <-dying:
   140  			return nil
   141  		}
   142  		if wantVersion != currentTools.Version.Number {
   143  			logger.Infof("upgrade requested from %v to %v", currentTools.Version, wantVersion)
   144  			// TODO(dimitern) 2013-10-03 bug #1234715
   145  			// Add a testing HTTPS storage to verify the
   146  			// disableSSLHostnameVerification behavior here.
   147  			wantTools, disableSSLHostnameVerification, err = u.st.Tools(u.tag)
   148  			if err != nil {
   149  				// Not being able to lookup Tools is considered fatal
   150  				return mask(err)
   151  			}
   152  			// The worker cannot be stopped while we're downloading
   153  			// the tools - this means that even if the API is going down
   154  			// repeatedly (causing the agent to be stopped), as long
   155  			// as we have got as far as this, we will still be able to
   156  			// upgrade the agent.
   157  			err := u.ensureTools(wantTools, disableSSLHostnameVerification)
   158  			if err == nil {
   159  				return &UpgradeReadyError{
   160  					OldTools:  currentTools,
   161  					NewTools:  wantTools,
   162  					AgentName: u.tag,
   163  					DataDir:   u.dataDir,
   164  				}
   165  			}
   166  			logger.Errorf("failed to fetch tools from %q: %v", wantTools.URL, err)
   167  			retry = retryAfter()
   168  		}
   169  	}
   170  }
   171  
   172  func (u *Upgrader) ensureTools(agentTools *coretools.Tools, disableSSLHostnameVerification bool) error {
   173  	if _, err := agenttools.ReadTools(u.dataDir, agentTools.Version); errors.Cause(
   174  		// Tools have already been downloaded
   175  		err) == nil {
   176  
   177  		return nil
   178  	}
   179  	client := http.DefaultClient
   180  	logger.Infof("fetching tools from %q", agentTools.URL)
   181  	if disableSSLHostnameVerification {
   182  		logger.Infof("hostname SSL verification disabled")
   183  		client = utils.GetNonValidatingHTTPClient()
   184  	}
   185  	resp, err := client.Get(agentTools.URL)
   186  	if err != nil {
   187  		return mask(err)
   188  	}
   189  	defer resp.Body.Close()
   190  	if resp.StatusCode != http.StatusOK {
   191  		return errors.Newf("bad HTTP response: %v", resp.Status)
   192  	}
   193  	err = agenttools.UnpackTools(u.dataDir, agentTools, resp.Body)
   194  	if err != nil {
   195  		return errors.Notef(err, "cannot unpack tools")
   196  	}
   197  	return nil
   198  }