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