github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/migration/precheck_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package migration_test 5 6 import ( 7 "strings" 8 9 "github.com/juju/errors" 10 "github.com/juju/names/v5" 11 "github.com/juju/replicaset/v3" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/version/v2" 14 "go.uber.org/mock/gomock" 15 gc "gopkg.in/check.v1" 16 17 coremigration "github.com/juju/juju/core/migration" 18 "github.com/juju/juju/core/presence" 19 "github.com/juju/juju/core/status" 20 environscloudspec "github.com/juju/juju/environs/cloudspec" 21 "github.com/juju/juju/migration" 22 "github.com/juju/juju/provider/lxd" 23 "github.com/juju/juju/state" 24 "github.com/juju/juju/testing" 25 "github.com/juju/juju/tools" 26 "github.com/juju/juju/upgrades/upgradevalidation" 27 upgradevalidationmocks "github.com/juju/juju/upgrades/upgradevalidation/mocks" 28 ) 29 30 var ( 31 modelName = "model-name" 32 modelUUID = "model-uuid" 33 modelOwner = names.NewUserTag("owner") 34 backendVersionBinary = version.MustParseBinary("1.2.3-ubuntu-amd64") 35 backendVersion = backendVersionBinary.Number 36 ) 37 38 type SourcePrecheckSuite struct { 39 precheckBaseSuite 40 } 41 42 var _ = gc.Suite(&SourcePrecheckSuite{}) 43 44 func sourcePrecheck(backend migration.PrecheckBackend) error { 45 return migration.SourcePrecheck( 46 backend, allAlivePresence(), allAlivePresence(), 47 func(names.ModelTag) (environscloudspec.CloudSpec, error) { 48 return environscloudspec.CloudSpec{Type: "lxd"}, nil 49 }, 50 ) 51 } 52 53 func (*SourcePrecheckSuite) TestSuccess(c *gc.C) { 54 backend := newHappyBackend() 55 backend.controllerBackend = newHappyBackend() 56 err := migration.SourcePrecheck( 57 backend, allAlivePresence(), allAlivePresence(), 58 func(names.ModelTag) (environscloudspec.CloudSpec, error) { 59 return environscloudspec.CloudSpec{Type: "lxd"}, nil 60 }, 61 ) 62 c.Assert(err, jc.ErrorIsNil) 63 } 64 65 func (*SourcePrecheckSuite) TestDyingModel(c *gc.C) { 66 backend := newFakeBackend() 67 backend.model.life = state.Dying 68 err := sourcePrecheck(backend) 69 c.Assert(err, gc.ErrorMatches, "model is dying") 70 } 71 72 func (*SourcePrecheckSuite) TestCharmUpgrades(c *gc.C) { 73 backend := &fakeBackend{ 74 apps: []migration.PrecheckApplication{ 75 &fakeApp{ 76 name: "spanner", 77 charmURL: "ch:spanner-3", 78 units: []migration.PrecheckUnit{ 79 &fakeUnit{name: "spanner/0", charmURL: "ch:spanner-3"}, 80 &fakeUnit{name: "spanner/1", charmURL: "ch:spanner-2"}, 81 }, 82 }, 83 }, 84 } 85 err := sourcePrecheck(backend) 86 c.Assert(err, gc.ErrorMatches, "unit spanner/1 is upgrading") 87 } 88 89 func (s *SourcePrecheckSuite) TestTargetController3Failed(c *gc.C) { 90 ctrl := gomock.NewController(c) 91 server := upgradevalidationmocks.NewMockServer(ctrl) 92 serverFactory := upgradevalidationmocks.NewMockServerFactory(ctrl) 93 s.PatchValue(&upgradevalidation.NewServerFactory, 94 func(_ lxd.NewHTTPClientFunc) lxd.ServerFactory { 95 return serverFactory 96 }, 97 ) 98 cloudSpec := lxd.CloudSpec{CloudSpec: environscloudspec.CloudSpec{Type: "lxd"}} 99 100 backend := newFakeBackend() 101 hasUpgradeSeriesLocks := true 102 backend.hasUpgradeSeriesLocks = &hasUpgradeSeriesLocks 103 backend.machineCountForSeriesWin = map[string]int{"win10": 1, "win7": 2} 104 backend.machineCountForSeriesUbuntu = map[string]int{"xenial": 1, "vivid": 2, "trusty": 3} 105 agentVersion := version.MustParse("2.9.35") 106 backend.model.agentVersion = &agentVersion 107 backend.model.name = "model-1" 108 backend.model.owner = names.NewUserTag("foo") 109 110 // - check LXD version. 111 serverFactory.EXPECT().RemoteServer(cloudSpec).Return(server, nil) 112 server.EXPECT().ServerVersion().Return("4.0") 113 114 err := migration.SourcePrecheck( 115 backend, allAlivePresence(), allAlivePresence(), 116 func(names.ModelTag) (environscloudspec.CloudSpec, error) { 117 return cloudSpec.CloudSpec, nil 118 }, 119 ) 120 c.Assert(err.Error(), gc.Equals, ` 121 cannot migrate to controller due to issues: 122 "foo/model-1": 123 - unexpected upgrade series lock found 124 - the model hosts deprecated windows machine(s): win10(1) win7(2) 125 - the model hosts deprecated ubuntu machine(s): trusty(3) vivid(2) xenial(1) 126 - LXD version has to be at least "5.0.0", but current version is only "4.0.0"`[1:]) 127 } 128 129 func (*SourcePrecheckSuite) TestTargetController2Failed(c *gc.C) { 130 backend := newFakeBackend() 131 hasUpgradeSeriesLocks := true 132 backend.hasUpgradeSeriesLocks = &hasUpgradeSeriesLocks 133 backend.machineCountForSeriesWin = map[string]int{"win10": 1, "win7": 2} 134 backend.machineCountForSeriesUbuntu = map[string]int{"xenial": 1, "vivid": 2, "trusty": 3} 135 agentVersion := version.MustParse("2.9.31") 136 backend.model.agentVersion = &agentVersion 137 backend.model.name = "model-1" 138 backend.model.owner = names.NewUserTag("foo") 139 err := migration.SourcePrecheck( 140 backend, allAlivePresence(), allAlivePresence(), 141 func(names.ModelTag) (environscloudspec.CloudSpec, error) { 142 return environscloudspec.CloudSpec{Type: "lxd"}, nil 143 }, 144 ) 145 c.Assert(err.Error(), gc.Equals, ` 146 cannot migrate to controller due to issues: 147 "foo/model-1": 148 - unexpected upgrade series lock found 149 - the model hosts deprecated windows machine(s): win10(1) win7(2) 150 - the model hosts deprecated ubuntu machine(s): trusty(3) vivid(2) xenial(1)`[1:]) 151 } 152 153 func (*SourcePrecheckSuite) TestImportingModel(c *gc.C) { 154 backend := newFakeBackend() 155 backend.model.migrationMode = state.MigrationModeImporting 156 err := sourcePrecheck(backend) 157 c.Assert(err, gc.ErrorMatches, "model is being imported as part of another migration") 158 } 159 160 func (*SourcePrecheckSuite) TestCleanupsError(c *gc.C) { 161 backend := newFakeBackend() 162 backend.cleanupErr = errors.New("boom") 163 err := sourcePrecheck(backend) 164 c.Assert(err, gc.ErrorMatches, "checking cleanups: boom") 165 } 166 167 func (*SourcePrecheckSuite) TestCleanupsNeeded(c *gc.C) { 168 backend := newFakeBackend() 169 backend.cleanupNeeded = true 170 err := sourcePrecheck(backend) 171 c.Assert(err, gc.ErrorMatches, "cleanup needed") 172 } 173 174 func (s *SourcePrecheckSuite) TestIsUpgradingError(c *gc.C) { 175 backend := newFakeBackend() 176 backend.controllerBackend.isUpgradingErr = errors.New("boom") 177 err := sourcePrecheck(backend) 178 c.Assert(err, gc.ErrorMatches, "controller: checking for upgrades: boom") 179 } 180 181 func (s *SourcePrecheckSuite) TestIsUpgrading(c *gc.C) { 182 backend := newFakeBackend() 183 backend.controllerBackend.isUpgrading = true 184 err := sourcePrecheck(backend) 185 c.Assert(err, gc.ErrorMatches, "controller: upgrade in progress") 186 } 187 188 func (s *SourcePrecheckSuite) TestAgentVersionError(c *gc.C) { 189 s.checkAgentVersionError(c, sourcePrecheck) 190 } 191 192 func (s *SourcePrecheckSuite) TestMachineRequiresReboot(c *gc.C) { 193 s.checkRebootRequired(c, sourcePrecheck) 194 } 195 196 func (s *SourcePrecheckSuite) TestMachineVersionsDontMatch(c *gc.C) { 197 s.checkMachineVersionsDontMatch(c, sourcePrecheck) 198 } 199 200 func (s *SourcePrecheckSuite) TestDyingMachine(c *gc.C) { 201 backend := newBackendWithDyingMachine() 202 err := sourcePrecheck(backend) 203 c.Assert(err, gc.ErrorMatches, "machine 0 is dying") 204 } 205 206 func (s *SourcePrecheckSuite) TestNonStartedMachine(c *gc.C) { 207 backend := newBackendWithDownMachine() 208 err := sourcePrecheck(backend) 209 c.Assert(err.Error(), gc.Equals, "machine 0 agent not functioning at this time (down)") 210 } 211 212 func (s *SourcePrecheckSuite) TestProvisioningMachine(c *gc.C) { 213 err := sourcePrecheck(newBackendWithProvisioningMachine()) 214 c.Assert(err.Error(), gc.Equals, "machine 0 not running (allocating)") 215 } 216 217 func (s *SourcePrecheckSuite) TestDownMachineAgent(c *gc.C) { 218 backend := newHappyBackend() 219 modelPresence := downAgentPresence("machine-1") 220 controllerPresence := allAlivePresence() 221 err := migration.SourcePrecheck( 222 backend, modelPresence, controllerPresence, 223 func(names.ModelTag) (environscloudspec.CloudSpec, error) { 224 return environscloudspec.CloudSpec{Type: "foo"}, nil 225 }, 226 ) 227 c.Assert(err.Error(), gc.Equals, "machine 1 agent not functioning at this time (down)") 228 } 229 230 func (s *SourcePrecheckSuite) TestDyingApplication(c *gc.C) { 231 backend := &fakeBackend{ 232 apps: []migration.PrecheckApplication{ 233 &fakeApp{ 234 name: "foo", 235 life: state.Dying, 236 }, 237 }, 238 } 239 err := sourcePrecheck(backend) 240 c.Assert(err.Error(), gc.Equals, "application foo is dying") 241 } 242 243 func (s *SourcePrecheckSuite) TestWithPendingMinUnits(c *gc.C) { 244 backend := &fakeBackend{ 245 apps: []migration.PrecheckApplication{ 246 &fakeApp{ 247 name: "foo", 248 minunits: 2, 249 units: []migration.PrecheckUnit{&fakeUnit{name: "foo/0"}}, 250 }, 251 }, 252 } 253 err := sourcePrecheck(backend) 254 c.Assert(err.Error(), gc.Equals, "application foo is below its minimum units threshold") 255 } 256 257 func (s *SourcePrecheckSuite) TestUnitVersionsDontMatch(c *gc.C) { 258 backend := &fakeBackend{ 259 model: fakeModel{modelType: state.ModelTypeIAAS}, 260 apps: []migration.PrecheckApplication{ 261 &fakeApp{ 262 name: "foo", 263 units: []migration.PrecheckUnit{&fakeUnit{name: "foo/0"}}, 264 }, 265 &fakeApp{ 266 name: "bar", 267 units: []migration.PrecheckUnit{ 268 &fakeUnit{name: "bar/0"}, 269 &fakeUnit{name: "bar/1", version: version.MustParseBinary("1.2.4-ubuntu-ppc64")}, 270 }, 271 }, 272 }, 273 } 274 err := sourcePrecheck(backend) 275 c.Assert(err.Error(), gc.Equals, "unit bar/1 agent binaries don't match model (1.2.4 != 1.2.3)") 276 } 277 278 func (s *SourcePrecheckSuite) TestCAASModelNoUnitVersionCheck(c *gc.C) { 279 backend := &fakeBackend{ 280 model: fakeModel{modelType: state.ModelTypeCAAS}, 281 apps: []migration.PrecheckApplication{ 282 &fakeApp{ 283 name: "foo", 284 units: []migration.PrecheckUnit{&fakeUnit{name: "foo/0", noTools: true}}, 285 }, 286 }, 287 } 288 err := sourcePrecheck(backend) 289 c.Assert(err, jc.ErrorIsNil) 290 } 291 292 func (s *SourcePrecheckSuite) TestDeadUnit(c *gc.C) { 293 backend := &fakeBackend{ 294 apps: []migration.PrecheckApplication{ 295 &fakeApp{ 296 name: "foo", 297 units: []migration.PrecheckUnit{ 298 &fakeUnit{name: "foo/0", life: state.Dead}, 299 }, 300 }, 301 }, 302 } 303 err := sourcePrecheck(backend) 304 c.Assert(err.Error(), gc.Equals, "unit foo/0 is dead") 305 } 306 307 func (s *SourcePrecheckSuite) TestUnitExecuting(c *gc.C) { 308 backend := &fakeBackend{ 309 apps: []migration.PrecheckApplication{ 310 &fakeApp{ 311 name: "foo", 312 units: []migration.PrecheckUnit{ 313 &fakeUnit{name: "foo/0", agentStatus: status.Executing}, 314 }, 315 }, 316 }, 317 } 318 err := sourcePrecheck(backend) 319 c.Assert(err, jc.ErrorIsNil) 320 } 321 322 func (s *SourcePrecheckSuite) TestUnitNotIdle(c *gc.C) { 323 backend := &fakeBackend{ 324 apps: []migration.PrecheckApplication{ 325 &fakeApp{ 326 name: "foo", 327 units: []migration.PrecheckUnit{ 328 &fakeUnit{name: "foo/0", agentStatus: status.Failed}, 329 }, 330 }, 331 }, 332 } 333 err := sourcePrecheck(backend) 334 c.Assert(err.Error(), gc.Equals, "unit foo/0 not idle or executing (failed)") 335 } 336 337 func (s *SourcePrecheckSuite) TestUnitLost(c *gc.C) { 338 backend := newHappyBackend() 339 modelPresence := downAgentPresence("unit-foo-0") 340 controllerPresence := allAlivePresence() 341 err := migration.SourcePrecheck( 342 backend, modelPresence, controllerPresence, 343 func(names.ModelTag) (environscloudspec.CloudSpec, error) { 344 return environscloudspec.CloudSpec{Type: "foo"}, nil 345 }, 346 ) 347 c.Assert(err.Error(), gc.Equals, "unit foo/0 not idle or executing (lost)") 348 } 349 350 func (*SourcePrecheckSuite) TestDyingControllerModel(c *gc.C) { 351 backend := newFakeBackend() 352 backend.controllerBackend.model.life = state.Dying 353 err := sourcePrecheck(backend) 354 c.Assert(err, gc.ErrorMatches, "controller: model is dying") 355 } 356 357 func (s *SourcePrecheckSuite) TestControllerAgentVersionError(c *gc.C) { 358 backend := newFakeBackend() 359 backend.controllerBackend.agentVersionErr = errors.New("boom") 360 err := sourcePrecheck(backend) 361 c.Assert(err, gc.ErrorMatches, "controller: retrieving model version: boom") 362 363 } 364 365 func (s *SourcePrecheckSuite) TestControllerMachineVersionsDontMatch(c *gc.C) { 366 backend := newFakeBackend() 367 backend.controllerBackend = newBackendWithMismatchingTools() 368 err := sourcePrecheck(backend) 369 c.Assert(err, gc.ErrorMatches, "controller: machine . agent binaries don't match model.+") 370 } 371 372 func (s *SourcePrecheckSuite) TestControllerMachineRequiresReboot(c *gc.C) { 373 backend := newFakeBackend() 374 backend.controllerBackend = newBackendWithRebootingMachine() 375 err := sourcePrecheck(backend) 376 c.Assert(err, gc.ErrorMatches, "controller: machine 0 is scheduled to reboot") 377 } 378 379 func (s *SourcePrecheckSuite) TestDyingControllerMachine(c *gc.C) { 380 backend := &fakeBackend{ 381 controllerBackend: newBackendWithDyingMachine(), 382 } 383 err := sourcePrecheck(backend) 384 c.Assert(err, gc.ErrorMatches, "controller: machine 0 is dying") 385 } 386 387 func (s *SourcePrecheckSuite) TestNonStartedControllerMachine(c *gc.C) { 388 backend := &fakeBackend{ 389 controllerBackend: newBackendWithDownMachine(), 390 } 391 err := sourcePrecheck(backend) 392 c.Assert(err.Error(), gc.Equals, "controller: machine 0 agent not functioning at this time (down)") 393 } 394 395 func (s *SourcePrecheckSuite) TestProvisioningControllerMachine(c *gc.C) { 396 backend := &fakeBackend{ 397 controllerBackend: newBackendWithProvisioningMachine(), 398 } 399 err := sourcePrecheck(backend) 400 c.Assert(err.Error(), gc.Equals, "controller: machine 0 not running (allocating)") 401 } 402 403 func (s *SourcePrecheckSuite) TestUnitsAllInScope(c *gc.C) { 404 backend := newHappyBackend() 405 backend.relations = []migration.PrecheckRelation{&fakeRelation{ 406 endpoints: []state.Endpoint{ 407 {ApplicationName: "foo"}, 408 {ApplicationName: "bar"}, 409 }, 410 relUnits: map[string]*fakeRelationUnit{ 411 "foo/0": {valid: true, inScope: true}, 412 "bar/0": {valid: true, inScope: true}, 413 "bar/1": {valid: true, inScope: true}, 414 }, 415 }} 416 err := sourcePrecheck(backend) 417 c.Assert(err, jc.ErrorIsNil) 418 } 419 420 func (s *SourcePrecheckSuite) TestSubordinatesNotYetInScope(c *gc.C) { 421 backend := newHappyBackend() 422 backend.relations = []migration.PrecheckRelation{&fakeRelation{ 423 key: "foo:db bar:db", 424 endpoints: []state.Endpoint{ 425 {ApplicationName: "foo"}, 426 {ApplicationName: "bar"}, 427 }, 428 relUnits: map[string]*fakeRelationUnit{ 429 "foo/0": {unitName: "foo/0", valid: true, inScope: true}, 430 "bar/0": {unitName: "bar/0", valid: true, inScope: true}, 431 "bar/1": {unitName: "bar/1", valid: true, inScope: false}, 432 }, 433 }} 434 err := sourcePrecheck(backend) 435 c.Assert(err, gc.ErrorMatches, `unit bar/1 hasn't joined relation "foo:db bar:db" yet`) 436 } 437 438 func (s *SourcePrecheckSuite) TestSubordinatesInvalidUnitsNotYetInScope(c *gc.C) { 439 backend := newHappyBackend() 440 backend.relations = []migration.PrecheckRelation{&fakeRelation{ 441 key: "foo:db bar:db", 442 endpoints: []state.Endpoint{ 443 {ApplicationName: "foo"}, 444 {ApplicationName: "bar"}, 445 }, 446 relUnits: map[string]*fakeRelationUnit{ 447 "foo/0": {valid: true, inScope: true}, 448 "bar/0": {valid: true, inScope: true}, 449 "bar/1": {valid: false, inScope: false}, 450 }, 451 }} 452 err := sourcePrecheck(backend) 453 c.Assert(err, jc.ErrorIsNil) 454 } 455 456 func (s *SourcePrecheckSuite) TestCrossModelUnitsNotYetInScope(c *gc.C) { 457 backend := newHappyBackend() 458 backend.relations = []migration.PrecheckRelation{&fakeRelation{ 459 key: "foo:db remote-mysql:db", 460 endpoints: []state.Endpoint{ 461 {ApplicationName: "foo"}, 462 {ApplicationName: "remote-mysql"}, 463 }, 464 relUnits: map[string]*fakeRelationUnit{ 465 "foo/0": {unitName: "foo/0", valid: true, inScope: true}, 466 }, 467 remoteAppName: "remote-mysql", 468 remoteRelUnits: map[string][]*fakeRelationUnit{ 469 "remote-mysql": {{unitName: "remote-mysql/0", valid: true, inScope: false}}, 470 }, 471 }} 472 err := sourcePrecheck(backend) 473 c.Assert(err, gc.ErrorMatches, `unit remote-mysql/0 hasn't joined relation "foo:db remote-mysql:db" yet`) 474 } 475 476 type TargetPrecheckSuite struct { 477 precheckBaseSuite 478 modelInfo coremigration.ModelInfo 479 } 480 481 var _ = gc.Suite(&TargetPrecheckSuite{}) 482 483 func (s *TargetPrecheckSuite) SetUpTest(c *gc.C) { 484 s.modelInfo = coremigration.ModelInfo{ 485 UUID: modelUUID, 486 Owner: modelOwner, 487 Name: modelName, 488 AgentVersion: backendVersion, 489 } 490 } 491 492 func (s *TargetPrecheckSuite) runPrecheck(backend migration.PrecheckBackend) error { 493 return migration.TargetPrecheck(backend, nil, s.modelInfo, allAlivePresence()) 494 } 495 496 func (s *TargetPrecheckSuite) TestSuccess(c *gc.C) { 497 err := s.runPrecheck(newHappyBackend()) 498 c.Assert(err, jc.ErrorIsNil) 499 } 500 501 func (s *TargetPrecheckSuite) TestModelVersionAheadOfTarget(c *gc.C) { 502 backend := newFakeBackend() 503 504 sourceVersion := backendVersion 505 sourceVersion.Patch++ 506 s.modelInfo.AgentVersion = sourceVersion 507 508 err := s.runPrecheck(backend) 509 c.Assert(err.Error(), gc.Equals, 510 `model has higher version than target controller (1.2.4 > 1.2.3)`) 511 } 512 513 func (s *TargetPrecheckSuite) TestModelMinimumVersion(c *gc.C) { 514 backend := newFakeBackend() 515 516 origBackendBinary := backendVersionBinary 517 origBackend := backendVersion 518 backendVersionBinary = version.MustParseBinary("3.0.0-ubuntu-amd64") 519 backendVersion = backendVersionBinary.Number 520 defer func() { 521 backendVersionBinary = origBackendBinary 522 backendVersion = origBackend 523 }() 524 525 s.modelInfo.AgentVersion = version.MustParse("2.8.0") 526 err := s.runPrecheck(backend) 527 c.Assert(err.Error(), gc.Equals, 528 `model must be upgraded to at least version 2.9.43 before being migrated to a controller with version 3.0.0`) 529 530 s.modelInfo.AgentVersion = version.MustParse("2.9.43") 531 err = s.runPrecheck(backend) 532 c.Assert(err, jc.ErrorIsNil) 533 } 534 535 func (s *TargetPrecheckSuite) TestSourceControllerMajorAhead(c *gc.C) { 536 backend := newFakeBackend() 537 538 sourceVersion := backendVersion 539 sourceVersion.Major++ 540 sourceVersion.Minor = 0 541 sourceVersion.Patch = 0 542 s.modelInfo.ControllerAgentVersion = sourceVersion 543 544 err := s.runPrecheck(backend) 545 c.Assert(err.Error(), gc.Equals, 546 `source controller has higher version than target controller (2.0.0 > 1.2.3)`) 547 } 548 549 func (s *TargetPrecheckSuite) TestSourceControllerMinorAhead(c *gc.C) { 550 backend := newFakeBackend() 551 552 sourceVersion := backendVersion 553 sourceVersion.Minor++ 554 sourceVersion.Patch = 0 555 s.modelInfo.ControllerAgentVersion = sourceVersion 556 557 err := s.runPrecheck(backend) 558 c.Assert(err.Error(), gc.Equals, 559 `source controller has higher version than target controller (1.3.0 > 1.2.3)`) 560 } 561 562 func (s *TargetPrecheckSuite) TestSourceControllerPatchAhead(c *gc.C) { 563 backend := newFakeBackend() 564 565 sourceVersion := backendVersion 566 sourceVersion.Patch++ 567 s.modelInfo.ControllerAgentVersion = sourceVersion 568 569 c.Assert(s.runPrecheck(backend), jc.ErrorIsNil) 570 } 571 572 func (s *TargetPrecheckSuite) TestSourceControllerBuildAhead(c *gc.C) { 573 backend := newFakeBackend() 574 575 sourceVersion := backendVersion 576 sourceVersion.Build++ 577 s.modelInfo.ControllerAgentVersion = sourceVersion 578 579 c.Assert(s.runPrecheck(backend), jc.ErrorIsNil) 580 } 581 582 func (s *TargetPrecheckSuite) TestSourceControllerTagMismatch(c *gc.C) { 583 backend := newFakeBackend() 584 585 sourceVersion := backendVersion 586 sourceVersion.Tag = "alpha" 587 s.modelInfo.ControllerAgentVersion = sourceVersion 588 589 c.Assert(s.runPrecheck(backend), jc.ErrorIsNil) 590 } 591 592 func (s *TargetPrecheckSuite) TestDying(c *gc.C) { 593 backend := newFakeBackend() 594 backend.model.life = state.Dying 595 err := s.runPrecheck(backend) 596 c.Assert(err, gc.ErrorMatches, "model is dying") 597 } 598 599 func (s *TargetPrecheckSuite) TestMachineRequiresReboot(c *gc.C) { 600 s.checkRebootRequired(c, s.runPrecheck) 601 } 602 603 func (s *TargetPrecheckSuite) TestAgentVersionError(c *gc.C) { 604 s.checkAgentVersionError(c, s.runPrecheck) 605 } 606 607 func (s *TargetPrecheckSuite) TestIsUpgradingError(c *gc.C) { 608 backend := &fakeBackend{ 609 isUpgradingErr: errors.New("boom"), 610 } 611 err := s.runPrecheck(backend) 612 c.Assert(err, gc.ErrorMatches, "checking for upgrades: boom") 613 } 614 615 func (s *TargetPrecheckSuite) TestIsUpgrading(c *gc.C) { 616 backend := &fakeBackend{ 617 isUpgrading: true, 618 } 619 err := s.runPrecheck(backend) 620 c.Assert(err, gc.ErrorMatches, "upgrade in progress") 621 } 622 623 func (s *TargetPrecheckSuite) TestIsMigrationActiveError(c *gc.C) { 624 backend := &fakeBackend{migrationActiveErr: errors.New("boom")} 625 err := s.runPrecheck(backend) 626 c.Assert(err, gc.ErrorMatches, "checking for active migration: boom") 627 } 628 629 func (s *TargetPrecheckSuite) TestIsMigrationActive(c *gc.C) { 630 backend := &fakeBackend{migrationActive: true} 631 err := s.runPrecheck(backend) 632 c.Assert(err, gc.ErrorMatches, "model is being migrated out of target controller") 633 } 634 635 func (s *TargetPrecheckSuite) TestMachineVersionsDontMatch(c *gc.C) { 636 s.checkMachineVersionsDontMatch(c, s.runPrecheck) 637 } 638 639 func (s *TargetPrecheckSuite) TestDyingMachine(c *gc.C) { 640 backend := newBackendWithDyingMachine() 641 err := s.runPrecheck(backend) 642 c.Assert(err, gc.ErrorMatches, "machine 0 is dying") 643 } 644 645 func (s *TargetPrecheckSuite) TestNonStartedMachine(c *gc.C) { 646 backend := newBackendWithDownMachine() 647 err := s.runPrecheck(backend) 648 c.Assert(err.Error(), gc.Equals, "machine 0 agent not functioning at this time (down)") 649 } 650 651 func (s *TargetPrecheckSuite) TestProvisioningMachine(c *gc.C) { 652 backend := newBackendWithProvisioningMachine() 653 err := s.runPrecheck(backend) 654 c.Assert(err.Error(), gc.Equals, "machine 0 not running (allocating)") 655 } 656 657 func (s *TargetPrecheckSuite) TestDownMachineAgent(c *gc.C) { 658 backend := newHappyBackend() 659 modelPresence := downAgentPresence("machine-1") 660 err := migration.TargetPrecheck(backend, nil, s.modelInfo, modelPresence) 661 c.Assert(err.Error(), gc.Equals, "machine 1 agent not functioning at this time (down)") 662 } 663 664 func (s *TargetPrecheckSuite) TestModelNameAlreadyInUse(c *gc.C) { 665 pool := &fakePool{ 666 models: []migration.PrecheckModel{ 667 &fakeModel{ 668 uuid: "uuid", 669 name: modelName, 670 modelType: state.ModelTypeIAAS, 671 owner: modelOwner, 672 }, 673 }, 674 } 675 backend := newFakeBackend() 676 backend.models = pool.uuids() 677 err := migration.TargetPrecheck(backend, pool, s.modelInfo, allAlivePresence()) 678 c.Assert(err, gc.ErrorMatches, "model named \"model-name\" already exists") 679 } 680 681 func (s *TargetPrecheckSuite) TestModelNameOverlapOkForDifferentOwner(c *gc.C) { 682 pool := &fakePool{ 683 models: []migration.PrecheckModel{ 684 &fakeModel{ 685 name: modelName, 686 modelType: state.ModelTypeIAAS, 687 owner: names.NewUserTag("someone.else"), 688 }, 689 }, 690 } 691 backend := newFakeBackend() 692 backend.models = pool.uuids() 693 err := migration.TargetPrecheck(backend, pool, s.modelInfo, allAlivePresence()) 694 c.Assert(err, jc.ErrorIsNil) 695 } 696 697 func (s *TargetPrecheckSuite) TestUUIDAlreadyExists(c *gc.C) { 698 pool := &fakePool{ 699 models: []migration.PrecheckModel{ 700 &fakeModel{uuid: modelUUID, modelType: state.ModelTypeIAAS}, 701 }, 702 } 703 backend := newFakeBackend() 704 backend.models = pool.uuids() 705 err := migration.TargetPrecheck(backend, pool, s.modelInfo, allAlivePresence()) 706 c.Assert(err.Error(), gc.Equals, "model with same UUID already exists (model-uuid)") 707 } 708 709 func (s *TargetPrecheckSuite) TestUUIDAlreadyExistsButImporting(c *gc.C) { 710 pool := &fakePool{ 711 models: []migration.PrecheckModel{ 712 &fakeModel{ 713 uuid: modelUUID, 714 modelType: state.ModelTypeIAAS, 715 migrationMode: state.MigrationModeImporting, 716 }, 717 }, 718 } 719 backend := newFakeBackend() 720 backend.models = pool.uuids() 721 err := migration.TargetPrecheck(backend, pool, s.modelInfo, allAlivePresence()) 722 c.Assert(err, jc.ErrorIsNil) 723 } 724 725 type precheckRunner func(migration.PrecheckBackend) error 726 727 type precheckBaseSuite struct { 728 testing.BaseSuite 729 } 730 731 func (*precheckBaseSuite) checkRebootRequired(c *gc.C, runPrecheck precheckRunner) { 732 err := runPrecheck(newBackendWithRebootingMachine()) 733 c.Assert(err, gc.ErrorMatches, "machine 0 is scheduled to reboot") 734 } 735 736 func (*precheckBaseSuite) checkAgentVersionError(c *gc.C, runPrecheck precheckRunner) { 737 backend := &fakeBackend{ 738 agentVersionErr: errors.New("boom"), 739 } 740 err := runPrecheck(backend) 741 c.Assert(err, gc.ErrorMatches, "retrieving model version: boom") 742 } 743 744 func (*precheckBaseSuite) checkMachineVersionsDontMatch(c *gc.C, runPrecheck precheckRunner) { 745 err := runPrecheck(newBackendWithMismatchingTools()) 746 c.Assert(err.Error(), gc.Equals, "machine 1 agent binaries don't match model (1.3.1 != 1.2.3)") 747 } 748 749 func newHappyBackend() *fakeBackend { 750 return &fakeBackend{ 751 machines: []migration.PrecheckMachine{ 752 &fakeMachine{id: "0"}, 753 &fakeMachine{id: "1"}, 754 }, 755 apps: []migration.PrecheckApplication{ 756 &fakeApp{ 757 name: "foo", 758 units: []migration.PrecheckUnit{&fakeUnit{name: "foo/0"}}, 759 }, 760 &fakeApp{ 761 name: "bar", 762 units: []migration.PrecheckUnit{ 763 &fakeUnit{name: "bar/0"}, 764 &fakeUnit{name: "bar/1"}, 765 }, 766 }, 767 }, 768 } 769 } 770 771 func newBackendWithMismatchingTools() *fakeBackend { 772 return &fakeBackend{ 773 machines: []migration.PrecheckMachine{ 774 &fakeMachine{id: "0"}, 775 &fakeMachine{id: "1", version: version.MustParseBinary("1.3.1-ubuntu-amd64")}, 776 }, 777 } 778 } 779 780 func newBackendWithRebootingMachine() *fakeBackend { 781 return &fakeBackend{ 782 machines: []migration.PrecheckMachine{ 783 &fakeMachine{id: "0", rebootAction: state.ShouldReboot}, 784 }, 785 } 786 } 787 788 func newBackendWithDyingMachine() *fakeBackend { 789 return &fakeBackend{ 790 machines: []migration.PrecheckMachine{ 791 &fakeMachine{id: "0", life: state.Dying}, 792 &fakeMachine{id: "1"}, 793 }, 794 } 795 } 796 797 func newBackendWithDownMachine() *fakeBackend { 798 return &fakeBackend{ 799 machines: []migration.PrecheckMachine{ 800 &fakeMachine{id: "0", status: status.Down}, 801 &fakeMachine{id: "1"}, 802 }, 803 } 804 } 805 806 func newBackendWithProvisioningMachine() *fakeBackend { 807 return &fakeBackend{ 808 machines: []migration.PrecheckMachine{ 809 &fakeMachine{id: "0", instanceStatus: status.Provisioning}, 810 &fakeMachine{id: "1"}, 811 }, 812 } 813 } 814 815 func newFakeBackend() *fakeBackend { 816 return &fakeBackend{ 817 controllerBackend: &fakeBackend{}, 818 } 819 } 820 821 type fakeBackend struct { 822 agentVersionErr error 823 824 model fakeModel 825 models []string 826 827 cleanupNeeded bool 828 cleanupErr error 829 830 isUpgrading bool 831 isUpgradingErr error 832 833 migrationActive bool 834 migrationActiveErr error 835 836 machines []migration.PrecheckMachine 837 allMachinesErr error 838 839 apps []migration.PrecheckApplication 840 allAppsErr error 841 842 relations []migration.PrecheckRelation 843 allRelsErr error 844 845 credentials state.Credential 846 credentialsErr error 847 848 controllerBackend *fakeBackend 849 850 hasUpgradeSeriesLocks *bool 851 hasUpgradeSeriesLocksErr error 852 853 machineCountForSeriesWin map[string]int 854 machineCountForSeriesUbuntu map[string]int 855 machineCountForSeriesErr error 856 857 mongoCurrentStatus *replicaset.Status 858 mongoCurrentStatusErr error 859 } 860 861 func (b *fakeBackend) Model() (migration.PrecheckModel, error) { 862 return &b.model, nil 863 } 864 865 func (b *fakeBackend) AllModelUUIDs() ([]string, error) { 866 return b.models, nil 867 } 868 869 func (b *fakeBackend) NeedsCleanup() (bool, error) { 870 return b.cleanupNeeded, b.cleanupErr 871 } 872 873 func (b *fakeBackend) AgentVersion() (version.Number, error) { 874 return backendVersion, b.agentVersionErr 875 } 876 877 func (b *fakeBackend) IsUpgrading() (bool, error) { 878 return b.isUpgrading, b.isUpgradingErr 879 } 880 881 func (b *fakeBackend) IsMigrationActive(string) (bool, error) { 882 return b.migrationActive, b.migrationActiveErr 883 } 884 885 func (b *fakeBackend) CloudCredential(_ names.CloudCredentialTag) (state.Credential, error) { 886 return b.credentials, b.credentialsErr 887 } 888 889 func (b *fakeBackend) AllMachines() ([]migration.PrecheckMachine, error) { 890 return b.machines, b.allMachinesErr 891 } 892 893 func (b *fakeBackend) AllApplications() ([]migration.PrecheckApplication, error) { 894 return b.apps, b.allAppsErr 895 } 896 897 func (b *fakeBackend) AllRelations() ([]migration.PrecheckRelation, error) { 898 return b.relations, b.allRelsErr 899 } 900 901 func (b *fakeBackend) ControllerBackend() (migration.PrecheckBackend, error) { 902 if b.controllerBackend == nil { 903 return b, nil 904 } 905 return b.controllerBackend, nil 906 } 907 908 func (b *fakeBackend) HasUpgradeSeriesLocks() (bool, error) { 909 if b.hasUpgradeSeriesLocks == nil { 910 return false, nil 911 } 912 return *b.hasUpgradeSeriesLocks, b.hasUpgradeSeriesLocksErr 913 } 914 915 func (b *fakeBackend) MachineCountForBase(base ...state.Base) (map[string]int, error) { 916 if strings.HasPrefix(base[0].Channel, "win") { 917 if b.machineCountForSeriesWin == nil { 918 return nil, nil 919 } 920 return b.machineCountForSeriesWin, b.machineCountForSeriesErr 921 } 922 if b.machineCountForSeriesUbuntu == nil { 923 return nil, nil 924 } 925 return b.machineCountForSeriesUbuntu, b.machineCountForSeriesErr 926 } 927 928 func (b *fakeBackend) MongoCurrentStatus() (*replicaset.Status, error) { 929 if b.mongoCurrentStatus == nil { 930 return &replicaset.Status{}, nil 931 } 932 return b.mongoCurrentStatus, b.mongoCurrentStatusErr 933 } 934 935 func (b *fakeBackend) AllCharmURLs() ([]*string, error) { 936 return nil, errors.NotFoundf("charms") 937 } 938 939 type fakePool struct { 940 models []migration.PrecheckModel 941 } 942 943 func (p *fakePool) uuids() []string { 944 out := make([]string, len(p.models)) 945 for i, model := range p.models { 946 out[i] = model.UUID() 947 } 948 return out 949 } 950 951 func (p *fakePool) GetModel(uuid string) (migration.PrecheckModel, func(), error) { 952 for _, model := range p.models { 953 if model.UUID() == uuid { 954 return model, func() {}, nil 955 } 956 } 957 return nil, nil, errors.NotFoundf("model %v", uuid) 958 } 959 960 type fakeModel struct { 961 uuid string 962 name string 963 owner names.UserTag 964 life state.Life 965 modelType state.ModelType 966 migrationMode state.MigrationMode 967 credential string 968 969 agentVersion *version.Number 970 } 971 972 func (m *fakeModel) Type() state.ModelType { 973 return m.modelType 974 } 975 976 func (m *fakeModel) UUID() string { 977 return m.uuid 978 } 979 980 func (m *fakeModel) Name() string { 981 return m.name 982 } 983 984 func (m *fakeModel) Owner() names.UserTag { 985 return m.owner 986 } 987 988 func (m *fakeModel) Life() state.Life { 989 return m.life 990 } 991 992 func (m *fakeModel) MigrationMode() state.MigrationMode { 993 return m.migrationMode 994 } 995 996 func (m *fakeModel) AgentVersion() (version.Number, error) { 997 if m.agentVersion == nil { 998 return version.MustParse("2.9.32"), nil 999 } 1000 return *m.agentVersion, nil 1001 } 1002 1003 func (m *fakeModel) CloudCredentialTag() (names.CloudCredentialTag, bool) { 1004 if names.IsValidCloudCredential(m.credential) { 1005 return names.NewCloudCredentialTag(m.credential), true 1006 } 1007 return names.CloudCredentialTag{}, false 1008 } 1009 1010 type fakeMachine struct { 1011 id string 1012 version version.Binary 1013 life state.Life 1014 status status.Status 1015 instanceStatus status.Status 1016 rebootAction state.RebootAction 1017 } 1018 1019 func (m *fakeMachine) Id() string { 1020 return m.id 1021 } 1022 1023 func (m *fakeMachine) Life() state.Life { 1024 return m.life 1025 } 1026 1027 func (m *fakeMachine) Status() (status.StatusInfo, error) { 1028 s := m.status 1029 if s == "" { 1030 // Avoid the need to specify this everywhere. 1031 s = status.Started 1032 } 1033 return status.StatusInfo{Status: s}, nil 1034 } 1035 1036 func (m *fakeMachine) InstanceStatus() (status.StatusInfo, error) { 1037 s := m.instanceStatus 1038 if s == "" { 1039 // Avoid the need to specify this everywhere. 1040 s = status.Running 1041 } 1042 return status.StatusInfo{Status: s}, nil 1043 } 1044 1045 func (m *fakeMachine) AgentTools() (*tools.Tools, error) { 1046 // Avoid having to specify the version when it's supposed to match 1047 // the model config. 1048 v := m.version 1049 if v.Compare(version.Zero) == 0 { 1050 v = backendVersionBinary 1051 } 1052 return &tools.Tools{ 1053 Version: v, 1054 }, nil 1055 } 1056 1057 func (m *fakeMachine) ShouldRebootOrShutdown() (state.RebootAction, error) { 1058 if m.rebootAction == "" { 1059 return state.ShouldDoNothing, nil 1060 } 1061 return m.rebootAction, nil 1062 } 1063 1064 type fakeApp struct { 1065 name string 1066 life state.Life 1067 charmURL string 1068 units []migration.PrecheckUnit 1069 minunits int 1070 } 1071 1072 func (a *fakeApp) Name() string { 1073 return a.name 1074 } 1075 1076 func (a *fakeApp) Life() state.Life { 1077 return a.life 1078 } 1079 1080 func (a *fakeApp) CharmURL() (*string, bool) { 1081 url := a.charmURL 1082 if url == "" { 1083 url = "ch:foo-1" 1084 } 1085 return &url, false 1086 } 1087 1088 func (a *fakeApp) AllUnits() ([]migration.PrecheckUnit, error) { 1089 return a.units, nil 1090 } 1091 1092 func (a *fakeApp) MinUnits() int { 1093 return a.minunits 1094 } 1095 1096 type fakeUnit struct { 1097 name string 1098 version version.Binary 1099 noTools bool 1100 life state.Life 1101 charmURL string 1102 agentStatus status.Status 1103 } 1104 1105 func (u *fakeUnit) Name() string { 1106 return u.name 1107 } 1108 1109 func (u *fakeUnit) AgentTools() (*tools.Tools, error) { 1110 if u.noTools { 1111 return nil, errors.NotFoundf("tools") 1112 } 1113 // Avoid having to specify the version when it's supposed to match 1114 // the model config. 1115 v := u.version 1116 if v.Compare(version.Zero) == 0 { 1117 v = backendVersionBinary 1118 } 1119 return &tools.Tools{ 1120 Version: v, 1121 }, nil 1122 } 1123 1124 func (u *fakeUnit) Life() state.Life { 1125 return u.life 1126 } 1127 1128 func (u *fakeUnit) ShouldBeAssigned() bool { 1129 return true 1130 } 1131 1132 func (u *fakeUnit) CharmURL() *string { 1133 url := u.charmURL 1134 if url == "" { 1135 url = "ch:foo-1" 1136 } 1137 return &url 1138 } 1139 1140 func (u *fakeUnit) AgentStatus() (status.StatusInfo, error) { 1141 s := u.agentStatus 1142 if s == "" { 1143 // Avoid the need to specify this everywhere. 1144 s = status.Idle 1145 } 1146 return status.StatusInfo{Status: s}, nil 1147 } 1148 1149 func (u *fakeUnit) Status() (status.StatusInfo, error) { 1150 return status.StatusInfo{Status: status.Idle}, nil 1151 } 1152 1153 func (u *fakeUnit) IsSidecar() (bool, error) { 1154 return false, nil 1155 } 1156 1157 type fakeRelation struct { 1158 key string 1159 endpoints []state.Endpoint 1160 relUnits map[string]*fakeRelationUnit 1161 remoteAppName string 1162 remoteRelUnits map[string][]*fakeRelationUnit 1163 unitErr error 1164 } 1165 1166 func (r *fakeRelation) String() string { 1167 return r.key 1168 } 1169 1170 func (r *fakeRelation) Endpoints() []state.Endpoint { 1171 return r.endpoints 1172 } 1173 1174 func (r *fakeRelation) AllRemoteUnits(appName string) ([]migration.PrecheckRelationUnit, error) { 1175 out := make([]migration.PrecheckRelationUnit, len(r.remoteRelUnits[appName])) 1176 for i, ru := range r.remoteRelUnits[appName] { 1177 out[i] = ru 1178 } 1179 return out, nil 1180 } 1181 1182 func (r *fakeRelation) RemoteApplication() (string, bool, error) { 1183 return r.remoteAppName, r.remoteAppName != "", nil 1184 } 1185 1186 func (r *fakeRelation) Unit(u migration.PrecheckUnit) (migration.PrecheckRelationUnit, error) { 1187 return r.relUnits[u.Name()], r.unitErr 1188 } 1189 1190 type fakeRelationUnit struct { 1191 unitName string 1192 valid, inScope bool 1193 validErr, scopeErr error 1194 } 1195 1196 func (ru *fakeRelationUnit) UnitName() string { 1197 return ru.unitName 1198 } 1199 1200 func (ru *fakeRelationUnit) Valid() (bool, error) { 1201 return ru.valid, ru.validErr 1202 } 1203 1204 func (ru *fakeRelationUnit) InScope() (bool, error) { 1205 return ru.inScope, ru.scopeErr 1206 } 1207 1208 func allAlivePresence() migration.ModelPresence { 1209 return &fakePresence{} 1210 } 1211 1212 func downAgentPresence(agent ...string) migration.ModelPresence { 1213 m := make(map[string]presence.Status) 1214 for _, a := range agent { 1215 m[a] = presence.Missing 1216 } 1217 return &fakePresence{ 1218 agentStatus: m, 1219 } 1220 } 1221 1222 type fakePresence struct { 1223 agentStatus map[string]presence.Status 1224 } 1225 1226 func (f *fakePresence) AgentStatus(agent string) (presence.Status, error) { 1227 if value, found := f.agentStatus[agent]; found { 1228 return value, nil 1229 } 1230 return presence.Alive, nil 1231 }