github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/modelupgrader/worker.go (about)

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