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 }