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