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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package environupgrader
     5  
     6  import (
     7  	stdcontext "context"
     8  	"fmt"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names/v5"
    12  	"github.com/juju/worker/v3"
    13  	"github.com/juju/worker/v3/catacomb"
    14  
    15  	"github.com/juju/juju/core/status"
    16  	"github.com/juju/juju/core/watcher"
    17  	"github.com/juju/juju/environs"
    18  	"github.com/juju/juju/rpc/params"
    19  	jujuworker "github.com/juju/juju/worker"
    20  	"github.com/juju/juju/worker/common"
    21  	"github.com/juju/juju/worker/gate"
    22  	"github.com/juju/juju/wrench"
    23  )
    24  
    25  // Logger is here to stop the desire of creating a package level Logger.
    26  // Don't do this, instead use the one passed as manifold config.
    27  type logger interface{}
    28  
    29  var _ logger = struct{}{}
    30  
    31  // ErrModelRemoved indicates that this worker was operating on the model that is no longer found.
    32  var ErrModelRemoved = errors.New("model has been removed")
    33  
    34  // Facade exposes capabilities required by the worker.
    35  type Facade interface {
    36  	ModelEnvironVersion(tag names.ModelTag) (int, error)
    37  	ModelTargetEnvironVersion(tag names.ModelTag) (int, error)
    38  	SetModelEnvironVersion(tag names.ModelTag, v int) error
    39  	SetModelStatus(names.ModelTag, status.Status, string, map[string]interface{}) error
    40  	WatchModelEnvironVersion(tag names.ModelTag) (watcher.NotifyWatcher, error)
    41  }
    42  
    43  // Config holds the configuration and dependencies for a worker.
    44  type Config struct {
    45  	// Facade holds the API facade used by this worker for getting,
    46  	// setting and watching the model's environ version.
    47  	Facade Facade
    48  
    49  	// GateUnlocker holds a gate.Unlocker that the worker must call
    50  	// after the model has been successfully upgraded.
    51  	GateUnlocker gate.Unlocker
    52  
    53  	// ControllerTag holds the tag of the controller that runs this
    54  	// worker.
    55  	ControllerTag names.ControllerTag
    56  
    57  	// ModelTag holds the tag of the model to which this worker is
    58  	// scoped.
    59  	ModelTag names.ModelTag
    60  
    61  	// Environ holds the Environ used to run upgrade steps, or nil
    62  	// if the worker should wait for upgrade steps to be run by
    63  	// another agent.
    64  	Environ environs.Environ
    65  
    66  	// CredentialAPI holds the API facade used to invalidate credential
    67  	// whenever the worker makes cloud calls if credential for this model
    68  	// becomes invalid.
    69  	CredentialAPI common.CredentialAPI
    70  
    71  	Logger Logger
    72  }
    73  
    74  // Validate returns an error if the config cannot be expected
    75  // to drive a functional worker.
    76  func (config Config) Validate() error {
    77  	if config.Facade == nil {
    78  		return errors.NotValidf("nil Facade")
    79  	}
    80  	if config.GateUnlocker == nil {
    81  		return errors.NotValidf("nil GateUnlocker")
    82  	}
    83  	if config.ControllerTag == (names.ControllerTag{}) {
    84  		return errors.NotValidf("empty ControllerTag")
    85  	}
    86  	if config.ModelTag == (names.ModelTag{}) {
    87  		return errors.NotValidf("empty ModelTag")
    88  	}
    89  	if config.CredentialAPI == nil {
    90  		return errors.NotValidf("nil CredentialAPI")
    91  	}
    92  	if config.Logger == nil {
    93  		return errors.NotValidf("nil Logger")
    94  	}
    95  	return nil
    96  }
    97  
    98  // NewWorker returns a worker that ensures that environ/provider schema upgrades
    99  // are run when the model is first loaded by a controller of a new version. The
   100  // worker either runs the upgrades or waits for another controller unit to run
   101  // them, depending on the configuration.
   102  func NewWorker(config Config) (worker.Worker, error) {
   103  	if err := config.Validate(); err != nil {
   104  		return nil, errors.Trace(err)
   105  	}
   106  	targetVersion, err := config.Facade.ModelTargetEnvironVersion(config.ModelTag)
   107  	if err != nil {
   108  		if params.IsCodeNotFound(err) {
   109  			return nil, ErrModelRemoved
   110  		}
   111  		return nil, errors.Trace(err)
   112  	}
   113  	if config.Environ != nil {
   114  		return newUpgradeWorker(config, targetVersion)
   115  	}
   116  	return newWaitWorker(config, targetVersion)
   117  }
   118  
   119  // newWaitWorker returns a worker that waits for the controller leader to run
   120  // the upgrade steps and update the model's environ version, and then unlocks
   121  // the gate.
   122  func newWaitWorker(config Config, targetVersion int) (worker.Worker, error) {
   123  	watcher, err := config.Facade.WatchModelEnvironVersion(config.ModelTag)
   124  	if err != nil {
   125  		return nil, errors.Trace(err)
   126  	}
   127  	ww := waitWorker{
   128  		watcher:       watcher,
   129  		facade:        config.Facade,
   130  		modelTag:      config.ModelTag,
   131  		gate:          config.GateUnlocker,
   132  		targetVersion: targetVersion,
   133  	}
   134  	if err := catacomb.Invoke(catacomb.Plan{
   135  		Site: &ww.catacomb,
   136  		Init: []worker.Worker{watcher},
   137  		Work: ww.loop,
   138  	}); err != nil {
   139  		return nil, errors.Trace(err)
   140  	}
   141  	return &ww, nil
   142  }
   143  
   144  type waitWorker struct {
   145  	catacomb      catacomb.Catacomb
   146  	watcher       watcher.NotifyWatcher
   147  	facade        Facade
   148  	modelTag      names.ModelTag
   149  	gate          gate.Unlocker
   150  	targetVersion int
   151  }
   152  
   153  func (ww *waitWorker) Kill() {
   154  	ww.catacomb.Kill(nil)
   155  }
   156  
   157  func (ww *waitWorker) Wait() error {
   158  	return ww.catacomb.Wait()
   159  }
   160  
   161  func (ww *waitWorker) loop() error {
   162  	for {
   163  		select {
   164  		case <-ww.catacomb.Dying():
   165  			return ww.catacomb.ErrDying()
   166  		case _, ok := <-ww.watcher.Changes():
   167  			if !ok {
   168  				return ww.catacomb.ErrDying()
   169  			}
   170  			currentVersion, err := ww.facade.ModelEnvironVersion(ww.modelTag)
   171  			if err != nil {
   172  				if params.IsCodeNotFound(err) {
   173  					return ErrModelRemoved
   174  				}
   175  				return errors.Trace(err)
   176  			}
   177  			if currentVersion >= ww.targetVersion {
   178  				ww.gate.Unlock()
   179  				return nil
   180  			}
   181  		}
   182  	}
   183  }
   184  
   185  // newUpgradeWorker returns a worker that runs the upgrade steps, updates the
   186  // model's environ version, and unlocks the gate.
   187  func newUpgradeWorker(config Config, targetVersion int) (worker.Worker, error) {
   188  	currentVersion, err := config.Facade.ModelEnvironVersion(config.ModelTag)
   189  	if err != nil {
   190  		return nil, errors.Trace(err)
   191  	}
   192  
   193  	return jujuworker.NewSimpleWorker(func(<-chan struct{}) error {
   194  		// NOTE(axw) the abort channel is ignored, because upgrade
   195  		// steps are not interruptible. If we find they need to be
   196  		// interruptible, we should consider passing through a
   197  		// context.Context for cancellation, and cancelling it if
   198  		// the abort channel is signalled.
   199  		setVersion := func(v int) error {
   200  			return config.Facade.SetModelEnvironVersion(config.ModelTag, v)
   201  		}
   202  		setStatus := func(s status.Status, info string) error {
   203  			return config.Facade.SetModelStatus(config.ModelTag, s, info, nil)
   204  		}
   205  		if targetVersion > currentVersion {
   206  			if err := setStatus(status.Busy, fmt.Sprintf(
   207  				"upgrading environ from version %d to %d",
   208  				currentVersion, targetVersion,
   209  			)); err != nil {
   210  				return errors.Trace(err)
   211  			}
   212  		}
   213  		if err := runEnvironUpgradeSteps(
   214  			config.Environ,
   215  			config.ControllerTag,
   216  			config.ModelTag,
   217  			currentVersion,
   218  			targetVersion,
   219  			setVersion,
   220  			common.NewCloudCallContextFunc(config.CredentialAPI),
   221  			config.Logger,
   222  		); err != nil {
   223  			info := fmt.Sprintf("failed to upgrade environ: %s", err)
   224  			if err := setStatus(status.Error, info); err != nil {
   225  				config.Logger.Warningf("failed to update model status: %v", err)
   226  			}
   227  			return errors.Annotate(err, "upgrading environ")
   228  		}
   229  		if err := setStatus(status.Available, ""); err != nil {
   230  			return errors.Trace(err)
   231  		}
   232  		config.GateUnlocker.Unlock()
   233  		return nil
   234  	}), nil
   235  }
   236  
   237  func runEnvironUpgradeSteps(
   238  	env environs.Environ,
   239  	controllerTag names.ControllerTag,
   240  	modelTag names.ModelTag,
   241  	currentVersion int,
   242  	targetVersion int,
   243  	setVersion func(int) error,
   244  	callCtxFunc common.CloudCallContextFunc,
   245  	logger Logger,
   246  ) error {
   247  	if wrench.IsActive("environupgrader", "fail-all") ||
   248  		wrench.IsActive("environupgrader", "fail-model-"+modelTag.Id()) {
   249  		return errors.New("wrench active")
   250  	}
   251  	upgrader, ok := env.(environs.Upgrader)
   252  	if !ok {
   253  		logger.Debugf("%T does not support environs.Upgrader", env)
   254  		return nil
   255  	}
   256  	args := environs.UpgradeOperationsParams{
   257  		ControllerUUID: controllerTag.Id(),
   258  	}
   259  	callCtx := callCtxFunc(stdcontext.Background())
   260  	for _, op := range upgrader.UpgradeOperations(callCtx, args) {
   261  		if op.TargetVersion <= currentVersion {
   262  			// The operation is for the same as or older
   263  			// than the current environ version.
   264  			logger.Tracef(
   265  				"ignoring upgrade operation for version %v",
   266  				op.TargetVersion,
   267  			)
   268  			continue
   269  		}
   270  		if op.TargetVersion > targetVersion {
   271  			// The operation is for a version newer than
   272  			// the provider's current version. This will
   273  			// only happen for an improperly written provider.
   274  			logger.Debugf(
   275  				"ignoring upgrade operation for version %v",
   276  				op.TargetVersion,
   277  			)
   278  			continue
   279  		}
   280  		logger.Debugf(
   281  			"running upgrade operation for version %v",
   282  			op.TargetVersion,
   283  		)
   284  		for _, step := range op.Steps {
   285  			logger.Debugf("running step %q", step.Description())
   286  			if err := step.Run(callCtx); err != nil {
   287  				return errors.Trace(err)
   288  			}
   289  		}
   290  		// Record the new version as we go, so we minimise the number
   291  		// of operations we'll re-run in the case of failure.
   292  		if err := setVersion(op.TargetVersion); err != nil {
   293  			return errors.Trace(err)
   294  		}
   295  	}
   296  	return nil
   297  }