github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/migrationmaster/worker_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package migrationmaster_test 5 6 import ( 7 "reflect" 8 "time" 9 10 "github.com/juju/errors" 11 jujutesting "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/utils" 14 "github.com/juju/version" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/names.v2" 17 "gopkg.in/macaroon.v1" 18 19 "github.com/juju/juju/api" 20 "github.com/juju/juju/apiserver/params" 21 coremigration "github.com/juju/juju/core/migration" 22 "github.com/juju/juju/migration" 23 coretesting "github.com/juju/juju/testing" 24 "github.com/juju/juju/watcher" 25 "github.com/juju/juju/worker" 26 "github.com/juju/juju/worker/fortress" 27 "github.com/juju/juju/worker/migrationmaster" 28 "github.com/juju/juju/worker/workertest" 29 ) 30 31 type Suite struct { 32 coretesting.BaseSuite 33 clock *jujutesting.Clock 34 stub *jujutesting.Stub 35 connection *stubConnection 36 connectionErr error 37 facade *stubMasterFacade 38 config migrationmaster.Config 39 } 40 41 var _ = gc.Suite(&Suite{}) 42 43 var ( 44 fakeModelBytes = []byte("model") 45 targetControllerTag = names.NewControllerTag("controller-uuid") 46 modelUUID = "model-uuid" 47 modelTag = names.NewModelTag(modelUUID) 48 modelName = "model-name" 49 ownerTag = names.NewUserTag("owner") 50 modelVersion = version.MustParse("1.2.4") 51 52 // Define stub calls that commonly appear in tests here to allow reuse. 53 apiOpenControllerCall = jujutesting.StubCall{ 54 "apiOpen", 55 []interface{}{ 56 &api.Info{ 57 Addrs: []string{"1.2.3.4:5"}, 58 CACert: "cert", 59 Tag: names.NewUserTag("admin"), 60 Password: "secret", 61 }, 62 migration.ControllerDialOpts(), 63 }, 64 } 65 apiOpenModelCall = jujutesting.StubCall{ 66 "apiOpen", 67 []interface{}{ 68 &api.Info{ 69 Addrs: []string{"1.2.3.4:5"}, 70 CACert: "cert", 71 Tag: names.NewUserTag("admin"), 72 Password: "secret", 73 ModelTag: modelTag, 74 }, 75 migration.ControllerDialOpts(), 76 }, 77 } 78 importCall = jujutesting.StubCall{ 79 "MigrationTarget.Import", 80 []interface{}{ 81 params.SerializedModel{Bytes: fakeModelBytes}, 82 }, 83 } 84 activateCall = jujutesting.StubCall{ 85 "MigrationTarget.Activate", 86 []interface{}{ 87 params.ModelArgs{ModelTag: modelTag.String()}, 88 }, 89 } 90 apiCloseCall = jujutesting.StubCall{"Connection.Close", nil} 91 abortCall = jujutesting.StubCall{ 92 "MigrationTarget.Abort", 93 []interface{}{ 94 params.ModelArgs{ModelTag: modelTag.String()}, 95 }, 96 } 97 watchStatusLockdownCalls = []jujutesting.StubCall{ 98 {"facade.Watch", nil}, 99 {"facade.MigrationStatus", nil}, 100 {"guard.Lockdown", nil}, 101 } 102 prechecksCalls = []jujutesting.StubCall{ 103 {"facade.Prechecks", nil}, 104 {"facade.ModelInfo", nil}, 105 apiOpenControllerCall, 106 {"MigrationTarget.Prechecks", []interface{}{params.MigrationModelInfo{ 107 UUID: modelUUID, 108 Name: modelName, 109 OwnerTag: ownerTag.String(), 110 AgentVersion: modelVersion, 111 }}}, 112 apiCloseCall, 113 } 114 abortCalls = []jujutesting.StubCall{ 115 {"facade.SetPhase", []interface{}{coremigration.ABORT}}, 116 apiOpenControllerCall, 117 abortCall, 118 apiCloseCall, 119 {"facade.SetPhase", []interface{}{coremigration.ABORTDONE}}, 120 } 121 ) 122 123 func (s *Suite) SetUpTest(c *gc.C) { 124 s.BaseSuite.SetUpTest(c) 125 126 s.clock = jujutesting.NewClock(time.Now()) 127 s.stub = new(jujutesting.Stub) 128 s.connection = &stubConnection{ 129 stub: s.stub, 130 controllerTag: targetControllerTag, 131 } 132 s.connectionErr = nil 133 134 s.facade = newStubMasterFacade(s.stub, s.clock.Now()) 135 136 // The default worker Config used by most of the tests. Tests may 137 // tweak parts of this as needed. 138 s.config = migrationmaster.Config{ 139 ModelUUID: utils.MustNewUUID().String(), 140 Facade: s.facade, 141 Guard: newStubGuard(s.stub), 142 APIOpen: s.apiOpen, 143 UploadBinaries: nullUploadBinaries, 144 CharmDownloader: fakeCharmDownloader, 145 ToolsDownloader: fakeToolsDownloader, 146 Clock: s.clock, 147 } 148 } 149 150 func (s *Suite) apiOpen(info *api.Info, dialOpts api.DialOpts) (api.Connection, error) { 151 s.stub.AddCall("apiOpen", info, dialOpts) 152 if s.connectionErr != nil { 153 return nil, s.connectionErr 154 } 155 return s.connection, nil 156 } 157 158 func (s *Suite) makeStatus(phase coremigration.Phase) coremigration.MigrationStatus { 159 return coremigration.MigrationStatus{ 160 MigrationId: "model-uuid:2", 161 ModelUUID: "model-uuid", 162 Phase: phase, 163 PhaseChangedTime: s.clock.Now(), 164 TargetInfo: coremigration.TargetInfo{ 165 ControllerTag: targetControllerTag, 166 Addrs: []string{"1.2.3.4:5"}, 167 CACert: "cert", 168 AuthTag: names.NewUserTag("admin"), 169 Password: "secret", 170 }, 171 } 172 } 173 174 func (s *Suite) TestSuccessfulMigration(c *gc.C) { 175 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 176 s.facade.queueMinionReports(makeMinionReports(coremigration.QUIESCE)) 177 s.facade.queueMinionReports(makeMinionReports(coremigration.VALIDATION)) 178 s.facade.queueMinionReports(makeMinionReports(coremigration.SUCCESS)) 179 s.config.UploadBinaries = makeStubUploadBinaries(s.stub) 180 181 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 182 183 // Observe that the migration was seen, the model exported, an API 184 // connection to the target controller was made, the model was 185 // imported and then the migration completed. 186 s.stub.CheckCalls(c, joinCalls( 187 // Wait for migration to start. 188 watchStatusLockdownCalls, 189 190 // QUIESCE 191 prechecksCalls, 192 []jujutesting.StubCall{ 193 {"facade.WatchMinionReports", nil}, 194 {"facade.MinionReports", nil}, 195 {"facade.SetPhase", []interface{}{coremigration.IMPORT}}, 196 197 //IMPORT 198 {"facade.Export", nil}, 199 apiOpenControllerCall, 200 importCall, 201 apiOpenModelCall, 202 {"UploadBinaries", []interface{}{ 203 []string{"charm0", "charm1"}, 204 fakeCharmDownloader, 205 map[version.Binary]string{ 206 version.MustParseBinary("2.1.0-trusty-amd64"): "/tools/0", 207 }, 208 fakeToolsDownloader, 209 }}, 210 apiCloseCall, // for target model 211 apiCloseCall, // for target controller 212 {"facade.SetPhase", []interface{}{coremigration.VALIDATION}}, 213 214 // VALIDATION 215 {"facade.WatchMinionReports", nil}, 216 {"facade.MinionReports", nil}, 217 apiOpenControllerCall, 218 activateCall, 219 apiCloseCall, 220 {"facade.SetPhase", []interface{}{coremigration.SUCCESS}}, 221 222 // SUCCESS 223 {"facade.WatchMinionReports", nil}, 224 {"facade.MinionReports", nil}, 225 {"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}}, 226 227 // LOGTRANSFER 228 {"facade.SetPhase", []interface{}{coremigration.REAP}}, 229 230 // REAP 231 {"facade.Reap", nil}, 232 {"facade.SetPhase", []interface{}{coremigration.DONE}}, 233 }), 234 ) 235 } 236 237 func (s *Suite) TestMigrationResume(c *gc.C) { 238 // Test that a partially complete migration can be resumed. 239 s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS)) 240 s.facade.queueMinionReports(makeMinionReports(coremigration.SUCCESS)) 241 242 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 243 s.stub.CheckCalls(c, joinCalls( 244 watchStatusLockdownCalls, 245 []jujutesting.StubCall{ 246 {"facade.WatchMinionReports", nil}, 247 {"facade.MinionReports", nil}, 248 {"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}}, 249 {"facade.SetPhase", []interface{}{coremigration.REAP}}, 250 {"facade.Reap", nil}, 251 {"facade.SetPhase", []interface{}{coremigration.DONE}}, 252 }, 253 )) 254 } 255 256 func (s *Suite) TestPreviouslyAbortedMigration(c *gc.C) { 257 s.facade.queueStatus(s.makeStatus(coremigration.ABORTDONE)) 258 259 worker, err := migrationmaster.New(s.config) 260 c.Assert(err, jc.ErrorIsNil) 261 defer workertest.CleanKill(c, worker) 262 263 s.waitForStubCalls(c, []string{ 264 "facade.Watch", 265 "facade.MigrationStatus", 266 "guard.Unlock", 267 }) 268 } 269 270 func (s *Suite) TestPreviouslyCompletedMigration(c *gc.C) { 271 s.facade.queueStatus(s.makeStatus(coremigration.DONE)) 272 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 273 s.stub.CheckCalls(c, []jujutesting.StubCall{ 274 {"facade.Watch", nil}, 275 {"facade.MigrationStatus", nil}, 276 }) 277 } 278 279 func (s *Suite) TestWatchFailure(c *gc.C) { 280 s.facade.watchErr = errors.New("boom") 281 s.checkWorkerErr(c, "watching for migration: boom") 282 } 283 284 func (s *Suite) TestStatusError(c *gc.C) { 285 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 286 s.facade.statusErr = errors.New("splat") 287 288 s.checkWorkerErr(c, "retrieving migration status: splat") 289 s.stub.CheckCalls(c, []jujutesting.StubCall{ 290 {"facade.Watch", nil}, 291 {"facade.MigrationStatus", nil}, 292 }) 293 } 294 295 func (s *Suite) TestStatusNotFound(c *gc.C) { 296 s.facade.statusErr = ¶ms.Error{Code: params.CodeNotFound} 297 s.facade.triggerWatcher() 298 299 worker, err := migrationmaster.New(s.config) 300 c.Assert(err, jc.ErrorIsNil) 301 defer workertest.CleanKill(c, worker) 302 303 s.waitForStubCalls(c, []string{ 304 "facade.Watch", 305 "facade.MigrationStatus", 306 "guard.Unlock", 307 }) 308 } 309 310 func (s *Suite) TestUnlockError(c *gc.C) { 311 s.facade.statusErr = ¶ms.Error{Code: params.CodeNotFound} 312 s.facade.triggerWatcher() 313 guard := newStubGuard(s.stub) 314 guard.unlockErr = errors.New("pow") 315 s.config.Guard = guard 316 317 s.checkWorkerErr(c, "pow") 318 s.stub.CheckCalls(c, []jujutesting.StubCall{ 319 {"facade.Watch", nil}, 320 {"facade.MigrationStatus", nil}, 321 {"guard.Unlock", nil}, 322 }) 323 } 324 325 func (s *Suite) TestLockdownError(c *gc.C) { 326 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 327 guard := newStubGuard(s.stub) 328 guard.lockdownErr = errors.New("biff") 329 s.config.Guard = guard 330 331 s.checkWorkerErr(c, "biff") 332 s.stub.CheckCalls(c, watchStatusLockdownCalls) 333 } 334 335 func (s *Suite) TestQUIESCEMinionWaitWatchError(c *gc.C) { 336 s.checkMinionWaitWatchError(c, coremigration.QUIESCE) 337 } 338 339 func (s *Suite) TestQUIESCEMinionWaitGetError(c *gc.C) { 340 s.checkMinionWaitGetError(c, coremigration.QUIESCE) 341 } 342 343 func (s *Suite) TestQUIESCEFailedAgent(c *gc.C) { 344 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 345 s.facade.queueMinionReports(coremigration.MinionReports{ 346 MigrationId: "model-uuid:2", 347 Phase: coremigration.QUIESCE, 348 FailedMachines: []string{"42"}, // a machine failed 349 }) 350 351 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 352 s.stub.CheckCalls(c, joinCalls( 353 watchStatusLockdownCalls, 354 prechecksCalls, 355 []jujutesting.StubCall{ 356 {"facade.WatchMinionReports", nil}, 357 {"facade.MinionReports", nil}, 358 }, 359 abortCalls, 360 )) 361 } 362 363 func (s *Suite) TestQUIESCEWrongController(c *gc.C) { 364 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 365 s.connection.controllerTag = names.NewControllerTag("another-controller") 366 367 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 368 s.stub.CheckCalls(c, joinCalls( 369 watchStatusLockdownCalls, 370 []jujutesting.StubCall{ 371 {"facade.Prechecks", nil}, 372 {"facade.ModelInfo", nil}, 373 apiOpenControllerCall, 374 apiCloseCall, 375 }, 376 abortCalls, 377 )) 378 } 379 380 func (s *Suite) TestQUIESCESourceChecksFail(c *gc.C) { 381 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 382 s.facade.prechecksErr = errors.New("boom") 383 384 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 385 s.stub.CheckCalls(c, joinCalls( 386 watchStatusLockdownCalls, 387 []jujutesting.StubCall{{"facade.Prechecks", nil}}, 388 abortCalls, 389 )) 390 } 391 392 func (s *Suite) TestQUIESCEModelInfoFail(c *gc.C) { 393 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 394 s.facade.modelInfoErr = errors.New("boom") 395 396 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 397 s.stub.CheckCalls(c, joinCalls( 398 watchStatusLockdownCalls, 399 []jujutesting.StubCall{ 400 {"facade.Prechecks", nil}, 401 {"facade.ModelInfo", nil}, 402 }, 403 abortCalls, 404 )) 405 } 406 407 func (s *Suite) TestQUIESCETargetChecksFail(c *gc.C) { 408 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 409 s.connection.prechecksErr = errors.New("boom") 410 411 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 412 s.stub.CheckCalls(c, joinCalls( 413 watchStatusLockdownCalls, 414 prechecksCalls, 415 abortCalls, 416 )) 417 } 418 419 func (s *Suite) TestExportFailure(c *gc.C) { 420 s.facade.queueStatus(s.makeStatus(coremigration.IMPORT)) 421 s.facade.exportErr = errors.New("boom") 422 423 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 424 s.stub.CheckCalls(c, joinCalls( 425 watchStatusLockdownCalls, 426 []jujutesting.StubCall{ 427 {"facade.Export", nil}, 428 }, 429 abortCalls, 430 )) 431 } 432 433 func (s *Suite) TestAPIOpenFailure(c *gc.C) { 434 s.facade.queueStatus(s.makeStatus(coremigration.IMPORT)) 435 s.connectionErr = errors.New("boom") 436 437 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 438 s.stub.CheckCalls(c, joinCalls( 439 watchStatusLockdownCalls, 440 []jujutesting.StubCall{ 441 {"facade.Export", nil}, 442 apiOpenControllerCall, 443 {"facade.SetPhase", []interface{}{coremigration.ABORT}}, 444 apiOpenControllerCall, 445 {"facade.SetPhase", []interface{}{coremigration.ABORTDONE}}, 446 }, 447 )) 448 } 449 450 func (s *Suite) TestImportFailure(c *gc.C) { 451 s.facade.queueStatus(s.makeStatus(coremigration.IMPORT)) 452 s.connection.importErr = errors.New("boom") 453 454 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 455 s.stub.CheckCalls(c, joinCalls( 456 watchStatusLockdownCalls, 457 []jujutesting.StubCall{ 458 {"facade.Export", nil}, 459 apiOpenControllerCall, 460 importCall, 461 apiCloseCall, 462 }, 463 abortCalls, 464 )) 465 } 466 467 func (s *Suite) TestVALIDATIONMinionWaitWatchError(c *gc.C) { 468 s.checkMinionWaitWatchError(c, coremigration.VALIDATION) 469 } 470 471 func (s *Suite) TestVALIDATIONMinionWaitGetError(c *gc.C) { 472 s.checkMinionWaitGetError(c, coremigration.VALIDATION) 473 } 474 475 func (s *Suite) TestVALIDATIONFailedAgent(c *gc.C) { 476 s.facade.queueStatus(s.makeStatus(coremigration.VALIDATION)) 477 s.facade.queueMinionReports(coremigration.MinionReports{ 478 MigrationId: "model-uuid:2", 479 Phase: coremigration.VALIDATION, 480 FailedMachines: []string{"42"}, // a machine failed 481 }) 482 483 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 484 s.stub.CheckCalls(c, joinCalls( 485 watchStatusLockdownCalls, 486 []jujutesting.StubCall{ 487 {"facade.WatchMinionReports", nil}, 488 {"facade.MinionReports", nil}, 489 }, 490 abortCalls, 491 )) 492 } 493 494 func (s *Suite) TestSUCCESSMinionWaitWatchError(c *gc.C) { 495 s.checkMinionWaitWatchError(c, coremigration.SUCCESS) 496 } 497 498 func (s *Suite) TestSUCCESSMinionWaitGetError(c *gc.C) { 499 s.checkMinionWaitGetError(c, coremigration.SUCCESS) 500 } 501 502 func (s *Suite) TestSUCCESSMinionWaitFailedMachine(c *gc.C) { 503 // With the SUCCESS phase the master should wait for all reports, 504 // continuing even if some minions report failure. 505 s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS)) 506 s.facade.queueMinionReports(coremigration.MinionReports{ 507 MigrationId: "model-uuid:2", 508 Phase: coremigration.SUCCESS, 509 FailedMachines: []string{"42"}, 510 }) 511 512 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 513 s.stub.CheckCalls(c, joinCalls( 514 watchStatusLockdownCalls, 515 []jujutesting.StubCall{ 516 {"facade.WatchMinionReports", nil}, 517 {"facade.MinionReports", nil}, 518 {"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}}, 519 {"facade.SetPhase", []interface{}{coremigration.REAP}}, 520 {"facade.Reap", nil}, 521 {"facade.SetPhase", []interface{}{coremigration.DONE}}, 522 }, 523 )) 524 } 525 526 func (s *Suite) TestSUCCESSMinionWaitFailedUnit(c *gc.C) { 527 // See note for TestMinionWaitFailedMachine above. 528 s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS)) 529 s.facade.queueMinionReports(coremigration.MinionReports{ 530 MigrationId: "model-uuid:2", 531 Phase: coremigration.SUCCESS, 532 FailedUnits: []string{"foo/2"}, 533 }) 534 535 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 536 s.stub.CheckCalls(c, joinCalls( 537 watchStatusLockdownCalls, 538 []jujutesting.StubCall{ 539 {"facade.WatchMinionReports", nil}, 540 {"facade.MinionReports", nil}, 541 {"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}}, 542 {"facade.SetPhase", []interface{}{coremigration.REAP}}, 543 {"facade.Reap", nil}, 544 {"facade.SetPhase", []interface{}{coremigration.DONE}}, 545 }, 546 )) 547 } 548 549 func (s *Suite) TestSUCCESSMinionWaitTimeout(c *gc.C) { 550 // The SUCCESS phase is special in that even if some minions fail 551 // to report the migration should continue. There's no turning 552 // back from SUCCESS. 553 s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS)) 554 555 worker, err := migrationmaster.New(s.config) 556 c.Assert(err, jc.ErrorIsNil) 557 defer workertest.DirtyKill(c, worker) 558 559 select { 560 case <-s.clock.Alarms(): 561 case <-time.After(coretesting.LongWait): 562 c.Fatal("timed out waiting for clock.After call") 563 } 564 565 // Move time ahead in order to trigger timeout. 566 s.clock.Advance(15 * time.Minute) 567 568 err = workertest.CheckKilled(c, worker) 569 c.Assert(err, gc.Equals, migrationmaster.ErrMigrated) 570 571 s.stub.CheckCalls(c, joinCalls( 572 watchStatusLockdownCalls, 573 []jujutesting.StubCall{ 574 {"facade.WatchMinionReports", nil}, 575 {"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}}, 576 {"facade.SetPhase", []interface{}{coremigration.REAP}}, 577 {"facade.Reap", nil}, 578 {"facade.SetPhase", []interface{}{coremigration.DONE}}, 579 }, 580 )) 581 } 582 583 func (s *Suite) TestMinionWaitWrongPhase(c *gc.C) { 584 s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS)) 585 586 // Have the phase in the minion reports be different from the 587 // migration status. This shouldn't happen but the migrationmaster 588 // should handle it. 589 s.facade.queueMinionReports(makeMinionReports(coremigration.IMPORT)) 590 591 s.checkWorkerErr(c, 592 `minion reports phase \(IMPORT\) does not match migration phase \(SUCCESS\)`) 593 } 594 595 func (s *Suite) TestMinionWaitMigrationIdChanged(c *gc.C) { 596 s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS)) 597 598 // Have the migration id in the minion reports be different from 599 // the migration status. This shouldn't happen but the 600 // migrationmaster should handle it. 601 s.facade.queueMinionReports(coremigration.MinionReports{ 602 MigrationId: "blah", 603 Phase: coremigration.SUCCESS, 604 }) 605 606 s.checkWorkerErr(c, 607 "unexpected migration id in minion reports, got blah, expected model-uuid:2") 608 } 609 610 func (s *Suite) TestAPIConnectWithMacaroon(c *gc.C) { 611 // Use ABORT because it involves an API connection to the target 612 // and is convenient. 613 status := s.makeStatus(coremigration.ABORT) 614 615 // Set up macaroon based auth to the target. 616 mac, err := macaroon.New([]byte("secret"), "id", "location") 617 c.Assert(err, jc.ErrorIsNil) 618 macs := []macaroon.Slice{{mac}} 619 status.TargetInfo.Password = "" 620 status.TargetInfo.Macaroons = macs 621 622 s.facade.queueStatus(status) 623 624 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 625 s.stub.CheckCalls(c, joinCalls( 626 watchStatusLockdownCalls, 627 []jujutesting.StubCall{ 628 { 629 "apiOpen", 630 []interface{}{ 631 &api.Info{ 632 Addrs: []string{"1.2.3.4:5"}, 633 CACert: "cert", 634 Tag: names.NewUserTag("admin"), 635 Macaroons: macs, // <--- 636 }, 637 migration.ControllerDialOpts(), 638 }, 639 }, 640 abortCall, 641 apiCloseCall, 642 {"facade.SetPhase", []interface{}{coremigration.ABORTDONE}}, 643 }, 644 )) 645 } 646 647 func (s *Suite) TestExternalControl(c *gc.C) { 648 status := s.makeStatus(coremigration.QUIESCE) 649 status.ExternalControl = true 650 s.facade.queueStatus(status) 651 652 status.Phase = coremigration.DONE 653 s.facade.queueStatus(status) 654 655 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 656 s.stub.CheckCalls(c, joinCalls( 657 // Wait for migration to start. 658 watchStatusLockdownCalls, 659 660 // Wait for migration to end. 661 []jujutesting.StubCall{ 662 {"facade.Watch", nil}, 663 {"facade.MigrationStatus", nil}, 664 }, 665 )) 666 } 667 668 func (s *Suite) TestExternalControlABORT(c *gc.C) { 669 status := s.makeStatus(coremigration.QUIESCE) 670 status.ExternalControl = true 671 s.facade.queueStatus(status) 672 673 status.Phase = coremigration.ABORTDONE 674 s.facade.queueStatus(status) 675 676 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 677 s.stub.CheckCalls(c, joinCalls( 678 // Wait for migration to start. 679 watchStatusLockdownCalls, 680 681 // Wait for migration to end. 682 []jujutesting.StubCall{ 683 {"facade.Watch", nil}, 684 {"facade.MigrationStatus", nil}, 685 }, 686 )) 687 } 688 689 func (s *Suite) checkWorkerReturns(c *gc.C, expected error) { 690 err := s.runWorker(c) 691 c.Check(errors.Cause(err), gc.Equals, expected) 692 } 693 694 func (s *Suite) checkWorkerErr(c *gc.C, expected string) { 695 err := s.runWorker(c) 696 c.Check(err, gc.ErrorMatches, expected) 697 } 698 699 func (s *Suite) runWorker(c *gc.C) error { 700 w, err := migrationmaster.New(s.config) 701 c.Assert(err, jc.ErrorIsNil) 702 defer workertest.DirtyKill(c, w) 703 return workertest.CheckKilled(c, w) 704 } 705 706 func (s *Suite) waitForStubCalls(c *gc.C, expectedCallNames []string) { 707 var callNames []string 708 for a := coretesting.LongAttempt.Start(); a.Next(); { 709 callNames = stubCallNames(s.stub) 710 if reflect.DeepEqual(callNames, expectedCallNames) { 711 return 712 } 713 } 714 c.Fatalf("failed to see expected calls\nobtained: %v\nexpected: %v", 715 callNames, expectedCallNames) 716 } 717 718 func (s *Suite) checkMinionWaitWatchError(c *gc.C, phase coremigration.Phase) { 719 s.facade.minionReportsWatchErr = errors.New("boom") 720 s.facade.queueStatus(s.makeStatus(phase)) 721 722 s.checkWorkerErr(c, "boom") 723 } 724 725 func (s *Suite) checkMinionWaitGetError(c *gc.C, phase coremigration.Phase) { 726 s.facade.queueStatus(s.makeStatus(phase)) 727 728 s.facade.minionReportsErr = errors.New("boom") 729 s.facade.triggerMinionReports() 730 731 s.checkWorkerErr(c, "boom") 732 } 733 734 func stubCallNames(stub *jujutesting.Stub) []string { 735 var out []string 736 for _, call := range stub.Calls() { 737 out = append(out, call.FuncName) 738 } 739 return out 740 } 741 742 func newStubGuard(stub *jujutesting.Stub) *stubGuard { 743 return &stubGuard{stub: stub} 744 } 745 746 type stubGuard struct { 747 stub *jujutesting.Stub 748 unlockErr error 749 lockdownErr error 750 } 751 752 func (g *stubGuard) Lockdown(fortress.Abort) error { 753 g.stub.AddCall("guard.Lockdown") 754 return g.lockdownErr 755 } 756 757 func (g *stubGuard) Unlock() error { 758 g.stub.AddCall("guard.Unlock") 759 return g.unlockErr 760 } 761 762 func newStubMasterFacade(stub *jujutesting.Stub, now time.Time) *stubMasterFacade { 763 return &stubMasterFacade{ 764 stub: stub, 765 watcherChanges: make(chan struct{}, 999), 766 767 // Give minionReportsChanges a larger-than-required buffer to 768 // support waits at a number of phases. 769 minionReportsChanges: make(chan struct{}, 999), 770 } 771 } 772 773 type stubMasterFacade struct { 774 migrationmaster.Facade 775 776 stub *jujutesting.Stub 777 778 watcherChanges chan struct{} 779 watchErr error 780 status []coremigration.MigrationStatus 781 statusErr error 782 783 prechecksErr error 784 modelInfoErr error 785 exportErr error 786 787 minionReportsChanges chan struct{} 788 minionReportsWatchErr error 789 minionReports []coremigration.MinionReports 790 minionReportsErr error 791 } 792 793 func (f *stubMasterFacade) triggerWatcher() { 794 select { 795 case f.watcherChanges <- struct{}{}: 796 default: 797 panic("migration watcher channel unexpectedly closed") 798 } 799 } 800 801 func (f *stubMasterFacade) queueStatus(status coremigration.MigrationStatus) { 802 f.status = append(f.status, status) 803 f.triggerWatcher() 804 } 805 806 func (f *stubMasterFacade) triggerMinionReports() { 807 select { 808 case f.minionReportsChanges <- struct{}{}: 809 default: 810 panic("minion reports watcher channel unexpectedly closed") 811 } 812 } 813 814 func (f *stubMasterFacade) queueMinionReports(r coremigration.MinionReports) { 815 f.minionReports = append(f.minionReports, r) 816 f.triggerMinionReports() 817 } 818 819 func (f *stubMasterFacade) Watch() (watcher.NotifyWatcher, error) { 820 f.stub.AddCall("facade.Watch") 821 if f.watchErr != nil { 822 return nil, f.watchErr 823 } 824 return newMockWatcher(f.watcherChanges), nil 825 } 826 827 func (f *stubMasterFacade) MigrationStatus() (coremigration.MigrationStatus, error) { 828 f.stub.AddCall("facade.MigrationStatus") 829 if f.statusErr != nil { 830 return coremigration.MigrationStatus{}, f.statusErr 831 } 832 if len(f.status) == 0 { 833 panic("no status queued to report") 834 } 835 out := f.status[0] 836 f.status = f.status[1:] 837 return out, nil 838 } 839 840 func (f *stubMasterFacade) WatchMinionReports() (watcher.NotifyWatcher, error) { 841 f.stub.AddCall("facade.WatchMinionReports") 842 if f.minionReportsWatchErr != nil { 843 return nil, f.minionReportsWatchErr 844 } 845 return newMockWatcher(f.minionReportsChanges), nil 846 } 847 848 func (f *stubMasterFacade) MinionReports() (coremigration.MinionReports, error) { 849 f.stub.AddCall("facade.MinionReports") 850 if f.minionReportsErr != nil { 851 return coremigration.MinionReports{}, f.minionReportsErr 852 } 853 if len(f.minionReports) == 0 { 854 return coremigration.MinionReports{}, errors.NotFoundf("reports") 855 856 } 857 r := f.minionReports[0] 858 f.minionReports = f.minionReports[1:] 859 return r, nil 860 } 861 862 func (f *stubMasterFacade) Prechecks() error { 863 f.stub.AddCall("facade.Prechecks") 864 return f.prechecksErr 865 } 866 867 func (f *stubMasterFacade) ModelInfo() (coremigration.ModelInfo, error) { 868 f.stub.AddCall("facade.ModelInfo") 869 if f.modelInfoErr != nil { 870 return coremigration.ModelInfo{}, f.modelInfoErr 871 } 872 return coremigration.ModelInfo{ 873 UUID: modelUUID, 874 Name: modelName, 875 Owner: ownerTag, 876 AgentVersion: modelVersion, 877 }, nil 878 } 879 880 func (f *stubMasterFacade) Export() (coremigration.SerializedModel, error) { 881 f.stub.AddCall("facade.Export") 882 if f.exportErr != nil { 883 return coremigration.SerializedModel{}, f.exportErr 884 } 885 return coremigration.SerializedModel{ 886 Bytes: fakeModelBytes, 887 Charms: []string{"charm0", "charm1"}, 888 Tools: map[version.Binary]string{ 889 version.MustParseBinary("2.1.0-trusty-amd64"): "/tools/0", 890 }, 891 }, nil 892 } 893 894 func (f *stubMasterFacade) SetPhase(phase coremigration.Phase) error { 895 f.stub.AddCall("facade.SetPhase", phase) 896 return nil 897 } 898 899 func (f *stubMasterFacade) SetStatusMessage(message string) error { 900 return nil 901 } 902 903 func (f *stubMasterFacade) Reap() error { 904 f.stub.AddCall("facade.Reap") 905 return nil 906 } 907 908 func newMockWatcher(changes chan struct{}) *mockWatcher { 909 return &mockWatcher{ 910 Worker: workertest.NewErrorWorker(nil), 911 changes: changes, 912 } 913 } 914 915 type mockWatcher struct { 916 worker.Worker 917 changes chan struct{} 918 } 919 920 func (w *mockWatcher) Changes() watcher.NotifyChannel { 921 return w.changes 922 } 923 924 type stubConnection struct { 925 api.Connection 926 stub *jujutesting.Stub 927 prechecksErr error 928 importErr error 929 controllerTag names.ControllerTag 930 } 931 932 func (c *stubConnection) BestFacadeVersion(string) int { 933 return 1 934 } 935 936 func (c *stubConnection) APICall(objType string, version int, id, request string, params, response interface{}) error { 937 c.stub.AddCall(objType+"."+request, params) 938 939 if objType == "MigrationTarget" { 940 switch request { 941 case "Prechecks": 942 return c.prechecksErr 943 case "Import": 944 return c.importErr 945 case "Activate": 946 return nil 947 } 948 } 949 return errors.New("unexpected API call") 950 } 951 952 func (c *stubConnection) Client() *api.Client { 953 // This is kinda crappy but the *Client doesn't have to be 954 // functional... 955 return new(api.Client) 956 } 957 958 func (c *stubConnection) Close() error { 959 c.stub.AddCall("Connection.Close") 960 return nil 961 } 962 963 func (c *stubConnection) ControllerTag() names.ControllerTag { 964 return c.controllerTag 965 } 966 967 func makeStubUploadBinaries(stub *jujutesting.Stub) func(migration.UploadBinariesConfig) error { 968 return func(config migration.UploadBinariesConfig) error { 969 stub.AddCall( 970 "UploadBinaries", 971 config.Charms, 972 config.CharmDownloader, 973 config.Tools, 974 config.ToolsDownloader, 975 ) 976 return nil 977 } 978 } 979 980 // nullUploadBinaries is a UploadBinaries variant which is intended to 981 // not get called. 982 func nullUploadBinaries(migration.UploadBinariesConfig) error { 983 panic("should not get called") 984 } 985 986 var fakeCharmDownloader = struct{ migration.CharmDownloader }{} 987 988 var fakeToolsDownloader = struct{ migration.ToolsDownloader }{} 989 990 func joinCalls(allCalls ...[]jujutesting.StubCall) (out []jujutesting.StubCall) { 991 for _, calls := range allCalls { 992 out = append(out, calls...) 993 } 994 return 995 } 996 997 func makeMinionReports(p coremigration.Phase) coremigration.MinionReports { 998 return coremigration.MinionReports{ 999 MigrationId: "model-uuid:2", 1000 Phase: p, 1001 SuccessCount: 5, 1002 UnknownCount: 0, 1003 } 1004 }