github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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/agent"
    12  	"github.com/juju/juju/state"
    13  	"github.com/juju/juju/state/api"
    14  	"github.com/juju/juju/version"
    15  )
    16  
    17  var logger = loggo.GetLogger("juju.upgrade")
    18  
    19  // Step defines an idempotent operation that is run to perform
    20  // a specific upgrade step.
    21  type Step interface {
    22  	// Description is a human readable description of what the upgrade step does.
    23  	Description() string
    24  
    25  	// Targets returns the target machine types for which the upgrade step is applicable.
    26  	Targets() []Target
    27  
    28  	// Run executes the upgrade business logic.
    29  	Run(context Context) error
    30  }
    31  
    32  // Operation defines what steps to perform to upgrade to a target version.
    33  type Operation interface {
    34  	// The Juju version for which this operation is applicable.
    35  	// Upgrade operations designed for versions of Juju earlier
    36  	// than we are upgrading from are not run since such steps would
    37  	// already have been used to get to the version we are running now.
    38  	TargetVersion() version.Number
    39  
    40  	// Steps to perform during an upgrade.
    41  	Steps() []Step
    42  }
    43  
    44  // Target defines the type of machine for which a particular upgrade
    45  // step can be run.
    46  type Target string
    47  
    48  const (
    49  	// AllMachines applies to any machine.
    50  	AllMachines = Target("allMachines")
    51  
    52  	// HostMachine is a machine on which units are deployed.
    53  	HostMachine = Target("hostMachine")
    54  
    55  	// StateServer is a machine participating in a Juju state server cluster.
    56  	StateServer = Target("stateServer")
    57  )
    58  
    59  // upgradeToVersion encapsulates the steps which need to be run to
    60  // upgrade any prior version of Juju to targetVersion.
    61  type upgradeToVersion struct {
    62  	targetVersion version.Number
    63  	steps         []Step
    64  }
    65  
    66  // Steps is defined on the Operation interface.
    67  func (u upgradeToVersion) Steps() []Step {
    68  	return u.steps
    69  }
    70  
    71  // TargetVersion is defined on the Operation interface.
    72  func (u upgradeToVersion) TargetVersion() version.Number {
    73  	return u.targetVersion
    74  }
    75  
    76  // Context is used give the upgrade steps attributes needed
    77  // to do their job.
    78  type Context interface {
    79  	// APIState returns an API connection to state.
    80  	APIState() *api.State
    81  
    82  	// State returns a connection to state. This will be non-nil
    83  	// only in the context of a state server.
    84  	State() *state.State
    85  
    86  	// AgentConfig returns the agent config for the machine that is being
    87  	// upgraded.
    88  	AgentConfig() agent.ConfigSetter
    89  }
    90  
    91  // upgradeContext is a default Context implementation.
    92  type upgradeContext struct {
    93  	// Work in progress........
    94  	// Exactly what a context needs is to be determined as the
    95  	// implementation evolves.
    96  	api         *api.State
    97  	st          *state.State
    98  	agentConfig agent.ConfigSetter
    99  }
   100  
   101  // APIState is defined on the Context interface.
   102  func (c *upgradeContext) APIState() *api.State {
   103  	return c.api
   104  }
   105  
   106  // State is defined on the Context interface.
   107  func (c *upgradeContext) State() *state.State {
   108  	return c.st
   109  }
   110  
   111  // AgentConfig is defined on the Context interface.
   112  func (c *upgradeContext) AgentConfig() agent.ConfigSetter {
   113  	return c.agentConfig
   114  }
   115  
   116  // NewContext returns a new upgrade context.
   117  func NewContext(agentConfig agent.ConfigSetter, api *api.State, st *state.State) Context {
   118  	return &upgradeContext{
   119  		api:         api,
   120  		st:          st,
   121  		agentConfig: agentConfig,
   122  	}
   123  }
   124  
   125  // upgradeError records a description of the step being performed and the error.
   126  type upgradeError struct {
   127  	description string
   128  	err         error
   129  }
   130  
   131  func (e *upgradeError) Error() string {
   132  	return fmt.Sprintf("%s: %v", e.description, e.err)
   133  }
   134  
   135  // PerformUpgrade runs the business logic needed to upgrade the current "from" version to this
   136  // version of Juju on the "target" type of machine.
   137  func PerformUpgrade(from version.Number, target Target, context Context) error {
   138  	// If from is not known, it is 1.16.
   139  	if from == version.Zero {
   140  		from = version.MustParse("1.16.0")
   141  	}
   142  	for _, upgradeOps := range upgradeOperations() {
   143  		targetVersion := upgradeOps.TargetVersion()
   144  		// Do not run steps for versions of Juju earlier or same as we are upgrading from.
   145  		if targetVersion.Compare(from) <= 0 {
   146  			continue
   147  		}
   148  		// Do not run steps for versions of Juju later than we are upgrading to.
   149  		if targetVersion.Compare(version.Current.Number) > 0 {
   150  			continue
   151  		}
   152  		if err := runUpgradeSteps(context, target, upgradeOps); err != nil {
   153  			return err
   154  		}
   155  	}
   156  	return nil
   157  }
   158  
   159  // validTarget returns true if target is in step.Targets().
   160  func validTarget(target Target, step Step) bool {
   161  	for _, opTarget := range step.Targets() {
   162  		if opTarget == AllMachines || target == opTarget {
   163  			return true
   164  		}
   165  	}
   166  	return len(step.Targets()) == 0
   167  }
   168  
   169  // runUpgradeSteps runs all the upgrade steps relevant to target.
   170  // As soon as any error is encountered, the operation is aborted since
   171  // subsequent steps may required successful completion of earlier ones.
   172  // The steps must be idempotent so that the entire upgrade operation can
   173  // be retried.
   174  func runUpgradeSteps(context Context, target Target, upgradeOp Operation) *upgradeError {
   175  	for _, step := range upgradeOp.Steps() {
   176  		if !validTarget(target, step) {
   177  			continue
   178  		}
   179  		logger.Infof("running upgrade step on target %q: %v", target, step.Description())
   180  		if err := step.Run(context); err != nil {
   181  			logger.Errorf("upgrade step %q failed: %v", step.Description(), err)
   182  			return &upgradeError{
   183  				description: step.Description(),
   184  				err:         err,
   185  			}
   186  		}
   187  	}
   188  	logger.Infof("All upgrade steps completed successfully")
   189  	return nil
   190  }
   191  
   192  type upgradeStep struct {
   193  	description string
   194  	targets     []Target
   195  	run         func(Context) error
   196  }
   197  
   198  // Description is defined on the Step interface.
   199  func (step *upgradeStep) Description() string {
   200  	return step.description
   201  }
   202  
   203  // Targets is defined on the Step interface.
   204  func (step *upgradeStep) Targets() []Target {
   205  	return step.targets
   206  }
   207  
   208  // Run is defined on the Step interface.
   209  func (step *upgradeStep) Run(context Context) error {
   210  	return step.run(context)
   211  }