github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/caasupgrader/upgrader.go (about)

     1  // Copyright 2019 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package caasupgrader
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/names/v5"
    12  	"github.com/juju/version/v2"
    13  	"github.com/juju/worker/v3/catacomb"
    14  
    15  	"github.com/juju/juju/api/agent/agent"
    16  	"github.com/juju/juju/core/arch"
    17  	coreos "github.com/juju/juju/core/os"
    18  	"github.com/juju/juju/core/watcher"
    19  	jujuversion "github.com/juju/juju/version"
    20  	"github.com/juju/juju/worker/gate"
    21  	"github.com/juju/juju/worker/upgrader"
    22  )
    23  
    24  var logger = loggo.GetLogger("juju.worker.caasupgrader")
    25  
    26  // Upgrader represents a worker that watches the state for upgrade
    27  // requests for a given CAAS agent.
    28  type Upgrader struct {
    29  	catacomb catacomb.Catacomb
    30  
    31  	upgraderClient   UpgraderClient
    32  	operatorUpgrader CAASOperatorUpgrader
    33  	tag              names.Tag
    34  	config           Config
    35  }
    36  
    37  // UpgraderClient provides the facade methods used by the worker.
    38  type UpgraderClient interface {
    39  	DesiredVersion(tag string) (version.Number, error)
    40  	SetVersion(tag string, v version.Binary) error
    41  	WatchAPIVersion(agentTag string) (watcher.NotifyWatcher, error)
    42  }
    43  
    44  type CAASOperatorUpgrader interface {
    45  	Upgrade(agentTag string, v version.Number) error
    46  }
    47  
    48  // Config contains the items the worker needs to start.
    49  type Config struct {
    50  	UpgraderClient              UpgraderClient
    51  	CAASOperatorUpgrader        CAASOperatorUpgrader
    52  	AgentTag                    names.Tag
    53  	OrigAgentVersion            version.Number
    54  	UpgradeStepsWaiter          gate.Waiter
    55  	InitialUpgradeCheckComplete gate.Unlocker
    56  }
    57  
    58  // NewUpgrader returns a new upgrader worker. It watches changes to the
    59  // current version of a CAAS agent. If an upgrade is needed, the worker
    60  // updates the docker image version for the specified agent.
    61  // TODO(caas) - support HA controllers
    62  func NewUpgrader(config Config) (*Upgrader, error) {
    63  	u := &Upgrader{
    64  		upgraderClient:   config.UpgraderClient,
    65  		operatorUpgrader: config.CAASOperatorUpgrader,
    66  		tag:              config.AgentTag,
    67  		config:           config,
    68  	}
    69  	err := catacomb.Invoke(catacomb.Plan{
    70  		Site: &u.catacomb,
    71  		Work: u.loop,
    72  	})
    73  	if err != nil {
    74  		return nil, errors.Trace(err)
    75  	}
    76  	return u, nil
    77  }
    78  
    79  // Kill implements worker.Worker.Kill.
    80  func (u *Upgrader) Kill() {
    81  	u.catacomb.Kill(nil)
    82  }
    83  
    84  // Wait implements worker.Worker.Wait.
    85  func (u *Upgrader) Wait() error {
    86  	return u.catacomb.Wait()
    87  }
    88  
    89  func (u *Upgrader) loop() error {
    90  	// Only controllers and sidecar unit agents set their version here - application agents do it in the main agent worker loop.
    91  	hostOSType := coreos.HostOSTypeName()
    92  	if agent.IsAllowedControllerTag(u.tag.Kind()) || u.tag.Kind() == names.UnitTagKind {
    93  		if err := u.upgraderClient.SetVersion(u.tag.String(), toBinaryVersion(jujuversion.Current, hostOSType)); err != nil {
    94  			return errors.Annotatef(err, "cannot set agent version for %q", u.tag.String())
    95  		}
    96  	}
    97  
    98  	// We don't read on the dying channel until we have received the
    99  	// initial event from the API version watcher, thus ensuring
   100  	// that we attempt an upgrade even if other workers are dying
   101  	// all around us. Similarly, we don't want to bind the watcher
   102  	// to the catacomb's lifetime (yet!) lest we wait forever for a
   103  	// stopped watcher.
   104  	//
   105  	// However, that absolutely depends on versionWatcher's guaranteed
   106  	// initial event, and we should assume that it'll break its contract
   107  	// sometime. So we allow the watcher to wait patiently for the event
   108  	// for a full minute; but after that we proceed regardless.
   109  	versionWatcher, err := u.upgraderClient.WatchAPIVersion(u.tag.String())
   110  	if err != nil {
   111  		return errors.Annotate(err, "getting upgrader facade watch api version client")
   112  	}
   113  	logger.Infof("abort check blocked until version event received")
   114  	// TODO(fwereade): 2016-03-17 lp:1558657
   115  	mustProceed := time.After(time.Minute)
   116  	var dying <-chan struct{}
   117  	allowDying := func() {
   118  		if dying == nil {
   119  			logger.Infof("unblocking abort check")
   120  			mustProceed = nil
   121  			dying = u.catacomb.Dying()
   122  			if err := u.catacomb.Add(versionWatcher); err != nil {
   123  				u.catacomb.Kill(err)
   124  			}
   125  		}
   126  	}
   127  
   128  	logger.Debugf("current agent binary version: %v", jujuversion.Current)
   129  	for {
   130  		select {
   131  		// NOTE: dying starts out nil, so it can't be chosen
   132  		// first time round the loop. However...
   133  		case <-dying:
   134  			return u.catacomb.ErrDying()
   135  		// ...*every* other case *must* allowDying(), before doing anything
   136  		// else, lest an error cause us to leak versionWatcher.
   137  		case <-mustProceed:
   138  			logger.Infof("version event not received after one minute")
   139  			allowDying()
   140  		case _, ok := <-versionWatcher.Changes():
   141  			allowDying()
   142  			if !ok {
   143  				return errors.New("version watcher closed")
   144  			}
   145  		}
   146  
   147  		wantVersion, err := u.upgraderClient.DesiredVersion(u.tag.String())
   148  		if err != nil {
   149  			return err
   150  		}
   151  
   152  		if wantVersion == jujuversion.Current {
   153  			u.config.InitialUpgradeCheckComplete.Unlock()
   154  			continue
   155  		} else if !upgrader.AllowedTargetVersion(jujuversion.Current, wantVersion) {
   156  			logger.Warningf("desired agent binary version: %s is older than current %s, refusing to downgrade",
   157  				wantVersion, jujuversion.Current)
   158  			u.config.InitialUpgradeCheckComplete.Unlock()
   159  			continue
   160  		}
   161  		direction := "upgrade"
   162  		if wantVersion.Compare(jujuversion.Current) == -1 {
   163  			direction = "downgrade"
   164  		}
   165  		logger.Debugf("%s requested for %v from %v to %v", direction, u.tag, jujuversion.Current, wantVersion)
   166  		err = u.operatorUpgrader.Upgrade(u.tag.String(), wantVersion)
   167  		if err != nil {
   168  			return errors.Annotatef(
   169  				err, "requesting upgrade for %v from %v to %v", u.tag.String(), jujuversion.Current, wantVersion)
   170  		}
   171  	}
   172  }
   173  
   174  func toBinaryVersion(vers version.Number, osType string) version.Binary {
   175  	outVers := version.Binary{
   176  		Number:  vers,
   177  		Arch:    arch.HostArch(),
   178  		Release: osType,
   179  	}
   180  	return outVers
   181  }