github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/upgrades/upgrade.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package upgrades
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/loggo"
    10  
    11  	"github.com/juju/juju/version"
    12  )
    13  
    14  var logger = loggo.GetLogger("juju.upgrade")
    15  
    16  // Step defines an idempotent operation that is run to perform
    17  // a specific upgrade step.
    18  type Step interface {
    19  	// Description is a human readable description of what the upgrade step does.
    20  	Description() string
    21  
    22  	// Targets returns the target machine types for which the upgrade step is applicable.
    23  	Targets() []Target
    24  
    25  	// Run executes the upgrade business logic.
    26  	Run(Context) error
    27  }
    28  
    29  // Operation defines what steps to perform to upgrade to a target version.
    30  type Operation interface {
    31  	// The Juju version for which this operation is applicable.
    32  	// Upgrade operations designed for versions of Juju earlier
    33  	// than we are upgrading from are not run since such steps would
    34  	// already have been used to get to the version we are running now.
    35  	TargetVersion() version.Number
    36  
    37  	// Steps to perform during an upgrade.
    38  	Steps() []Step
    39  }
    40  
    41  // Target defines the type of machine for which a particular upgrade
    42  // step can be run.
    43  type Target string
    44  
    45  const (
    46  	// AllMachines applies to any machine.
    47  	AllMachines = Target("allMachines")
    48  
    49  	// HostMachine is a machine on which units are deployed.
    50  	HostMachine = Target("hostMachine")
    51  
    52  	// StateServer is a machine participating in a Juju state server cluster.
    53  	StateServer = Target("stateServer")
    54  
    55  	// DatabaseMaster is a StateServer that has the master database, and as such
    56  	// is the only target that should run database schema upgrade steps.
    57  	DatabaseMaster = Target("databaseMaster")
    58  )
    59  
    60  // upgradeToVersion encapsulates the steps which need to be run to
    61  // upgrade any prior version of Juju to targetVersion.
    62  type upgradeToVersion struct {
    63  	targetVersion version.Number
    64  	steps         []Step
    65  }
    66  
    67  // Steps is defined on the Operation interface.
    68  func (u upgradeToVersion) Steps() []Step {
    69  	return u.steps
    70  }
    71  
    72  // TargetVersion is defined on the Operation interface.
    73  func (u upgradeToVersion) TargetVersion() version.Number {
    74  	return u.targetVersion
    75  }
    76  
    77  // upgradeError records a description of the step being performed and the error.
    78  type upgradeError struct {
    79  	description string
    80  	err         error
    81  }
    82  
    83  func (e *upgradeError) Error() string {
    84  	return fmt.Sprintf("%s: %v", e.description, e.err)
    85  }
    86  
    87  // AreUpgradesDefined returns true if there are upgrade operations
    88  // defined between the version supplied and the running software
    89  // version.
    90  func AreUpgradesDefined(from version.Number) bool {
    91  	return newUpgradeOpsIterator(from).Next() || newStateUpgradeOpsIterator(from).Next()
    92  }
    93  
    94  // PerformUpgrade runs the business logic needed to upgrade the current "from" version to this
    95  // version of Juju on the "target" type of machine.
    96  func PerformUpgrade(from version.Number, targets []Target, context Context) error {
    97  	if hasStateTarget(targets) {
    98  		ops := newStateUpgradeOpsIterator(from)
    99  		if err := runUpgradeSteps(ops, targets, context.StateContext()); err != nil {
   100  			return err
   101  		}
   102  	}
   103  
   104  	ops := newUpgradeOpsIterator(from)
   105  	if err := runUpgradeSteps(ops, targets, context.APIContext()); err != nil {
   106  		return err
   107  	}
   108  
   109  	logger.Infof("All upgrade steps completed successfully")
   110  	return nil
   111  }
   112  
   113  func hasStateTarget(targets []Target) bool {
   114  	for _, target := range targets {
   115  		if target == StateServer || target == DatabaseMaster {
   116  			return true
   117  		}
   118  	}
   119  	return false
   120  }
   121  
   122  // runUpgradeSteps finds all the upgrade operations relevant to
   123  // the targets given and runs the associated upgrade steps.
   124  //
   125  // As soon as any error is encountered, the operation is aborted since
   126  // subsequent steps may required successful completion of earlier
   127  // ones. The steps must be idempotent so that the entire upgrade
   128  // operation can be retried.
   129  func runUpgradeSteps(ops *opsIterator, targets []Target, context Context) error {
   130  	for ops.Next() {
   131  		for _, step := range ops.Get().Steps() {
   132  			if targetsMatch(targets, step.Targets()) {
   133  				logger.Infof("running upgrade step: %v", step.Description())
   134  				if err := step.Run(context); err != nil {
   135  					logger.Errorf("upgrade step %q failed: %v", step.Description(), err)
   136  					return &upgradeError{
   137  						description: step.Description(),
   138  						err:         err,
   139  					}
   140  				}
   141  			}
   142  		}
   143  	}
   144  	return nil
   145  }
   146  
   147  // targetsMatch returns true if any machineTargets match any of
   148  // stepTargets.
   149  func targetsMatch(machineTargets []Target, stepTargets []Target) bool {
   150  	for _, machineTarget := range machineTargets {
   151  		for _, stepTarget := range stepTargets {
   152  			if machineTarget == stepTarget || stepTarget == AllMachines {
   153  				return true
   154  			}
   155  		}
   156  	}
   157  	return false
   158  }
   159  
   160  // upgradeStep is a default Step implementation.
   161  type upgradeStep struct {
   162  	description string
   163  	targets     []Target
   164  	run         func(Context) error
   165  }
   166  
   167  var _ Step = (*upgradeStep)(nil)
   168  
   169  // Description is defined on the Step interface.
   170  func (step *upgradeStep) Description() string {
   171  	return step.description
   172  }
   173  
   174  // Targets is defined on the Step interface.
   175  func (step *upgradeStep) Targets() []Target {
   176  	return step.targets
   177  }
   178  
   179  // Run is defined on the Step interface.
   180  func (step *upgradeStep) Run(context Context) error {
   181  	return step.run(context)
   182  }