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