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  }