github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/names"
    14  	"github.com/juju/utils"
    15  	"launchpad.net/tomb"
    16  
    17  	"github.com/juju/juju/agent"
    18  	agenttools "github.com/juju/juju/agent/tools"
    19  	"github.com/juju/juju/api/upgrader"
    20  	"github.com/juju/juju/state/watcher"
    21  	coretools "github.com/juju/juju/tools"
    22  	"github.com/juju/juju/version"
    23  )
    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  var logger = loggo.GetLogger("juju.worker.upgrader")
    32  
    33  // Upgrader represents a worker that watches the state for upgrade
    34  // requests.
    35  type Upgrader struct {
    36  	tomb                   tomb.Tomb
    37  	st                     *upgrader.State
    38  	dataDir                string
    39  	tag                    names.Tag
    40  	origAgentVersion       version.Number
    41  	areUpgradeStepsRunning func() bool
    42  	agentUpgradeComplete   chan struct{}
    43  }
    44  
    45  // NewAgentUpgrader returns a new upgrader worker. It watches changes to the
    46  // current version of the current agent (with the given tag) and tries to
    47  // download the tools for any new version into the given data directory.  If
    48  // an upgrade is needed, the worker will exit with an UpgradeReadyError
    49  // holding details of the requested upgrade. The tools will have been
    50  // downloaded and unpacked.
    51  func NewAgentUpgrader(
    52  	st *upgrader.State,
    53  	agentConfig agent.Config,
    54  	origAgentVersion version.Number,
    55  	areUpgradeStepsRunning func() bool,
    56  	agentUpgradeComplete chan struct{},
    57  ) *Upgrader {
    58  	u := &Upgrader{
    59  		st:                     st,
    60  		dataDir:                agentConfig.DataDir(),
    61  		tag:                    agentConfig.Tag(),
    62  		origAgentVersion:       origAgentVersion,
    63  		areUpgradeStepsRunning: areUpgradeStepsRunning,
    64  		agentUpgradeComplete:   agentUpgradeComplete,
    65  	}
    66  	go func() {
    67  		defer u.tomb.Done()
    68  		u.tomb.Kill(u.loop())
    69  	}()
    70  	return u
    71  }
    72  
    73  // Kill implements worker.Worker.Kill.
    74  func (u *Upgrader) Kill() {
    75  	u.tomb.Kill(nil)
    76  }
    77  
    78  // Wait implements worker.Worker.Wait.
    79  func (u *Upgrader) Wait() error {
    80  	return u.tomb.Wait()
    81  }
    82  
    83  // Stop stops the upgrader and returns any
    84  // error it encountered when running.
    85  func (u *Upgrader) Stop() error {
    86  	u.Kill()
    87  	return u.Wait()
    88  }
    89  
    90  // allowedTargetVersion checks if targetVersion is too different from
    91  // curVersion to allow a downgrade.
    92  func allowedTargetVersion(
    93  	origAgentVersion version.Number,
    94  	curVersion version.Number,
    95  	upgradeRunning bool,
    96  	targetVersion version.Number,
    97  ) bool {
    98  	if upgradeRunning && targetVersion == origAgentVersion {
    99  		return true
   100  	}
   101  	if targetVersion.Major < curVersion.Major {
   102  		return false
   103  	}
   104  	if targetVersion.Major == curVersion.Major && targetVersion.Minor < curVersion.Minor {
   105  		return false
   106  	}
   107  	return true
   108  }
   109  
   110  // closeChannel can be called multiple times to
   111  // close the channel without panicing.
   112  func closeChannel(ch chan struct{}) {
   113  	select {
   114  	case <-ch:
   115  		return
   116  	default:
   117  		close(ch)
   118  	}
   119  }
   120  
   121  func (u *Upgrader) loop() error {
   122  	// Start by reporting current tools (which includes arch/series, and is
   123  	// used by the state server in communicating the desired version below).
   124  	if err := u.st.SetVersion(u.tag.String(), version.Current); err != nil {
   125  		return errors.Annotate(err, "cannot set agent version")
   126  	}
   127  	versionWatcher, err := u.st.WatchAPIVersion(u.tag.String())
   128  	if err != nil {
   129  		return err
   130  	}
   131  	changes := versionWatcher.Changes()
   132  	defer watcher.Stop(versionWatcher, &u.tomb)
   133  	var retry <-chan time.Time
   134  
   135  	// We don't read on the dying channel until we have received the
   136  	// initial event from the API version watcher, thus ensuring
   137  	// that we attempt an upgrade even if other workers are dying
   138  	// all around us.
   139  	var (
   140  		dying       <-chan struct{}
   141  		wantTools   *coretools.Tools
   142  		wantVersion version.Number
   143  	)
   144  	for {
   145  		select {
   146  		case _, ok := <-changes:
   147  			if !ok {
   148  				return watcher.EnsureErr(versionWatcher)
   149  			}
   150  			wantVersion, err = u.st.DesiredVersion(u.tag.String())
   151  			if err != nil {
   152  				return err
   153  			}
   154  			logger.Infof("desired tool version: %v", wantVersion)
   155  			dying = u.tomb.Dying()
   156  		case <-retry:
   157  		case <-dying:
   158  			return nil
   159  		}
   160  		if wantVersion == version.Current.Number {
   161  			closeChannel(u.agentUpgradeComplete)
   162  			continue
   163  		} else if !allowedTargetVersion(u.origAgentVersion, version.Current.Number,
   164  			u.areUpgradeStepsRunning(), wantVersion) {
   165  			// See also bug #1299802 where when upgrading from
   166  			// 1.16 to 1.18 there is a race condition that can
   167  			// cause the unit agent to upgrade, and then want to
   168  			// downgrade when its associate machine agent has not
   169  			// finished upgrading.
   170  			logger.Infof("desired tool version: %s is older than current %s, refusing to downgrade",
   171  				wantVersion, version.Current)
   172  			closeChannel(u.agentUpgradeComplete)
   173  			continue
   174  		}
   175  		logger.Infof("upgrade requested from %v to %v", version.Current, wantVersion)
   176  
   177  		// Check if tools have already been downloaded.
   178  		wantVersionBinary := toBinaryVersion(wantVersion)
   179  		if u.toolsAlreadyDownloaded(wantVersionBinary) {
   180  			return u.newUpgradeReadyError(wantVersionBinary)
   181  		}
   182  
   183  		// Check if tools are available for download.
   184  		wantTools, err = u.st.Tools(u.tag.String())
   185  		if err != nil {
   186  			// Not being able to lookup Tools is considered fatal
   187  			return err
   188  		}
   189  		// The worker cannot be stopped while we're downloading
   190  		// the tools - this means that even if the API is going down
   191  		// repeatedly (causing the agent to be stopped), as long
   192  		// as we have got as far as this, we will still be able to
   193  		// upgrade the agent.
   194  		err := u.ensureTools(wantTools)
   195  		if err == nil {
   196  			return u.newUpgradeReadyError(wantTools.Version)
   197  		}
   198  		logger.Errorf("failed to fetch tools from %q: %v", wantTools.URL, err)
   199  		retry = retryAfter()
   200  	}
   201  }
   202  
   203  func toBinaryVersion(vers version.Number) version.Binary {
   204  	outVers := version.Current
   205  	outVers.Number = vers
   206  	return outVers
   207  }
   208  
   209  func (u *Upgrader) toolsAlreadyDownloaded(wantVersion version.Binary) bool {
   210  	_, err := agenttools.ReadTools(u.dataDir, wantVersion)
   211  	return err == nil
   212  }
   213  
   214  func (u *Upgrader) newUpgradeReadyError(newVersion version.Binary) *UpgradeReadyError {
   215  	return &UpgradeReadyError{
   216  		OldTools:  version.Current,
   217  		NewTools:  newVersion,
   218  		AgentName: u.tag.String(),
   219  		DataDir:   u.dataDir,
   220  	}
   221  }
   222  
   223  func (u *Upgrader) ensureTools(agentTools *coretools.Tools) error {
   224  	logger.Infof("fetching tools from %q", agentTools.URL)
   225  	// The reader MUST verify the tools' hash, so there is no
   226  	// need to validate the peer. We cannot anyway: see http://pad.lv/1261780.
   227  	resp, err := utils.GetNonValidatingHTTPClient().Get(agentTools.URL)
   228  	if err != nil {
   229  		return err
   230  	}
   231  	defer resp.Body.Close()
   232  	if resp.StatusCode != http.StatusOK {
   233  		return fmt.Errorf("bad HTTP response: %v", resp.Status)
   234  	}
   235  	err = agenttools.UnpackTools(u.dataDir, agentTools, resp.Body)
   236  	if err != nil {
   237  		return fmt.Errorf("cannot unpack tools: %v", err)
   238  	}
   239  	logger.Infof("unpacked tools %s to %s", agentTools.Version, u.dataDir)
   240  	return nil
   241  }