github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/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 "context" 8 "net/http" 9 "net/textproto" 10 "net/url" 11 "reflect" 12 "time" 13 14 "github.com/juju/clock/testclock" 15 "github.com/juju/errors" 16 "github.com/juju/loggo" 17 "github.com/juju/names/v5" 18 jujutesting "github.com/juju/testing" 19 jc "github.com/juju/testing/checkers" 20 "github.com/juju/utils/v3" 21 "github.com/juju/version/v2" 22 "github.com/juju/worker/v3" 23 "github.com/juju/worker/v3/workertest" 24 gc "gopkg.in/check.v1" 25 "gopkg.in/macaroon.v2" 26 27 "github.com/juju/juju/api" 28 "github.com/juju/juju/api/base" 29 apiclient "github.com/juju/juju/api/client/client" 30 "github.com/juju/juju/api/common" 31 apiservererrors "github.com/juju/juju/apiserver/errors" 32 coremigration "github.com/juju/juju/core/migration" 33 resourcetesting "github.com/juju/juju/core/resources/testing" 34 "github.com/juju/juju/core/watcher" 35 "github.com/juju/juju/migration" 36 "github.com/juju/juju/rpc/params" 37 coretesting "github.com/juju/juju/testing" 38 jujuversion "github.com/juju/juju/version" 39 "github.com/juju/juju/worker/fortress" 40 "github.com/juju/juju/worker/migrationmaster" 41 ) 42 43 type Suite struct { 44 coretesting.BaseSuite 45 clock *testclock.Clock 46 stub *jujutesting.Stub 47 connection *stubConnection 48 connectionErr error 49 facade *stubMasterFacade 50 config migrationmaster.Config 51 } 52 53 var _ = gc.Suite(&Suite{}) 54 55 var ( 56 fakeModelBytes = []byte("model") 57 sourceControllerTag = names.NewControllerTag("source-controller-uuid") 58 targetControllerTag = names.NewControllerTag("controller-uuid") 59 modelUUID = "model-uuid" 60 modelTag = names.NewModelTag(modelUUID) 61 modelName = "model-name" 62 ownerTag = names.NewUserTag("owner") 63 modelVersion = version.MustParse("1.2.4") 64 65 // Define stub calls that commonly appear in tests here to allow reuse. 66 apiOpenControllerCall = jujutesting.StubCall{ 67 FuncName: "apiOpen", 68 Args: []interface{}{ 69 &api.Info{ 70 Addrs: []string{"1.2.3.4:5"}, 71 CACert: "cert", 72 Tag: names.NewUserTag("admin"), 73 Password: "secret", 74 }, 75 migration.ControllerDialOpts(), 76 }, 77 } 78 importCall = jujutesting.StubCall{ 79 FuncName: "MigrationTarget.Import", 80 Args: []interface{}{ 81 params.SerializedModel{Bytes: fakeModelBytes}, 82 }, 83 } 84 activateCall = jujutesting.StubCall{ 85 FuncName: "MigrationTarget.Activate", 86 Args: []interface{}{ 87 params.ActivateModelArgs{ 88 ModelTag: modelTag.String(), 89 ControllerTag: sourceControllerTag.String(), 90 ControllerAlias: "mycontroller", 91 SourceAPIAddrs: []string{"source-addr"}, 92 SourceCACert: "cacert", 93 CrossModelUUIDs: []string{"related-model-uuid"}, 94 }, 95 }, 96 } 97 checkMachinesCall = jujutesting.StubCall{ 98 FuncName: "MigrationTarget.CheckMachines", 99 Args: []interface{}{ 100 params.ModelArgs{ModelTag: modelTag.String()}, 101 }, 102 } 103 adoptResourcesCall = jujutesting.StubCall{ 104 FuncName: "MigrationTarget.AdoptResources", 105 Args: []interface{}{ 106 params.AdoptResourcesArgs{ 107 ModelTag: modelTag.String(), 108 SourceControllerVersion: jujuversion.Current, 109 }, 110 }, 111 } 112 latestLogTimeCall = jujutesting.StubCall{ 113 FuncName: "MigrationTarget.LatestLogTime", 114 Args: []interface{}{ 115 params.ModelArgs{ModelTag: modelTag.String()}, 116 }, 117 } 118 apiCloseCall = jujutesting.StubCall{FuncName: "Connection.Close"} 119 abortCall = jujutesting.StubCall{ 120 FuncName: "MigrationTarget.Abort", 121 Args: []interface{}{ 122 params.ModelArgs{ModelTag: modelTag.String()}, 123 }, 124 } 125 watchStatusLockdownCalls = []jujutesting.StubCall{ 126 {"facade.Watch", nil}, 127 {"facade.MigrationStatus", nil}, 128 {"guard.Lockdown", nil}, 129 } 130 prechecksCalls = []jujutesting.StubCall{ 131 {"facade.ModelInfo", nil}, 132 {"facade.Prechecks", []interface{}{}}, 133 apiOpenControllerCall, 134 {"MigrationTarget.Prechecks", []interface{}{params.MigrationModelInfo{ 135 UUID: modelUUID, 136 Name: modelName, 137 OwnerTag: ownerTag.String(), 138 AgentVersion: modelVersion, 139 }}}, 140 apiCloseCall, 141 } 142 abortCalls = []jujutesting.StubCall{ 143 {"facade.SetPhase", []interface{}{coremigration.ABORT}}, 144 apiOpenControllerCall, 145 abortCall, 146 apiCloseCall, 147 {"facade.SetPhase", []interface{}{coremigration.ABORTDONE}}, 148 } 149 openDestLogStreamCall = jujutesting.StubCall{FuncName: "ConnectControllerStream", Args: []interface{}{ 150 "/migrate/logtransfer", 151 url.Values{}, 152 http.Header{ 153 textproto.CanonicalMIMEHeaderKey(params.MigrationModelHTTPHeader): {modelUUID}, 154 }, 155 }} 156 ) 157 158 func (s *Suite) SetUpTest(c *gc.C) { 159 s.BaseSuite.SetUpTest(c) 160 161 s.clock = testclock.NewClock(time.Now()) 162 s.stub = new(jujutesting.Stub) 163 s.connection = &stubConnection{ 164 c: c, 165 stub: s.stub, 166 controllerTag: targetControllerTag, 167 logStream: &mockStream{}, 168 controllerVersion: params.ControllerVersionResults{ 169 Version: "2.9.99", 170 }, 171 facadeVersion: 2, 172 } 173 s.connectionErr = nil 174 175 s.facade = newStubMasterFacade(s.stub) 176 177 // The default worker Config used by most of the tests. Tests may 178 // tweak parts of this as needed. 179 s.config = migrationmaster.Config{ 180 ModelUUID: utils.MustNewUUID().String(), 181 Facade: s.facade, 182 Guard: newStubGuard(s.stub), 183 APIOpen: s.apiOpen, 184 UploadBinaries: nullUploadBinaries, 185 CharmDownloader: fakeCharmDownloader, 186 ToolsDownloader: fakeToolsDownloader, 187 Clock: s.clock, 188 } 189 } 190 191 func (s *Suite) apiOpen(info *api.Info, dialOpts api.DialOpts) (api.Connection, error) { 192 s.stub.AddCall("apiOpen", info, dialOpts) 193 if s.connectionErr != nil { 194 return nil, s.connectionErr 195 } 196 return s.connection, nil 197 } 198 199 func (s *Suite) makeStatus(phase coremigration.Phase) coremigration.MigrationStatus { 200 return coremigration.MigrationStatus{ 201 MigrationId: "model-uuid:2", 202 ModelUUID: "model-uuid", 203 Phase: phase, 204 PhaseChangedTime: s.clock.Now(), 205 TargetInfo: coremigration.TargetInfo{ 206 ControllerTag: targetControllerTag, 207 Addrs: []string{"1.2.3.4:5"}, 208 CACert: "cert", 209 AuthTag: names.NewUserTag("admin"), 210 Password: "secret", 211 }, 212 } 213 } 214 215 func (s *Suite) TestSuccessfulMigration(c *gc.C) { 216 s.facade.exportedResources = []coremigration.SerializedModelResource{{ 217 ApplicationRevision: resourcetesting.NewResource(c, nil, "blob", "app", "").Resource, 218 }} 219 220 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 221 s.facade.queueMinionReports(makeMinionReports(coremigration.QUIESCE)) 222 s.facade.queueMinionReports(makeMinionReports(coremigration.VALIDATION)) 223 s.facade.queueMinionReports(makeMinionReports(coremigration.SUCCESS)) 224 s.config.UploadBinaries = makeStubUploadBinaries(s.stub) 225 226 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 227 228 // Observe that the migration was seen, the model exported, an API 229 // connection to the target controller was made, the model was 230 // imported and then the migration completed. 231 assertExpectedCallArgs(c, s.stub, joinCalls( 232 // Wait for migration to start. 233 watchStatusLockdownCalls, 234 []jujutesting.StubCall{ 235 {"facade.MinionReportTimeout", nil}, 236 }, 237 238 // QUIESCE 239 prechecksCalls, 240 []jujutesting.StubCall{ 241 {"facade.WatchMinionReports", nil}, 242 {"facade.MinionReports", nil}, 243 }, 244 prechecksCalls, 245 []jujutesting.StubCall{ 246 {"facade.SetPhase", []interface{}{coremigration.IMPORT}}, 247 248 //IMPORT 249 {"facade.Export", nil}, 250 apiOpenControllerCall, 251 importCall, 252 {"UploadBinaries", []interface{}{ 253 []string{"charm0", "charm1"}, 254 fakeCharmDownloader, 255 map[version.Binary]string{ 256 version.MustParseBinary("2.1.0-ubuntu-amd64"): "/tools/0", 257 }, 258 fakeToolsDownloader, 259 s.facade.exportedResources, 260 s.facade, 261 }}, 262 apiCloseCall, // for target controller 263 {"facade.SetPhase", []interface{}{coremigration.PROCESSRELATIONS}}, 264 265 // PROCESSRELATIONS 266 {"facade.ProcessRelations", []interface{}{""}}, 267 {"facade.SetPhase", []interface{}{coremigration.VALIDATION}}, 268 269 // VALIDATION 270 {"facade.WatchMinionReports", nil}, 271 {"facade.MinionReports", nil}, 272 apiOpenControllerCall, 273 checkMachinesCall, 274 {"facade.SourceControllerInfo", nil}, 275 activateCall, 276 apiCloseCall, 277 {"facade.SetPhase", []interface{}{coremigration.SUCCESS}}, 278 279 // SUCCESS 280 {"facade.WatchMinionReports", nil}, 281 {"facade.MinionReports", nil}, 282 apiOpenControllerCall, 283 adoptResourcesCall, 284 apiCloseCall, 285 {"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}}, 286 287 // LOGTRANSFER 288 apiOpenControllerCall, 289 latestLogTimeCall, 290 {"StreamModelLog", []interface{}{time.Time{}}}, 291 openDestLogStreamCall, 292 {"facade.SetPhase", []interface{}{coremigration.REAP}}, 293 294 // REAP 295 {"facade.Reap", nil}, 296 {"facade.SetPhase", []interface{}{coremigration.DONE}}, 297 }), 298 ) 299 } 300 301 func (s *Suite) TestIncompatibleTarget(c *gc.C) { 302 s.connection.facadeVersion = 1 303 s.facade.exportedResources = []coremigration.SerializedModelResource{{ 304 ApplicationRevision: resourcetesting.NewResource(c, nil, "blob", "app", "").Resource, 305 }} 306 307 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 308 s.facade.queueMinionReports(makeMinionReports(coremigration.QUIESCE)) 309 s.facade.queueMinionReports(makeMinionReports(coremigration.VALIDATION)) 310 s.facade.queueMinionReports(makeMinionReports(coremigration.SUCCESS)) 311 s.config.UploadBinaries = makeStubUploadBinaries(s.stub) 312 313 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 314 315 // Observe that the migration was seen, the model exported, an API 316 // connection to the target controller was made, the model was 317 // imported and then the migration completed. 318 s.stub.CheckCalls(c, joinCalls( 319 // Wait for migration to start. 320 watchStatusLockdownCalls, 321 []jujutesting.StubCall{ 322 {"facade.MinionReportTimeout", nil}, 323 }, 324 325 // QUIESCE 326 []jujutesting.StubCall{ 327 {"facade.ModelInfo", nil}, 328 {"facade.Prechecks", []interface{}{}}, 329 apiOpenControllerCall, 330 {"facade.SourceControllerInfo", nil}, 331 apiCloseCall, 332 }, 333 abortCalls, 334 )) 335 } 336 337 func (s *Suite) TestMigrationResume(c *gc.C) { 338 // Test that a partially complete migration can be resumed. 339 s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS)) 340 s.facade.queueMinionReports(makeMinionReports(coremigration.SUCCESS)) 341 342 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 343 s.stub.CheckCalls(c, joinCalls( 344 watchStatusLockdownCalls, 345 []jujutesting.StubCall{ 346 {"facade.MinionReportTimeout", nil}, 347 {"facade.WatchMinionReports", nil}, 348 {"facade.MinionReports", nil}, 349 apiOpenControllerCall, 350 adoptResourcesCall, 351 apiCloseCall, 352 {"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}}, 353 apiOpenControllerCall, 354 latestLogTimeCall, 355 {"StreamModelLog", []interface{}{time.Time{}}}, 356 openDestLogStreamCall, 357 {"facade.SetPhase", []interface{}{coremigration.REAP}}, 358 {"facade.Reap", nil}, 359 {"facade.SetPhase", []interface{}{coremigration.DONE}}, 360 }, 361 )) 362 } 363 364 func (s *Suite) TestPreviouslyAbortedMigration(c *gc.C) { 365 s.facade.queueStatus(s.makeStatus(coremigration.ABORTDONE)) 366 367 w, err := migrationmaster.New(s.config) 368 c.Assert(err, jc.ErrorIsNil) 369 defer workertest.CleanKill(c, w) 370 371 s.waitForStubCalls(c, []string{ 372 "facade.Watch", 373 "facade.MigrationStatus", 374 "guard.Unlock", 375 }) 376 } 377 378 func (s *Suite) TestPreviouslyCompletedMigration(c *gc.C) { 379 s.facade.queueStatus(s.makeStatus(coremigration.DONE)) 380 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 381 s.stub.CheckCalls(c, []jujutesting.StubCall{ 382 {"facade.Watch", nil}, 383 {"facade.MigrationStatus", nil}, 384 }) 385 } 386 387 func (s *Suite) TestWatchFailure(c *gc.C) { 388 s.facade.watchErr = errors.New("boom") 389 s.checkWorkerErr(c, "watching for migration: boom") 390 } 391 392 func (s *Suite) TestStatusError(c *gc.C) { 393 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 394 s.facade.statusErr = errors.New("splat") 395 396 s.checkWorkerErr(c, "retrieving migration status: splat") 397 s.stub.CheckCalls(c, []jujutesting.StubCall{ 398 {"facade.Watch", nil}, 399 {"facade.MigrationStatus", nil}, 400 }) 401 } 402 403 func (s *Suite) TestStatusNotFound(c *gc.C) { 404 s.facade.statusErr = ¶ms.Error{Code: params.CodeNotFound} 405 s.facade.triggerWatcher() 406 407 w, err := migrationmaster.New(s.config) 408 c.Assert(err, jc.ErrorIsNil) 409 defer workertest.CleanKill(c, w) 410 411 s.waitForStubCalls(c, []string{ 412 "facade.Watch", 413 "facade.MigrationStatus", 414 "guard.Unlock", 415 }) 416 } 417 418 func (s *Suite) TestUnlockError(c *gc.C) { 419 s.facade.statusErr = ¶ms.Error{Code: params.CodeNotFound} 420 s.facade.triggerWatcher() 421 guard := newStubGuard(s.stub) 422 guard.unlockErr = errors.New("pow") 423 s.config.Guard = guard 424 425 s.checkWorkerErr(c, "pow") 426 s.stub.CheckCalls(c, []jujutesting.StubCall{ 427 {"facade.Watch", nil}, 428 {"facade.MigrationStatus", nil}, 429 {"guard.Unlock", nil}, 430 }) 431 } 432 433 func (s *Suite) TestLockdownError(c *gc.C) { 434 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 435 guard := newStubGuard(s.stub) 436 guard.lockdownErr = errors.New("biff") 437 s.config.Guard = guard 438 439 s.checkWorkerErr(c, "biff") 440 s.stub.CheckCalls(c, watchStatusLockdownCalls) 441 } 442 443 func (s *Suite) TestQUIESCEMinionWaitWatchError(c *gc.C) { 444 s.checkMinionWaitWatchError(c, coremigration.QUIESCE) 445 } 446 447 func (s *Suite) TestQUIESCEMinionWaitGetError(c *gc.C) { 448 s.checkMinionWaitGetError(c, coremigration.QUIESCE) 449 } 450 451 func (s *Suite) TestQUIESCEFailedAgent(c *gc.C) { 452 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 453 s.facade.queueMinionReports(coremigration.MinionReports{ 454 MigrationId: "model-uuid:2", 455 Phase: coremigration.QUIESCE, 456 FailedMachines: []string{"42"}, // a machine failed 457 }) 458 459 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 460 461 expectedCalls := joinCalls( 462 watchStatusLockdownCalls, 463 []jujutesting.StubCall{ 464 {"facade.MinionReportTimeout", nil}, 465 }, 466 prechecksCalls, 467 []jujutesting.StubCall{ 468 {"facade.WatchMinionReports", nil}, 469 {"facade.MinionReports", nil}, 470 }, 471 abortCalls, 472 ) 473 474 assertExpectedCallArgs(c, s.stub, expectedCalls) 475 } 476 477 func (s *Suite) TestQUIESCEWrongController(c *gc.C) { 478 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 479 s.connection.controllerTag = names.NewControllerTag("another-controller") 480 481 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 482 s.stub.CheckCalls(c, joinCalls( 483 watchStatusLockdownCalls, 484 []jujutesting.StubCall{ 485 {"facade.MinionReportTimeout", nil}, 486 {"facade.ModelInfo", nil}, 487 {"facade.Prechecks", []interface{}{}}, 488 apiOpenControllerCall, 489 apiCloseCall, 490 }, 491 abortCalls, 492 )) 493 } 494 495 func (s *Suite) TestQUIESCESourceChecksFail(c *gc.C) { 496 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 497 s.facade.prechecksErr = errors.New("boom") 498 499 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 500 s.stub.CheckCalls(c, joinCalls( 501 watchStatusLockdownCalls, 502 []jujutesting.StubCall{ 503 {"facade.MinionReportTimeout", nil}, 504 {"facade.ModelInfo", nil}, 505 {"facade.Prechecks", []interface{}{}}, 506 }, 507 abortCalls, 508 )) 509 } 510 511 func (s *Suite) TestQUIESCEModelInfoFail(c *gc.C) { 512 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 513 s.facade.modelInfoErr = errors.New("boom") 514 515 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 516 s.stub.CheckCalls(c, joinCalls( 517 watchStatusLockdownCalls, 518 []jujutesting.StubCall{ 519 {"facade.MinionReportTimeout", nil}, 520 {"facade.ModelInfo", nil}, 521 }, 522 abortCalls, 523 )) 524 } 525 526 func (s *Suite) TestQUIESCETargetChecksFail(c *gc.C) { 527 s.facade.queueStatus(s.makeStatus(coremigration.QUIESCE)) 528 s.connection.prechecksErr = errors.New("boom") 529 530 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 531 assertExpectedCallArgs(c, s.stub, joinCalls( 532 watchStatusLockdownCalls, 533 []jujutesting.StubCall{ 534 {"facade.MinionReportTimeout", nil}, 535 }, 536 prechecksCalls, 537 abortCalls, 538 )) 539 } 540 541 func (s *Suite) TestProcessRelationsFailure(c *gc.C) { 542 s.facade.queueStatus(s.makeStatus(coremigration.PROCESSRELATIONS)) 543 s.facade.processRelationsErr = errors.New("boom") 544 545 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 546 s.stub.CheckCalls(c, joinCalls( 547 watchStatusLockdownCalls, 548 []jujutesting.StubCall{ 549 {"facade.MinionReportTimeout", nil}, 550 {"facade.ProcessRelations", []interface{}{""}}, 551 }, 552 abortCalls, 553 )) 554 } 555 556 func (s *Suite) TestExportFailure(c *gc.C) { 557 s.facade.queueStatus(s.makeStatus(coremigration.IMPORT)) 558 s.facade.exportErr = errors.New("boom") 559 560 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 561 s.stub.CheckCalls(c, joinCalls( 562 watchStatusLockdownCalls, 563 []jujutesting.StubCall{ 564 {"facade.MinionReportTimeout", nil}, 565 {"facade.Export", nil}, 566 }, 567 abortCalls, 568 )) 569 } 570 571 func (s *Suite) TestAPIOpenFailure(c *gc.C) { 572 s.facade.queueStatus(s.makeStatus(coremigration.IMPORT)) 573 s.connectionErr = errors.New("boom") 574 575 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 576 s.stub.CheckCalls(c, joinCalls( 577 watchStatusLockdownCalls, 578 []jujutesting.StubCall{ 579 {"facade.MinionReportTimeout", nil}, 580 {"facade.Export", nil}, 581 apiOpenControllerCall, 582 {"facade.SetPhase", []interface{}{coremigration.ABORT}}, 583 apiOpenControllerCall, 584 {"facade.SetPhase", []interface{}{coremigration.ABORTDONE}}, 585 }, 586 )) 587 } 588 589 func (s *Suite) TestImportFailure(c *gc.C) { 590 s.facade.queueStatus(s.makeStatus(coremigration.IMPORT)) 591 s.connection.importErr = errors.New("boom") 592 593 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 594 s.stub.CheckCalls(c, joinCalls( 595 watchStatusLockdownCalls, 596 []jujutesting.StubCall{ 597 {"facade.MinionReportTimeout", nil}, 598 {"facade.Export", nil}, 599 apiOpenControllerCall, 600 importCall, 601 apiCloseCall, 602 }, 603 abortCalls, 604 )) 605 } 606 607 func (s *Suite) TestVALIDATIONMinionWaitWatchError(c *gc.C) { 608 s.checkMinionWaitWatchError(c, coremigration.VALIDATION) 609 } 610 611 func (s *Suite) TestVALIDATIONMinionWaitGetError(c *gc.C) { 612 s.checkMinionWaitGetError(c, coremigration.VALIDATION) 613 } 614 615 func (s *Suite) TestVALIDATIONFailedAgent(c *gc.C) { 616 // Set the last phase change status to be further back 617 // in time than the max wait time for minion reports. 618 sts := s.makeStatus(coremigration.VALIDATION) 619 sts.PhaseChangedTime = time.Now().Add(-20 * time.Minute) 620 s.facade.queueStatus(sts) 621 622 w, err := migrationmaster.New(s.config) 623 c.Assert(err, jc.ErrorIsNil) 624 625 // Queue the reports *after* the watcher is started. 626 // The test will only pass if the minion wait timeout 627 // is independent of the phase change time. 628 s.facade.queueMinionReports(coremigration.MinionReports{ 629 MigrationId: "model-uuid:2", 630 Phase: coremigration.VALIDATION, 631 FailedMachines: []string{"42"}, // a machine failed 632 }) 633 634 err = workertest.CheckKilled(c, w) 635 c.Check(errors.Cause(err), gc.Equals, migrationmaster.ErrInactive) 636 s.stub.CheckCalls(c, joinCalls( 637 watchStatusLockdownCalls, 638 []jujutesting.StubCall{ 639 {"facade.MinionReportTimeout", nil}, 640 {"facade.WatchMinionReports", nil}, 641 {"facade.MinionReports", nil}, 642 }, 643 abortCalls, 644 )) 645 } 646 647 func (s *Suite) TestVALIDATIONCheckMachinesOneError(c *gc.C) { 648 s.facade.queueStatus(s.makeStatus(coremigration.VALIDATION)) 649 s.facade.queueMinionReports(makeMinionReports(coremigration.VALIDATION)) 650 651 s.connection.machineErrs = []string{"been so strange"} 652 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 653 s.stub.CheckCalls(c, joinCalls( 654 watchStatusLockdownCalls, 655 []jujutesting.StubCall{ 656 {"facade.MinionReportTimeout", nil}, 657 {"facade.WatchMinionReports", nil}, 658 {"facade.MinionReports", nil}, 659 apiOpenControllerCall, 660 checkMachinesCall, 661 apiCloseCall, 662 }, 663 abortCalls, 664 )) 665 lastMessages := s.facade.statuses[len(s.facade.statuses)-2:] 666 c.Assert(lastMessages, gc.DeepEquals, []string{ 667 "machine sanity check failed, 1 error found", 668 "aborted, removing model from target controller: machine sanity check failed, 1 error found", 669 }) 670 } 671 672 func (s *Suite) TestVALIDATIONCheckMachinesSeveralErrors(c *gc.C) { 673 s.facade.queueStatus(s.makeStatus(coremigration.VALIDATION)) 674 s.facade.queueMinionReports(makeMinionReports(coremigration.VALIDATION)) 675 s.connection.machineErrs = []string{"been so strange", "lit up"} 676 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 677 s.stub.CheckCalls(c, joinCalls( 678 watchStatusLockdownCalls, 679 []jujutesting.StubCall{ 680 {"facade.MinionReportTimeout", nil}, 681 {"facade.WatchMinionReports", nil}, 682 {"facade.MinionReports", nil}, 683 apiOpenControllerCall, 684 checkMachinesCall, 685 apiCloseCall, 686 }, 687 abortCalls, 688 )) 689 lastMessages := s.facade.statuses[len(s.facade.statuses)-2:] 690 c.Assert(lastMessages, gc.DeepEquals, []string{ 691 "machine sanity check failed, 2 errors found", 692 "aborted, removing model from target controller: machine sanity check failed, 2 errors found", 693 }) 694 } 695 696 func (s *Suite) TestVALIDATIONCheckMachinesOtherError(c *gc.C) { 697 s.facade.queueStatus(s.makeStatus(coremigration.VALIDATION)) 698 s.facade.queueMinionReports(makeMinionReports(coremigration.VALIDATION)) 699 s.connection.checkMachineErr = errors.Errorf("something went bang") 700 701 s.checkWorkerReturns(c, s.connection.checkMachineErr) 702 s.stub.CheckCalls(c, joinCalls( 703 watchStatusLockdownCalls, 704 []jujutesting.StubCall{ 705 {"facade.MinionReportTimeout", nil}, 706 {"facade.WatchMinionReports", nil}, 707 {"facade.MinionReports", nil}, 708 apiOpenControllerCall, 709 checkMachinesCall, 710 apiCloseCall, 711 }, 712 )) 713 } 714 715 func (s *Suite) TestSUCCESSMinionWaitWatchError(c *gc.C) { 716 s.checkMinionWaitWatchError(c, coremigration.SUCCESS) 717 } 718 719 func (s *Suite) TestSUCCESSMinionWaitGetError(c *gc.C) { 720 s.checkMinionWaitGetError(c, coremigration.SUCCESS) 721 } 722 723 func (s *Suite) TestSUCCESSMinionWaitFailedMachine(c *gc.C) { 724 // With the SUCCESS phase the master should wait for all reports, 725 // continuing even if some minions report failure. 726 s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS)) 727 s.facade.queueMinionReports(coremigration.MinionReports{ 728 MigrationId: "model-uuid:2", 729 Phase: coremigration.SUCCESS, 730 FailedMachines: []string{"42"}, 731 }) 732 733 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 734 s.stub.CheckCalls(c, joinCalls( 735 watchStatusLockdownCalls, 736 []jujutesting.StubCall{ 737 {"facade.MinionReportTimeout", nil}, 738 {"facade.WatchMinionReports", nil}, 739 {"facade.MinionReports", nil}, 740 apiOpenControllerCall, 741 adoptResourcesCall, 742 apiCloseCall, 743 {"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}}, 744 apiOpenControllerCall, 745 latestLogTimeCall, 746 {"StreamModelLog", []interface{}{time.Time{}}}, 747 openDestLogStreamCall, 748 {"facade.SetPhase", []interface{}{coremigration.REAP}}, 749 {"facade.Reap", nil}, 750 {"facade.SetPhase", []interface{}{coremigration.DONE}}, 751 }, 752 )) 753 } 754 755 func (s *Suite) TestSUCCESSMinionWaitFailedUnit(c *gc.C) { 756 // See note for TestMinionWaitFailedMachine above. 757 s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS)) 758 s.facade.queueMinionReports(coremigration.MinionReports{ 759 MigrationId: "model-uuid:2", 760 Phase: coremigration.SUCCESS, 761 FailedUnits: []string{"foo/2"}, 762 FailedApplications: []string{"bar"}, 763 }) 764 765 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 766 s.stub.CheckCalls(c, joinCalls( 767 watchStatusLockdownCalls, 768 []jujutesting.StubCall{ 769 {"facade.MinionReportTimeout", nil}, 770 {"facade.WatchMinionReports", nil}, 771 {"facade.MinionReports", nil}, 772 apiOpenControllerCall, 773 adoptResourcesCall, 774 apiCloseCall, 775 {"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}}, 776 apiOpenControllerCall, 777 latestLogTimeCall, 778 {"StreamModelLog", []interface{}{time.Time{}}}, 779 openDestLogStreamCall, 780 {"facade.SetPhase", []interface{}{coremigration.REAP}}, 781 {"facade.Reap", nil}, 782 {"facade.SetPhase", []interface{}{coremigration.DONE}}, 783 }, 784 )) 785 } 786 787 func (s *Suite) TestSUCCESSMinionWaitTimeout(c *gc.C) { 788 // The SUCCESS phase is special in that even if some minions fail 789 // to report the migration should continue. There's no turning 790 // back from SUCCESS. 791 s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS)) 792 793 w, err := migrationmaster.New(s.config) 794 c.Assert(err, jc.ErrorIsNil) 795 defer workertest.DirtyKill(c, w) 796 797 select { 798 case <-s.clock.Alarms(): 799 case <-time.After(coretesting.LongWait): 800 c.Fatal("timed out waiting for clock.After call") 801 } 802 803 // Move time ahead in order to trigger timeout. 804 s.clock.Advance(15 * time.Minute) 805 806 err = workertest.CheckKilled(c, w) 807 c.Assert(err, gc.Equals, migrationmaster.ErrMigrated) 808 809 s.stub.CheckCalls(c, joinCalls( 810 watchStatusLockdownCalls, 811 []jujutesting.StubCall{ 812 {"facade.MinionReportTimeout", nil}, 813 {"facade.WatchMinionReports", nil}, 814 apiOpenControllerCall, 815 adoptResourcesCall, 816 apiCloseCall, 817 {"facade.SetPhase", []interface{}{coremigration.LOGTRANSFER}}, 818 apiOpenControllerCall, 819 latestLogTimeCall, 820 {"StreamModelLog", []interface{}{time.Time{}}}, 821 openDestLogStreamCall, 822 {"facade.SetPhase", []interface{}{coremigration.REAP}}, 823 {"facade.Reap", nil}, 824 {"facade.SetPhase", []interface{}{coremigration.DONE}}, 825 }, 826 )) 827 } 828 829 func (s *Suite) TestMinionWaitWrongPhase(c *gc.C) { 830 s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS)) 831 832 // Have the phase in the minion reports be different from the 833 // migration status. This shouldn't happen but the migrationmaster 834 // should handle it. 835 s.facade.queueMinionReports(makeMinionReports(coremigration.IMPORT)) 836 837 s.checkWorkerErr(c, 838 `minion reports phase \(IMPORT\) does not match migration phase \(SUCCESS\)`) 839 } 840 841 func (s *Suite) TestMinionWaitMigrationIdChanged(c *gc.C) { 842 s.facade.queueStatus(s.makeStatus(coremigration.SUCCESS)) 843 844 // Have the migration id in the minion reports be different from 845 // the migration status. This shouldn't happen but the 846 // migrationmaster should handle it. 847 s.facade.queueMinionReports(coremigration.MinionReports{ 848 MigrationId: "blah", 849 Phase: coremigration.SUCCESS, 850 }) 851 852 s.checkWorkerErr(c, 853 "unexpected migration id in minion reports, got blah, expected model-uuid:2") 854 } 855 856 func (s *Suite) assertAPIConnectWithMacaroon(c *gc.C, authUser names.UserTag) { 857 // Use ABORT because it involves an API connection to the target 858 // and is convenient. 859 status := s.makeStatus(coremigration.ABORT) 860 status.TargetInfo.AuthTag = authUser 861 862 // Set up macaroon based auth to the target. 863 mac, err := macaroon.New([]byte("secret"), []byte("id"), "location", macaroon.LatestVersion) 864 c.Assert(err, jc.ErrorIsNil) 865 macs := []macaroon.Slice{{mac}} 866 status.TargetInfo.Password = "" 867 status.TargetInfo.Macaroons = macs 868 869 s.facade.queueStatus(status) 870 871 s.checkWorkerReturns(c, migrationmaster.ErrInactive) 872 var apiUser names.Tag 873 if authUser.IsLocal() { 874 apiUser = authUser 875 } 876 s.stub.CheckCalls(c, joinCalls( 877 watchStatusLockdownCalls, 878 []jujutesting.StubCall{ 879 {"facade.MinionReportTimeout", nil}, 880 { 881 "apiOpen", 882 []interface{}{ 883 &api.Info{ 884 Addrs: []string{"1.2.3.4:5"}, 885 CACert: "cert", 886 Tag: apiUser, 887 Macaroons: macs, // <--- 888 }, 889 migration.ControllerDialOpts(), 890 }, 891 }, 892 abortCall, 893 apiCloseCall, 894 {"facade.SetPhase", []interface{}{coremigration.ABORTDONE}}, 895 }, 896 )) 897 } 898 899 func (s *Suite) TestAPIConnectWithMacaroonLocalUser(c *gc.C) { 900 s.assertAPIConnectWithMacaroon(c, names.NewUserTag("admin")) 901 } 902 903 func (s *Suite) TestAPIConnectWithMacaroonExternalUser(c *gc.C) { 904 s.assertAPIConnectWithMacaroon(c, names.NewUserTag("fred@external")) 905 } 906 907 func (s *Suite) TestLogTransferErrorOpeningTargetAPI(c *gc.C) { 908 s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER)) 909 s.connectionErr = errors.New("people of earth") 910 911 s.checkWorkerReturns(c, s.connectionErr) 912 s.stub.CheckCalls(c, joinCalls( 913 watchStatusLockdownCalls, 914 []jujutesting.StubCall{ 915 {"facade.MinionReportTimeout", nil}, 916 apiOpenControllerCall, 917 }, 918 )) 919 } 920 921 func (s *Suite) TestLogTransferErrorGettingStartTime(c *gc.C) { 922 s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER)) 923 s.connection.latestLogErr = errors.New("tender vittles") 924 925 s.checkWorkerReturns(c, s.connection.latestLogErr) 926 s.stub.CheckCalls(c, joinCalls( 927 watchStatusLockdownCalls, 928 []jujutesting.StubCall{ 929 {"facade.MinionReportTimeout", nil}, 930 apiOpenControllerCall, 931 latestLogTimeCall, 932 }, 933 )) 934 } 935 936 func (s *Suite) TestLogTransferErrorOpeningLogSource(c *gc.C) { 937 s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER)) 938 s.facade.streamErr = errors.New("chicken bones") 939 940 s.checkWorkerReturns(c, s.facade.streamErr) 941 s.stub.CheckCalls(c, joinCalls( 942 watchStatusLockdownCalls, 943 []jujutesting.StubCall{ 944 {"facade.MinionReportTimeout", nil}, 945 apiOpenControllerCall, 946 latestLogTimeCall, 947 {"StreamModelLog", []interface{}{time.Time{}}}, 948 }, 949 )) 950 } 951 952 func (s *Suite) TestLogTransferErrorOpeningLogDest(c *gc.C) { 953 s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER)) 954 s.connection.streamErr = errors.New("tule lake shuffle") 955 956 s.checkWorkerReturns(c, s.connection.streamErr) 957 s.stub.CheckCalls(c, joinCalls( 958 watchStatusLockdownCalls, 959 []jujutesting.StubCall{ 960 {"facade.MinionReportTimeout", nil}, 961 apiOpenControllerCall, 962 latestLogTimeCall, 963 {"StreamModelLog", []interface{}{time.Time{}}}, 964 openDestLogStreamCall, 965 }, 966 )) 967 } 968 969 func (s *Suite) TestLogTransferErrorWriting(c *gc.C) { 970 s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER)) 971 s.facade.logMessages = func(d chan<- common.LogMessage) { 972 safeSend(c, d, common.LogMessage{Message: "the go team"}) 973 } 974 s.connection.logStream.writeErr = errors.New("bottle rocket") 975 s.checkWorkerReturns(c, s.connection.logStream.writeErr) 976 s.stub.CheckCalls(c, joinCalls( 977 watchStatusLockdownCalls, 978 []jujutesting.StubCall{ 979 {"facade.MinionReportTimeout", nil}, 980 apiOpenControllerCall, 981 latestLogTimeCall, 982 {"StreamModelLog", []interface{}{time.Time{}}}, 983 openDestLogStreamCall, 984 }, 985 )) 986 c.Assert(s.connection.logStream.closeCount, gc.Equals, 1) 987 } 988 989 func (s *Suite) TestLogTransferSendsRecords(c *gc.C) { 990 t1, err := time.Parse("2006-01-02 15:04", "2016-11-28 16:11") 991 c.Assert(err, jc.ErrorIsNil) 992 s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER)) 993 messages := []common.LogMessage{ 994 {Message: "the go team"}, 995 {Message: "joan as police woman"}, 996 { 997 Entity: "the mules", 998 Timestamp: t1, 999 Severity: "warning", 1000 Module: "this one", 1001 Location: "nearby", 1002 Message: "ham shank", 1003 }, 1004 } 1005 s.facade.logMessages = func(d chan<- common.LogMessage) { 1006 for _, message := range messages { 1007 safeSend(c, d, message) 1008 } 1009 } 1010 1011 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 1012 s.stub.CheckCalls(c, joinCalls( 1013 watchStatusLockdownCalls, 1014 []jujutesting.StubCall{ 1015 {"facade.MinionReportTimeout", nil}, 1016 apiOpenControllerCall, 1017 latestLogTimeCall, 1018 {"StreamModelLog", []interface{}{time.Time{}}}, 1019 openDestLogStreamCall, 1020 {"facade.SetPhase", []interface{}{coremigration.REAP}}, 1021 {"facade.Reap", nil}, 1022 {"facade.SetPhase", []interface{}{coremigration.DONE}}, 1023 }, 1024 )) 1025 c.Assert(s.connection.logStream.written, gc.DeepEquals, []params.LogRecord{ 1026 {Message: "the go team"}, 1027 {Message: "joan as police woman"}, 1028 { 1029 Time: t1, 1030 Module: "this one", 1031 Location: "nearby", 1032 Level: "warning", 1033 Message: "ham shank", 1034 Entity: "the mules", 1035 }, 1036 }) 1037 c.Assert(s.connection.logStream.closeCount, gc.Equals, 1) 1038 } 1039 1040 func (s *Suite) TestLogTransferReportsProgress(c *gc.C) { 1041 s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER)) 1042 messages := []common.LogMessage{ 1043 {Message: "captain beefheart"}, 1044 {Message: "super furry animals"}, 1045 {Message: "ezra furman"}, 1046 {Message: "these new puritans"}, 1047 } 1048 s.facade.logMessages = func(d chan<- common.LogMessage) { 1049 for _, message := range messages { 1050 safeSend(c, d, message) 1051 c.Assert(s.clock.WaitAdvance(20*time.Second, coretesting.LongWait, 1), jc.ErrorIsNil) 1052 } 1053 } 1054 1055 var logWriter loggo.TestWriter 1056 c.Assert(loggo.RegisterWriter("migrationmaster-tests", &logWriter), jc.ErrorIsNil) 1057 defer func() { 1058 _, _ = loggo.RemoveWriter("migrationmaster-tests") 1059 logWriter.Clear() 1060 }() 1061 1062 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 1063 1064 c.Assert(logWriter.Log()[:3], jc.LogMatches, []string{ 1065 "successful, transferring logs to target controller \\(0 sent\\)", 1066 // This is a bit of a punt, but without accepting a range 1067 // we sometimes see this test failing on loaded test machines. 1068 "successful, transferring logs to target controller \\([23] sent\\)", 1069 "successful, transferr(ing|ed) logs to target controller \\([234] sent\\)", 1070 }) 1071 } 1072 1073 func (s *Suite) TestLogTransfer_ChecksLatestTime(c *gc.C) { 1074 s.facade.queueStatus(s.makeStatus(coremigration.LOGTRANSFER)) 1075 t := time.Date(2016, 12, 2, 10, 39, 10, 20, time.UTC) 1076 s.connection.latestLogTime = t 1077 1078 s.checkWorkerReturns(c, migrationmaster.ErrMigrated) 1079 s.stub.CheckCalls(c, joinCalls( 1080 watchStatusLockdownCalls, 1081 []jujutesting.StubCall{ 1082 {"facade.MinionReportTimeout", nil}, 1083 apiOpenControllerCall, 1084 latestLogTimeCall, 1085 {"StreamModelLog", []interface{}{t}}, 1086 openDestLogStreamCall, 1087 {"facade.SetPhase", []interface{}{coremigration.REAP}}, 1088 {"facade.Reap", nil}, 1089 {"facade.SetPhase", []interface{}{coremigration.DONE}}, 1090 }, 1091 )) 1092 } 1093 1094 func safeSend(c *gc.C, d chan<- common.LogMessage, message common.LogMessage) { 1095 select { 1096 case d <- message: 1097 case <-time.After(coretesting.ShortWait): 1098 c.Fatalf("timed out sending log message") 1099 } 1100 } 1101 1102 func (s *Suite) checkWorkerReturns(c *gc.C, expected error) { 1103 err := s.runWorker(c) 1104 c.Check(errors.Cause(err), gc.Equals, expected) 1105 } 1106 1107 func (s *Suite) checkWorkerErr(c *gc.C, expected string) { 1108 err := s.runWorker(c) 1109 c.Check(err, gc.ErrorMatches, expected) 1110 } 1111 1112 func (s *Suite) runWorker(c *gc.C) error { 1113 w, err := migrationmaster.New(s.config) 1114 c.Assert(err, jc.ErrorIsNil) 1115 defer workertest.DirtyKill(c, w) 1116 return workertest.CheckKilled(c, w) 1117 } 1118 1119 func (s *Suite) waitForStubCalls(c *gc.C, expectedCallNames []string) { 1120 var callNames []string 1121 for a := coretesting.LongAttempt.Start(); a.Next(); { 1122 callNames = stubCallNames(s.stub) 1123 if reflect.DeepEqual(callNames, expectedCallNames) { 1124 return 1125 } 1126 } 1127 c.Fatalf("failed to see expected calls\nobtained: %v\nexpected: %v", 1128 callNames, expectedCallNames) 1129 } 1130 1131 func (s *Suite) checkMinionWaitWatchError(c *gc.C, phase coremigration.Phase) { 1132 s.facade.minionReportsWatchErr = errors.New("boom") 1133 s.facade.queueStatus(s.makeStatus(phase)) 1134 1135 s.checkWorkerErr(c, "boom") 1136 } 1137 1138 func (s *Suite) checkMinionWaitGetError(c *gc.C, phase coremigration.Phase) { 1139 s.facade.queueStatus(s.makeStatus(phase)) 1140 1141 s.facade.minionReportsErr = errors.New("boom") 1142 s.facade.triggerMinionReports() 1143 1144 s.checkWorkerErr(c, "boom") 1145 } 1146 1147 // assertExpectedCallArgs checks that the stub has been called with the 1148 // expected arguments. It ignores the facade versions map on the Prechecks 1149 // call because that's an implementation detail of the api facade, not the 1150 // worker. As long as it's non-zero, otherwise we don't care. 1151 func assertExpectedCallArgs(c *gc.C, stub *jujutesting.Stub, expectedCalls []jujutesting.StubCall) { 1152 stub.CheckCallNames(c, callNames(expectedCalls)...) 1153 for i, call := range expectedCalls { 1154 stubCall := stub.Calls()[i] 1155 1156 if call.FuncName == "MigrationTarget.Prechecks" { 1157 mc := jc.NewMultiChecker() 1158 mc.AddExpr("_.FacadeVersions", gc.Not(gc.HasLen), 0) 1159 1160 c.Assert(stubCall.Args, mc, call.Args, gc.Commentf("call %s", call.FuncName)) 1161 continue 1162 } 1163 1164 c.Assert(stubCall, jc.DeepEquals, call, gc.Commentf("call %s", call.FuncName)) 1165 } 1166 } 1167 1168 func stubCallNames(stub *jujutesting.Stub) []string { 1169 var out []string 1170 for _, call := range stub.Calls() { 1171 out = append(out, call.FuncName) 1172 } 1173 return out 1174 } 1175 1176 func newStubGuard(stub *jujutesting.Stub) *stubGuard { 1177 return &stubGuard{stub: stub} 1178 } 1179 1180 type stubGuard struct { 1181 stub *jujutesting.Stub 1182 unlockErr error 1183 lockdownErr error 1184 } 1185 1186 func (g *stubGuard) Lockdown(fortress.Abort) error { 1187 g.stub.AddCall("guard.Lockdown") 1188 return g.lockdownErr 1189 } 1190 1191 func (g *stubGuard) Unlock() error { 1192 g.stub.AddCall("guard.Unlock") 1193 return g.unlockErr 1194 } 1195 1196 func newStubMasterFacade(stub *jujutesting.Stub) *stubMasterFacade { 1197 return &stubMasterFacade{ 1198 stub: stub, 1199 watcherChanges: make(chan struct{}, 999), 1200 1201 // Give minionReportsChanges a larger-than-required buffer to 1202 // support waits at a number of phases. 1203 minionReportsChanges: make(chan struct{}, 999), 1204 minionReportTimeout: 15 * time.Minute, 1205 } 1206 } 1207 1208 type stubMasterFacade struct { 1209 migrationmaster.Facade 1210 1211 stub *jujutesting.Stub 1212 1213 watcherChanges chan struct{} 1214 watchErr error 1215 status []coremigration.MigrationStatus 1216 statusErr error 1217 1218 prechecksErr error 1219 modelInfoErr error 1220 exportErr error 1221 processRelationsErr error 1222 1223 logMessages func(chan<- common.LogMessage) 1224 streamErr error 1225 1226 minionReportsChanges chan struct{} 1227 minionReportsWatchErr error 1228 minionReports []coremigration.MinionReports 1229 minionReportsErr error 1230 minionReportTimeout time.Duration 1231 1232 exportedResources []coremigration.SerializedModelResource 1233 1234 statuses []string 1235 } 1236 1237 func (f *stubMasterFacade) triggerWatcher() { 1238 select { 1239 case f.watcherChanges <- struct{}{}: 1240 default: 1241 panic("migration watcher channel unexpectedly closed") 1242 } 1243 } 1244 1245 func (f *stubMasterFacade) queueStatus(status coremigration.MigrationStatus) { 1246 f.status = append(f.status, status) 1247 f.triggerWatcher() 1248 } 1249 1250 func (f *stubMasterFacade) triggerMinionReports() { 1251 select { 1252 case f.minionReportsChanges <- struct{}{}: 1253 default: 1254 panic("minion reports watcher channel unexpectedly closed") 1255 } 1256 } 1257 1258 func (f *stubMasterFacade) queueMinionReports(r coremigration.MinionReports) { 1259 f.minionReports = append(f.minionReports, r) 1260 f.triggerMinionReports() 1261 } 1262 1263 func (f *stubMasterFacade) Watch() (watcher.NotifyWatcher, error) { 1264 f.stub.AddCall("facade.Watch") 1265 if f.watchErr != nil { 1266 return nil, f.watchErr 1267 } 1268 return newMockWatcher(f.watcherChanges), nil 1269 } 1270 1271 func (f *stubMasterFacade) MigrationStatus() (coremigration.MigrationStatus, error) { 1272 f.stub.AddCall("facade.MigrationStatus") 1273 if f.statusErr != nil { 1274 return coremigration.MigrationStatus{}, f.statusErr 1275 } 1276 if len(f.status) == 0 { 1277 panic("no status queued to report") 1278 } 1279 out := f.status[0] 1280 f.status = f.status[1:] 1281 return out, nil 1282 } 1283 1284 func (f *stubMasterFacade) WatchMinionReports() (watcher.NotifyWatcher, error) { 1285 f.stub.AddCall("facade.WatchMinionReports") 1286 if f.minionReportsWatchErr != nil { 1287 return nil, f.minionReportsWatchErr 1288 } 1289 return newMockWatcher(f.minionReportsChanges), nil 1290 } 1291 1292 func (f *stubMasterFacade) MinionReports() (coremigration.MinionReports, error) { 1293 f.stub.AddCall("facade.MinionReports") 1294 if f.minionReportsErr != nil { 1295 return coremigration.MinionReports{}, f.minionReportsErr 1296 } 1297 if len(f.minionReports) == 0 { 1298 return coremigration.MinionReports{}, errors.NotFoundf("reports") 1299 1300 } 1301 r := f.minionReports[0] 1302 f.minionReports = f.minionReports[1:] 1303 return r, nil 1304 } 1305 1306 func (f *stubMasterFacade) MinionReportTimeout() (time.Duration, error) { 1307 f.stub.AddCall("facade.MinionReportTimeout") 1308 return f.minionReportTimeout, nil 1309 } 1310 1311 func (f *stubMasterFacade) Prechecks() error { 1312 f.stub.AddCall("facade.Prechecks") 1313 return f.prechecksErr 1314 } 1315 1316 func (f *stubMasterFacade) ModelInfo() (coremigration.ModelInfo, error) { 1317 f.stub.AddCall("facade.ModelInfo") 1318 if f.modelInfoErr != nil { 1319 return coremigration.ModelInfo{}, f.modelInfoErr 1320 } 1321 return coremigration.ModelInfo{ 1322 UUID: modelUUID, 1323 Name: modelName, 1324 Owner: ownerTag, 1325 AgentVersion: modelVersion, 1326 }, nil 1327 } 1328 1329 func (f *stubMasterFacade) SourceControllerInfo() (coremigration.SourceControllerInfo, []string, error) { 1330 f.stub.AddCall("facade.SourceControllerInfo") 1331 return coremigration.SourceControllerInfo{ 1332 ControllerTag: sourceControllerTag, 1333 ControllerAlias: "mycontroller", 1334 Addrs: []string{"source-addr"}, 1335 CACert: "cacert", 1336 }, []string{"related-model-uuid"}, nil 1337 } 1338 1339 func (f *stubMasterFacade) Export() (coremigration.SerializedModel, error) { 1340 f.stub.AddCall("facade.Export") 1341 if f.exportErr != nil { 1342 return coremigration.SerializedModel{}, f.exportErr 1343 } 1344 return coremigration.SerializedModel{ 1345 Bytes: fakeModelBytes, 1346 Charms: []string{"charm0", "charm1"}, 1347 Tools: map[version.Binary]string{ 1348 version.MustParseBinary("2.1.0-ubuntu-amd64"): "/tools/0", 1349 }, 1350 Resources: f.exportedResources, 1351 }, nil 1352 } 1353 1354 func (f *stubMasterFacade) ProcessRelations(controllerAlias string) error { 1355 f.stub.AddCall("facade.ProcessRelations", controllerAlias) 1356 if f.processRelationsErr != nil { 1357 return f.processRelationsErr 1358 } 1359 return nil 1360 } 1361 1362 func (f *stubMasterFacade) SetPhase(phase coremigration.Phase) error { 1363 f.stub.AddCall("facade.SetPhase", phase) 1364 return nil 1365 } 1366 1367 func (f *stubMasterFacade) SetStatusMessage(message string) error { 1368 f.statuses = append(f.statuses, message) 1369 return nil 1370 } 1371 1372 func (f *stubMasterFacade) Reap() error { 1373 f.stub.AddCall("facade.Reap") 1374 return nil 1375 } 1376 1377 func (f *stubMasterFacade) StreamModelLog(_ context.Context, start time.Time) (<-chan common.LogMessage, error) { 1378 f.stub.AddCall("StreamModelLog", start) 1379 if f.streamErr != nil { 1380 return nil, f.streamErr 1381 } 1382 result := make(chan common.LogMessage) 1383 messageFunc := f.logMessages 1384 if messageFunc == nil { 1385 messageFunc = func(chan<- common.LogMessage) {} 1386 } 1387 go func() { 1388 defer close(result) 1389 messageFunc(result) 1390 }() 1391 return result, nil 1392 } 1393 1394 func newMockWatcher(changes chan struct{}) *mockWatcher { 1395 return &mockWatcher{ 1396 Worker: workertest.NewErrorWorker(nil), 1397 changes: changes, 1398 } 1399 } 1400 1401 type mockWatcher struct { 1402 worker.Worker 1403 changes chan struct{} 1404 } 1405 1406 func (w *mockWatcher) Changes() watcher.NotifyChannel { 1407 return w.changes 1408 } 1409 1410 type stubConnection struct { 1411 c *gc.C 1412 api.Connection 1413 stub *jujutesting.Stub 1414 prechecksErr error 1415 importErr error 1416 processRelationsErr error 1417 controllerTag names.ControllerTag 1418 1419 streamErr error 1420 logStream *mockStream 1421 1422 latestLogErr error 1423 latestLogTime time.Time 1424 1425 machineErrs []string 1426 checkMachineErr error 1427 1428 facadeVersion int 1429 1430 controllerVersion params.ControllerVersionResults 1431 } 1432 1433 func (c *stubConnection) BestFacadeVersion(string) int { 1434 return c.facadeVersion 1435 } 1436 1437 func (c *stubConnection) APICall(objType string, _ int, _, request string, args, response interface{}) error { 1438 c.stub.AddCall(objType+"."+request, args) 1439 1440 if objType == "MigrationTarget" { 1441 switch request { 1442 case "Prechecks": 1443 return c.prechecksErr 1444 case "Import": 1445 return c.importErr 1446 case "ProcessRelations": 1447 return c.processRelationsErr 1448 case "Activate", "AdoptResources": 1449 return nil 1450 case "LatestLogTime": 1451 responseTime := response.(*time.Time) 1452 // This is needed because even if a zero time comes back 1453 // from the API it will have a timezone attached. 1454 *responseTime = c.latestLogTime.In(time.UTC) 1455 return c.latestLogErr 1456 case "CheckMachines": 1457 results := response.(*params.ErrorResults) 1458 for _, msg := range c.machineErrs { 1459 results.Results = append(results.Results, params.ErrorResult{ 1460 Error: apiservererrors.ServerError(errors.Errorf(msg)), 1461 }) 1462 } 1463 return c.checkMachineErr 1464 } 1465 } else if objType == "Controller" { 1466 switch request { 1467 case "ControllerVersion": 1468 c.c.Logf("objType %q request %q, args %#v", objType, request, args) 1469 controllerVersion := response.(*params.ControllerVersionResults) 1470 *controllerVersion = c.controllerVersion 1471 return nil 1472 } 1473 } 1474 return errors.New("unexpected API call") 1475 } 1476 1477 func (c *stubConnection) Client() *apiclient.Client { 1478 // This is kinda crappy but the *Client doesn't have to be 1479 // functional... 1480 return new(apiclient.Client) 1481 } 1482 1483 func (c *stubConnection) Close() error { 1484 c.stub.AddCall("Connection.Close") 1485 return nil 1486 } 1487 1488 func (c *stubConnection) ControllerTag() names.ControllerTag { 1489 return c.controllerTag 1490 } 1491 1492 func (c *stubConnection) ConnectControllerStream(path string, attrs url.Values, headers http.Header) (base.Stream, error) { 1493 c.stub.AddCall("ConnectControllerStream", path, attrs, headers) 1494 if c.streamErr != nil { 1495 return nil, c.streamErr 1496 } 1497 return c.logStream, nil 1498 } 1499 1500 func makeStubUploadBinaries(stub *jujutesting.Stub) func(migration.UploadBinariesConfig) error { 1501 return func(config migration.UploadBinariesConfig) error { 1502 stub.AddCall( 1503 "UploadBinaries", 1504 config.Charms, 1505 config.CharmDownloader, 1506 config.Tools, 1507 config.ToolsDownloader, 1508 config.Resources, 1509 config.ResourceDownloader, 1510 ) 1511 return nil 1512 } 1513 } 1514 1515 // nullUploadBinaries is a UploadBinaries variant which is intended to 1516 // not get called. 1517 func nullUploadBinaries(migration.UploadBinariesConfig) error { 1518 panic("should not get called") 1519 } 1520 1521 var fakeCharmDownloader = struct{ migration.CharmDownloader }{} 1522 1523 var fakeToolsDownloader = struct{ migration.ToolsDownloader }{} 1524 1525 func joinCalls(allCalls ...[]jujutesting.StubCall) (out []jujutesting.StubCall) { 1526 for _, calls := range allCalls { 1527 out = append(out, calls...) 1528 } 1529 return 1530 } 1531 1532 func callNames(calls []jujutesting.StubCall) []string { 1533 var out []string 1534 for _, call := range calls { 1535 out = append(out, call.FuncName) 1536 } 1537 return out 1538 } 1539 1540 func makeMinionReports(p coremigration.Phase) coremigration.MinionReports { 1541 return coremigration.MinionReports{ 1542 MigrationId: "model-uuid:2", 1543 Phase: p, 1544 SuccessCount: 5, 1545 UnknownCount: 0, 1546 } 1547 } 1548 1549 type mockStream struct { 1550 base.Stream 1551 c *gc.C 1552 written []params.LogRecord 1553 writeErr error 1554 closeCount int 1555 } 1556 1557 func (s *mockStream) WriteJSON(v interface{}) error { 1558 if s.writeErr != nil { 1559 return s.writeErr 1560 } 1561 rec, ok := v.(params.LogRecord) 1562 if !ok { 1563 s.c.Errorf("unexpected value written to stream: %v", v) 1564 return nil 1565 } 1566 s.written = append(s.written, rec) 1567 return nil 1568 } 1569 1570 func (s *mockStream) Close() error { 1571 s.closeCount++ 1572 return nil 1573 }