github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/upgrades/upgrade_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package upgrades_test
     5  
     6  import (
     7  	"fmt"
     8  	"path/filepath"
     9  	"strings"
    10  	stdtesting "testing"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/names/v5"
    14  	"github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	"github.com/juju/version/v2"
    17  	gc "gopkg.in/check.v1"
    18  
    19  	"github.com/juju/juju/agent"
    20  	"github.com/juju/juju/api"
    21  	"github.com/juju/juju/api/base"
    22  	"github.com/juju/juju/controller"
    23  	"github.com/juju/juju/core/model"
    24  	"github.com/juju/juju/mongo"
    25  	coretesting "github.com/juju/juju/testing"
    26  	"github.com/juju/juju/upgrades"
    27  	jujuversion "github.com/juju/juju/version"
    28  )
    29  
    30  func TestPackage(t *stdtesting.T) {
    31  	gc.TestingT(t)
    32  }
    33  
    34  // Untl we add upgrade steps for 3.0, keep static analysis happy.
    35  var _ = findStateStep
    36  
    37  func findStateStep(c *gc.C, ver version.Number, description string) upgrades.Step {
    38  	for _, op := range (*upgrades.StateUpgradeOperations)() {
    39  		if op.TargetVersion() == ver {
    40  			for _, step := range op.Steps() {
    41  				if step.Description() == description {
    42  					return step
    43  				}
    44  			}
    45  		}
    46  	}
    47  	c.Fatalf("could not find state step %q for %s", description, ver)
    48  	return nil
    49  }
    50  
    51  type upgradeSuite struct {
    52  	coretesting.BaseSuite
    53  }
    54  
    55  var _ = gc.Suite(&upgradeSuite{})
    56  
    57  type mockUpgradeOperation struct {
    58  	targetVersion version.Number
    59  	steps         []upgrades.Step
    60  }
    61  
    62  func (m *mockUpgradeOperation) TargetVersion() version.Number {
    63  	return m.targetVersion
    64  }
    65  
    66  func (m *mockUpgradeOperation) Steps() []upgrades.Step {
    67  	return m.steps
    68  }
    69  
    70  type mockUpgradeStep struct {
    71  	msg     string
    72  	targets []upgrades.Target
    73  }
    74  
    75  func (u *mockUpgradeStep) Description() string {
    76  	return u.msg
    77  }
    78  
    79  func (u *mockUpgradeStep) Targets() []upgrades.Target {
    80  	return u.targets
    81  }
    82  
    83  func (u *mockUpgradeStep) Run(ctx upgrades.Context) error {
    84  	if strings.HasSuffix(u.msg, "error") {
    85  		return errors.New("upgrade error occurred")
    86  	}
    87  	context := ctx.(*mockContext)
    88  	context.messages = append(context.messages, u.msg)
    89  	return nil
    90  }
    91  
    92  func newUpgradeStep(msg string, targets ...upgrades.Target) *mockUpgradeStep {
    93  	if len(targets) < 1 {
    94  		panic(fmt.Sprintf("step %q must have at least one target", msg))
    95  	}
    96  	return &mockUpgradeStep{
    97  		msg:     msg,
    98  		targets: targets,
    99  	}
   100  }
   101  
   102  type mockContext struct {
   103  	messages        []string
   104  	agentConfig     *mockAgentConfig
   105  	realAgentConfig agent.ConfigSetter
   106  	apiState        api.Connection
   107  	state           upgrades.StateBackend
   108  }
   109  
   110  func (c *mockContext) APIState() base.APICaller {
   111  	return c.apiState
   112  }
   113  
   114  func (c *mockContext) State() upgrades.StateBackend {
   115  	return c.state
   116  }
   117  
   118  func (c *mockContext) AgentConfig() agent.ConfigSetter {
   119  	if c.realAgentConfig != nil {
   120  		return c.realAgentConfig
   121  	}
   122  	return c.agentConfig
   123  }
   124  
   125  func (c *mockContext) StateContext() upgrades.Context {
   126  	return c
   127  }
   128  
   129  func (c *mockContext) APIContext() upgrades.Context {
   130  	return c
   131  }
   132  
   133  type mockAgentConfig struct {
   134  	agent.ConfigSetter
   135  	dataDir      string
   136  	logDir       string
   137  	tag          names.Tag
   138  	jobs         []model.MachineJob
   139  	apiAddresses []string
   140  	values       map[string]string
   141  	mongoInfo    *mongo.MongoInfo
   142  	servingInfo  controller.StateServingInfo
   143  	modelTag     names.ModelTag
   144  }
   145  
   146  func (mock *mockAgentConfig) Tag() names.Tag {
   147  	return mock.tag
   148  }
   149  
   150  func (mock *mockAgentConfig) DataDir() string {
   151  	return mock.dataDir
   152  }
   153  
   154  func (mock *mockAgentConfig) TransientDataDir() string {
   155  	return filepath.Join(mock.dataDir, "transient")
   156  }
   157  
   158  func (mock *mockAgentConfig) LogDir() string {
   159  	return mock.logDir
   160  }
   161  
   162  func (mock *mockAgentConfig) SystemIdentityPath() string {
   163  	return filepath.Join(mock.dataDir, agent.SystemIdentity)
   164  }
   165  
   166  func (mock *mockAgentConfig) Jobs() []model.MachineJob {
   167  	return mock.jobs
   168  }
   169  
   170  func (mock *mockAgentConfig) APIAddresses() ([]string, error) {
   171  	return mock.apiAddresses, nil
   172  }
   173  
   174  func (mock *mockAgentConfig) Value(name string) string {
   175  	return mock.values[name]
   176  }
   177  
   178  func (mock *mockAgentConfig) MongoInfo() (*mongo.MongoInfo, bool) {
   179  	return mock.mongoInfo, true
   180  }
   181  
   182  func (mock *mockAgentConfig) StateServingInfo() (controller.StateServingInfo, bool) {
   183  	return mock.servingInfo, true
   184  }
   185  
   186  func (mock *mockAgentConfig) SetStateServingInfo(info controller.StateServingInfo) {
   187  	mock.servingInfo = info
   188  }
   189  
   190  func (mock *mockAgentConfig) Model() names.ModelTag {
   191  	return mock.modelTag
   192  }
   193  
   194  type mockStateBackend struct {
   195  	upgrades.StateBackend
   196  	testing.Stub
   197  }
   198  
   199  func (mock *mockStateBackend) ControllerUUID() (string, error) {
   200  	mock.MethodCall(mock, "ControllerUUID")
   201  	return "a-b-c-d", mock.Stub.NextErr()
   202  }
   203  
   204  func stateUpgradeOperations() []upgrades.Operation {
   205  	steps := []upgrades.Operation{
   206  		&mockUpgradeOperation{
   207  			targetVersion: version.MustParse("1.11.0"),
   208  			steps: []upgrades.Step{
   209  				newUpgradeStep("state step 1 - 1.11.0", upgrades.Controller),
   210  				newUpgradeStep("state step 2 error", upgrades.Controller),
   211  				newUpgradeStep("state step 3 - 1.11.0", upgrades.Controller),
   212  			},
   213  		},
   214  		&mockUpgradeOperation{
   215  			targetVersion: version.MustParse("1.21.0"),
   216  			steps: []upgrades.Step{
   217  				newUpgradeStep("state step 1 - 1.21.0", upgrades.DatabaseMaster),
   218  				newUpgradeStep("state step 2 - 1.21.0", upgrades.Controller),
   219  			},
   220  		},
   221  		&mockUpgradeOperation{
   222  			targetVersion: version.MustParse("1.22.0"),
   223  			steps: []upgrades.Step{
   224  				newUpgradeStep("state step 1 - 1.22.0", upgrades.DatabaseMaster),
   225  				newUpgradeStep("state step 2 - 1.22.0", upgrades.Controller),
   226  			},
   227  		},
   228  	}
   229  	return steps
   230  }
   231  
   232  func upgradeOperations() []upgrades.Operation {
   233  	steps := []upgrades.Operation{
   234  		&mockUpgradeOperation{
   235  			targetVersion: version.MustParse("1.12.0"),
   236  			steps: []upgrades.Step{
   237  				newUpgradeStep("step 1 - 1.12.0", upgrades.AllMachines),
   238  				newUpgradeStep("step 2 error", upgrades.HostMachine),
   239  				newUpgradeStep("step 3", upgrades.HostMachine),
   240  			},
   241  		},
   242  		&mockUpgradeOperation{
   243  			targetVersion: version.MustParse("1.16.0"),
   244  			steps: []upgrades.Step{
   245  				newUpgradeStep("step 1 - 1.16.0", upgrades.HostMachine),
   246  				newUpgradeStep("step 2 - 1.16.0", upgrades.HostMachine),
   247  				newUpgradeStep("step 3 - 1.16.0", upgrades.Controller),
   248  			},
   249  		},
   250  		&mockUpgradeOperation{
   251  			targetVersion: version.MustParse("1.17.0"),
   252  			steps: []upgrades.Step{
   253  				newUpgradeStep("step 1 - 1.17.0", upgrades.HostMachine),
   254  			},
   255  		},
   256  		&mockUpgradeOperation{
   257  			targetVersion: version.MustParse("1.17.1"),
   258  			steps: []upgrades.Step{
   259  				newUpgradeStep("step 1 - 1.17.1", upgrades.HostMachine),
   260  				newUpgradeStep("step 2 - 1.17.1", upgrades.Controller),
   261  			},
   262  		},
   263  		&mockUpgradeOperation{
   264  			targetVersion: version.MustParse("1.18.0"),
   265  			steps: []upgrades.Step{
   266  				newUpgradeStep("step 1 - 1.18.0", upgrades.HostMachine),
   267  				newUpgradeStep("step 2 - 1.18.0", upgrades.Controller),
   268  			},
   269  		},
   270  		&mockUpgradeOperation{
   271  			targetVersion: version.MustParse("1.20.0"),
   272  			steps: []upgrades.Step{
   273  				newUpgradeStep("step 1 - 1.20.0", upgrades.AllMachines),
   274  				newUpgradeStep("step 2 - 1.20.0", upgrades.HostMachine),
   275  				newUpgradeStep("step 3 - 1.20.0", upgrades.Controller),
   276  			},
   277  		},
   278  		&mockUpgradeOperation{
   279  			targetVersion: version.MustParse("1.21.0"),
   280  			steps: []upgrades.Step{
   281  				newUpgradeStep("step 1 - 1.21.0", upgrades.AllMachines),
   282  			},
   283  		},
   284  		&mockUpgradeOperation{
   285  			targetVersion: version.MustParse("1.22.0"),
   286  			steps: []upgrades.Step{
   287  				// Separate targets used intentionally
   288  				newUpgradeStep("step 1 - 1.22.0", upgrades.Controller, upgrades.HostMachine),
   289  				newUpgradeStep("step 2 - 1.22.0", upgrades.AllMachines),
   290  			},
   291  		},
   292  	}
   293  	return steps
   294  }
   295  
   296  type upgradeTest struct {
   297  	about         string
   298  	fromVersion   string
   299  	toVersion     string
   300  	targets       []upgrades.Target
   301  	expectedSteps []string
   302  	err           string
   303  }
   304  
   305  func targets(t ...upgrades.Target) []upgrades.Target {
   306  	return t
   307  }
   308  
   309  var upgradeTests = []upgradeTest{
   310  	{
   311  		about:         "from version excludes steps for same version",
   312  		fromVersion:   "1.18.0",
   313  		targets:       targets(upgrades.HostMachine),
   314  		expectedSteps: []string{},
   315  	},
   316  	{
   317  		about:         "target version excludes steps for newer version",
   318  		toVersion:     "1.17.1",
   319  		targets:       targets(upgrades.HostMachine),
   320  		expectedSteps: []string{"step 1 - 1.17.0", "step 1 - 1.17.1"},
   321  	},
   322  	{
   323  		about:         "from version excludes older steps",
   324  		fromVersion:   "1.17.0",
   325  		targets:       targets(upgrades.HostMachine),
   326  		expectedSteps: []string{"step 1 - 1.17.1", "step 1 - 1.18.0"},
   327  	},
   328  	{
   329  		about:         "incompatible targets excluded",
   330  		fromVersion:   "1.17.1",
   331  		targets:       targets(upgrades.Controller),
   332  		expectedSteps: []string{"step 2 - 1.18.0"},
   333  	},
   334  	{
   335  		about:         "allMachines matches everything",
   336  		fromVersion:   "1.18.1",
   337  		toVersion:     "1.20.0",
   338  		targets:       targets(upgrades.HostMachine),
   339  		expectedSteps: []string{"step 1 - 1.20.0", "step 2 - 1.20.0"},
   340  	},
   341  	{
   342  		about:         "allMachines matches everything",
   343  		fromVersion:   "1.18.1",
   344  		toVersion:     "1.20.0",
   345  		targets:       targets(upgrades.Controller),
   346  		expectedSteps: []string{"step 1 - 1.20.0", "step 3 - 1.20.0"},
   347  	},
   348  	{
   349  		about:         "state step error aborts, subsequent state steps not run",
   350  		fromVersion:   "1.10.0",
   351  		targets:       targets(upgrades.Controller),
   352  		expectedSteps: []string{"state step 1 - 1.11.0"},
   353  		err:           "state step 2 error: upgrade error occurred",
   354  	},
   355  	{
   356  		about:         "error aborts, subsequent steps not run",
   357  		fromVersion:   "1.11.0",
   358  		targets:       targets(upgrades.HostMachine),
   359  		expectedSteps: []string{"step 1 - 1.12.0"},
   360  		err:           "step 2 error: upgrade error occurred",
   361  	},
   362  	{
   363  		about:         "default from version is 1.16",
   364  		fromVersion:   "",
   365  		targets:       targets(upgrades.Controller),
   366  		expectedSteps: []string{"step 2 - 1.17.1", "step 2 - 1.18.0"},
   367  	},
   368  	{
   369  		about:         "controllers don't get database master",
   370  		fromVersion:   "1.20.0",
   371  		toVersion:     "1.21.0",
   372  		targets:       targets(upgrades.Controller),
   373  		expectedSteps: []string{"state step 2 - 1.21.0", "step 1 - 1.21.0"},
   374  	},
   375  	{
   376  		about:         "database master only (not actually possible in reality)",
   377  		fromVersion:   "1.20.0",
   378  		toVersion:     "1.21.0",
   379  		targets:       targets(upgrades.DatabaseMaster),
   380  		expectedSteps: []string{"state step 1 - 1.21.0", "step 1 - 1.21.0"},
   381  	},
   382  	{
   383  		about:       "all state steps are run first",
   384  		fromVersion: "1.20.0",
   385  		toVersion:   "1.22.0",
   386  		targets:     targets(upgrades.DatabaseMaster, upgrades.Controller),
   387  		expectedSteps: []string{
   388  			"state step 1 - 1.21.0", "state step 2 - 1.21.0",
   389  			"state step 1 - 1.22.0", "state step 2 - 1.22.0",
   390  			"step 1 - 1.21.0",
   391  			"step 1 - 1.22.0", "step 2 - 1.22.0",
   392  		},
   393  	},
   394  	{
   395  		about:         "machine with multiple targets - each step only run once",
   396  		fromVersion:   "1.20.0",
   397  		toVersion:     "1.21.0",
   398  		targets:       targets(upgrades.HostMachine, upgrades.Controller),
   399  		expectedSteps: []string{"state step 2 - 1.21.0", "step 1 - 1.21.0"},
   400  	},
   401  	{
   402  		about:         "step with multiple targets",
   403  		fromVersion:   "1.21.0",
   404  		toVersion:     "1.22.0",
   405  		targets:       targets(upgrades.HostMachine),
   406  		expectedSteps: []string{"step 1 - 1.22.0", "step 2 - 1.22.0"},
   407  	},
   408  	{
   409  		about:         "machine and step with multiple targets - each step only run once",
   410  		fromVersion:   "1.21.0",
   411  		toVersion:     "1.22.0",
   412  		targets:       targets(upgrades.HostMachine, upgrades.Controller),
   413  		expectedSteps: []string{"state step 2 - 1.22.0", "step 1 - 1.22.0", "step 2 - 1.22.0"},
   414  	},
   415  	{
   416  		about:         "upgrade to alpha release runs steps for final release",
   417  		fromVersion:   "1.20.0",
   418  		toVersion:     "1.21-alpha1",
   419  		targets:       targets(upgrades.HostMachine),
   420  		expectedSteps: []string{"step 1 - 1.21.0"},
   421  	},
   422  	{
   423  		about:         "upgrade to beta release runs steps for final release",
   424  		fromVersion:   "1.20.0",
   425  		toVersion:     "1.21-beta2",
   426  		targets:       targets(upgrades.HostMachine),
   427  		expectedSteps: []string{"step 1 - 1.21.0"},
   428  	},
   429  	{
   430  		about:         "starting release steps included when upgrading from an alpha release",
   431  		fromVersion:   "1.20-alpha3",
   432  		toVersion:     "1.21.0",
   433  		targets:       targets(upgrades.HostMachine),
   434  		expectedSteps: []string{"step 1 - 1.20.0", "step 2 - 1.20.0", "step 1 - 1.21.0"},
   435  	},
   436  	{
   437  		about:         "starting release steps included when upgrading from an beta release",
   438  		fromVersion:   "1.20-beta1",
   439  		toVersion:     "1.21.0",
   440  		targets:       targets(upgrades.HostMachine),
   441  		expectedSteps: []string{"step 1 - 1.20.0", "step 2 - 1.20.0", "step 1 - 1.21.0"},
   442  	},
   443  	{
   444  		about:         "nothing happens when the version hasn't changed but contains a tag",
   445  		fromVersion:   "1.21-alpha1",
   446  		toVersion:     "1.21-alpha1",
   447  		targets:       targets(upgrades.DatabaseMaster),
   448  		expectedSteps: []string{},
   449  	},
   450  	{
   451  		about:         "upgrades between pre-final versions should run steps for the final version",
   452  		fromVersion:   "1.21-beta2",
   453  		toVersion:     "1.21-beta3",
   454  		targets:       targets(upgrades.DatabaseMaster),
   455  		expectedSteps: []string{"state step 1 - 1.21.0", "step 1 - 1.21.0"},
   456  	},
   457  }
   458  
   459  func (s *upgradeSuite) TestPerformUpgrade(c *gc.C) {
   460  	s.PatchValue(upgrades.StateUpgradeOperations, stateUpgradeOperations)
   461  	s.PatchValue(upgrades.UpgradeOperations, upgradeOperations)
   462  	for i, test := range upgradeTests {
   463  		c.Logf("%d: %s", i, test.about)
   464  		var messages []string
   465  		ctx := &mockContext{
   466  			messages: messages,
   467  			state:    &mockStateBackend{},
   468  		}
   469  		fromVersion := version.Zero
   470  		if test.fromVersion != "" {
   471  			fromVersion = version.MustParse(test.fromVersion)
   472  		}
   473  		toVersion := version.MustParse("1.18.0")
   474  		if test.toVersion != "" {
   475  			toVersion = version.MustParse(test.toVersion)
   476  		}
   477  		s.PatchValue(&jujuversion.Current, toVersion)
   478  		err := upgrades.PerformUpgrade(fromVersion, test.targets, ctx)
   479  		if test.err == "" {
   480  			c.Check(err, jc.ErrorIsNil)
   481  		} else {
   482  			c.Check(err, gc.ErrorMatches, test.err)
   483  		}
   484  		c.Check(ctx.messages, jc.DeepEquals, test.expectedSteps)
   485  	}
   486  }
   487  
   488  type contextStep struct {
   489  	useAPI bool
   490  }
   491  
   492  func (s *contextStep) Description() string {
   493  	return "something"
   494  }
   495  
   496  func (s *contextStep) Targets() []upgrades.Target {
   497  	return []upgrades.Target{upgrades.Controller}
   498  }
   499  
   500  func (s *contextStep) Run(context upgrades.Context) error {
   501  	if s.useAPI {
   502  		context.APIState()
   503  	} else {
   504  		context.State()
   505  	}
   506  	return nil
   507  }
   508  
   509  func (s *upgradeSuite) TestStateStepsGetRestrictedContext(c *gc.C) {
   510  	s.PatchValue(upgrades.StateUpgradeOperations, func() []upgrades.Operation {
   511  		return []upgrades.Operation{
   512  			&mockUpgradeOperation{
   513  				targetVersion: version.MustParse("1.21.0"),
   514  				steps:         []upgrades.Step{&contextStep{useAPI: true}},
   515  			},
   516  		}
   517  	})
   518  
   519  	s.PatchValue(upgrades.UpgradeOperations,
   520  		func() []upgrades.Operation { return nil })
   521  
   522  	s.checkContextRestriction(c, "API not available from this context")
   523  }
   524  
   525  func (s *upgradeSuite) TestAPIStepsGetRestrictedContext(c *gc.C) {
   526  	s.PatchValue(upgrades.StateUpgradeOperations,
   527  		func() []upgrades.Operation { return nil })
   528  
   529  	s.PatchValue(upgrades.UpgradeOperations, func() []upgrades.Operation {
   530  		return []upgrades.Operation{
   531  			&mockUpgradeOperation{
   532  				targetVersion: version.MustParse("1.21.0"),
   533  				steps:         []upgrades.Step{&contextStep{useAPI: false}},
   534  			},
   535  		}
   536  	})
   537  
   538  	s.checkContextRestriction(c, "State not available from this context")
   539  }
   540  
   541  func (s *upgradeSuite) checkContextRestriction(c *gc.C, expectedPanic string) {
   542  	fromVersion := version.MustParse("1.20.0")
   543  	type fakeAgentConfigSetter struct{ agent.ConfigSetter }
   544  	ctx := upgrades.NewContext(fakeAgentConfigSetter{}, nil, &mockStateBackend{})
   545  	c.Assert(
   546  		func() { _ = upgrades.PerformUpgrade(fromVersion, targets(upgrades.Controller), ctx) },
   547  		gc.PanicMatches, expectedPanic,
   548  	)
   549  }
   550  
   551  func (s *upgradeSuite) TestStateStepsNotAttemptedWhenNoStateTarget(c *gc.C) {
   552  	stateCount := 0
   553  	stateUpgradeOperations := func() []upgrades.Operation {
   554  		stateCount++
   555  		return nil
   556  	}
   557  	s.PatchValue(upgrades.StateUpgradeOperations, stateUpgradeOperations)
   558  
   559  	apiCount := 0
   560  	upgradeOperations := func() []upgrades.Operation {
   561  		apiCount++
   562  		return nil
   563  	}
   564  	s.PatchValue(upgrades.UpgradeOperations, upgradeOperations)
   565  
   566  	fromVers := version.MustParse("1.18.0")
   567  	state := &mockStateBackend{}
   568  	ctx := &mockContext{state: state}
   569  	check := func(target upgrades.Target, expectedStateCallCount int, expectedStateMethodCalls []string) {
   570  		stateCount = 0
   571  		apiCount = 0
   572  		err := upgrades.PerformUpgrade(fromVers, targets(target), ctx)
   573  		c.Assert(err, jc.ErrorIsNil)
   574  		c.Assert(stateCount, gc.Equals, expectedStateCallCount)
   575  		c.Assert(apiCount, gc.Equals, 1)
   576  		state.CheckCallNames(c, expectedStateMethodCalls...)
   577  		state.ResetCalls()
   578  	}
   579  
   580  	check(upgrades.Controller, 1, nil)
   581  	check(upgrades.DatabaseMaster, 1, nil)
   582  	check(upgrades.AllMachines, 0, nil)
   583  	check(upgrades.HostMachine, 0, nil)
   584  }
   585  
   586  func (s *upgradeSuite) TestUpgradeOperationsOrdered(c *gc.C) {
   587  	var previous version.Number
   588  	for i, utv := range (*upgrades.UpgradeOperations)() {
   589  		vers := utv.TargetVersion()
   590  		if i > 0 {
   591  			c.Check(previous.Compare(vers), gc.Equals, -1)
   592  		}
   593  		previous = vers
   594  	}
   595  }
   596  
   597  func (s *upgradeSuite) TestStateUpgradeOperationsVersions(c *gc.C) {
   598  	versions := extractUpgradeVersions(c, (*upgrades.StateUpgradeOperations)())
   599  	c.Assert(versions, gc.DeepEquals, []string(nil))
   600  }
   601  
   602  func (s *upgradeSuite) TestUpgradeOperationsVersions(c *gc.C) {
   603  	versions := extractUpgradeVersions(c, (*upgrades.UpgradeOperations)())
   604  	c.Assert(versions, gc.DeepEquals, []string(nil))
   605  }
   606  
   607  func extractUpgradeVersions(c *gc.C, ops []upgrades.Operation) []string {
   608  	var versions []string
   609  	for _, utv := range ops {
   610  		vers := utv.TargetVersion()
   611  		// Upgrade steps should only be targeted at final versions (not alpha/beta).
   612  		c.Check(vers.Tag, gc.Equals, "")
   613  		versions = append(versions, vers.String())
   614  	}
   615  	return versions
   616  }