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