github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "time" 8 9 "github.com/juju/errors" 10 "github.com/juju/names" 11 jujutesting "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 gc "gopkg.in/check.v1" 14 15 "github.com/juju/juju/api" 16 masterapi "github.com/juju/juju/api/migrationmaster" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/core/migration" 19 coretesting "github.com/juju/juju/testing" 20 "github.com/juju/juju/watcher" 21 "github.com/juju/juju/worker" 22 "github.com/juju/juju/worker/dependency" 23 "github.com/juju/juju/worker/fortress" 24 "github.com/juju/juju/worker/migrationmaster" 25 "github.com/juju/juju/worker/workertest" 26 ) 27 28 type Suite struct { 29 coretesting.BaseSuite 30 stub *jujutesting.Stub 31 connection *stubConnection 32 connectionErr error 33 } 34 35 var _ = gc.Suite(&Suite{}) 36 37 var ( 38 fakeSerializedModel = []byte("model") 39 modelTagString = names.NewModelTag("model-uuid").String() 40 41 // Define stub calls that commonly appear in tests here to allow reuse. 42 apiOpenCall = jujutesting.StubCall{ 43 "apiOpen", 44 []interface{}{ 45 &api.Info{ 46 Addrs: []string{"1.2.3.4:5"}, 47 CACert: "cert", 48 Tag: names.NewUserTag("admin"), 49 Password: "secret", 50 }, 51 api.DialOpts{}, 52 }, 53 } 54 importCall = jujutesting.StubCall{ 55 "APICall:MigrationTarget.Import", 56 []interface{}{ 57 params.SerializedModel{Bytes: fakeSerializedModel}, 58 }, 59 } 60 activateCall = jujutesting.StubCall{ 61 "APICall:MigrationTarget.Activate", 62 []interface{}{ 63 params.ModelArgs{ModelTag: modelTagString}, 64 }, 65 } 66 connCloseCall = jujutesting.StubCall{"Connection.Close", nil} 67 abortCall = jujutesting.StubCall{ 68 "APICall:MigrationTarget.Abort", 69 []interface{}{ 70 params.ModelArgs{ModelTag: modelTagString}, 71 }, 72 } 73 ) 74 75 func (s *Suite) SetUpTest(c *gc.C) { 76 s.BaseSuite.SetUpTest(c) 77 78 s.stub = new(jujutesting.Stub) 79 s.connection = &stubConnection{stub: s.stub} 80 s.connectionErr = nil 81 s.PatchValue(migrationmaster.ApiOpen, s.apiOpen) 82 s.PatchValue(migrationmaster.TempSuccessSleep, time.Millisecond) 83 } 84 85 func (s *Suite) apiOpen(info *api.Info, dialOpts api.DialOpts) (api.Connection, error) { 86 s.stub.AddCall("apiOpen", info, dialOpts) 87 if s.connectionErr != nil { 88 return nil, s.connectionErr 89 } 90 return s.connection, nil 91 } 92 93 func (s *Suite) triggerMigration(masterClient *stubMasterClient) { 94 masterClient.watcherChanges <- struct{}{} 95 } 96 97 func (s *Suite) TestSuccessfulMigration(c *gc.C) { 98 masterClient := newStubMasterClient(s.stub) 99 worker, err := migrationmaster.New(migrationmaster.Config{ 100 Facade: masterClient, 101 Guard: newStubGuard(s.stub), 102 }) 103 c.Assert(err, jc.ErrorIsNil) 104 s.triggerMigration(masterClient) 105 106 err = workertest.CheckKilled(c, worker) 107 c.Assert(errors.Cause(err), gc.Equals, dependency.ErrUninstall) 108 109 // Observe that the migration was seen, the model exported, an API 110 // connection to the target controller was made, the model was 111 // imported and then the migration completed. 112 s.stub.CheckCalls(c, []jujutesting.StubCall{ 113 {"masterClient.Watch", nil}, 114 {"masterClient.GetMigrationStatus", nil}, 115 {"guard.Lockdown", nil}, 116 {"masterClient.SetPhase", []interface{}{migration.READONLY}}, 117 {"masterClient.SetPhase", []interface{}{migration.PRECHECK}}, 118 {"masterClient.SetPhase", []interface{}{migration.IMPORT}}, 119 {"masterClient.Export", nil}, 120 apiOpenCall, 121 importCall, 122 connCloseCall, 123 {"masterClient.SetPhase", []interface{}{migration.VALIDATION}}, 124 apiOpenCall, 125 activateCall, 126 connCloseCall, 127 {"masterClient.SetPhase", []interface{}{migration.SUCCESS}}, 128 {"masterClient.SetPhase", []interface{}{migration.LOGTRANSFER}}, 129 {"masterClient.SetPhase", []interface{}{migration.REAP}}, 130 {"masterClient.SetPhase", []interface{}{migration.DONE}}, 131 }) 132 } 133 134 func (s *Suite) TestMigrationResume(c *gc.C) { 135 // Test that a partially complete migration can be resumed. 136 137 masterClient := newStubMasterClient(s.stub) 138 worker, err := migrationmaster.New(migrationmaster.Config{ 139 Facade: masterClient, 140 Guard: newStubGuard(s.stub), 141 }) 142 c.Assert(err, jc.ErrorIsNil) 143 masterClient.status.Phase = migration.SUCCESS 144 s.triggerMigration(masterClient) 145 146 err = workertest.CheckKilled(c, worker) 147 c.Assert(errors.Cause(err), gc.Equals, dependency.ErrUninstall) 148 149 s.stub.CheckCalls(c, []jujutesting.StubCall{ 150 {"masterClient.Watch", nil}, 151 {"masterClient.GetMigrationStatus", nil}, 152 {"guard.Lockdown", nil}, 153 {"masterClient.SetPhase", []interface{}{migration.LOGTRANSFER}}, 154 {"masterClient.SetPhase", []interface{}{migration.REAP}}, 155 {"masterClient.SetPhase", []interface{}{migration.DONE}}, 156 }) 157 } 158 159 func (s *Suite) TestPreviouslyAbortedMigration(c *gc.C) { 160 masterClient := newStubMasterClient(s.stub) 161 masterClient.status.Phase = migration.ABORTDONE 162 s.triggerMigration(masterClient) 163 worker, err := migrationmaster.New(migrationmaster.Config{ 164 Facade: masterClient, 165 Guard: newStubGuard(s.stub), 166 }) 167 c.Assert(err, jc.ErrorIsNil) 168 workertest.CheckAlive(c, worker) 169 workertest.CleanKill(c, worker) 170 171 // No reliable way to test stub calls in this case unfortunately. 172 } 173 174 func (s *Suite) TestPreviouslyCompletedMigration(c *gc.C) { 175 masterClient := newStubMasterClient(s.stub) 176 masterClient.status.Phase = migration.DONE 177 s.triggerMigration(masterClient) 178 worker, err := migrationmaster.New(migrationmaster.Config{ 179 Facade: masterClient, 180 Guard: newStubGuard(s.stub), 181 }) 182 c.Assert(err, jc.ErrorIsNil) 183 184 err = workertest.CheckKilled(c, worker) 185 c.Assert(errors.Cause(err), gc.Equals, dependency.ErrUninstall) 186 187 s.stub.CheckCalls(c, []jujutesting.StubCall{ 188 {"masterClient.Watch", nil}, 189 {"masterClient.GetMigrationStatus", nil}, 190 }) 191 } 192 193 func (s *Suite) TestWatchFailure(c *gc.C) { 194 masterClient := newStubMasterClient(s.stub) 195 masterClient.watchErr = errors.New("boom") 196 worker, err := migrationmaster.New(migrationmaster.Config{ 197 Facade: masterClient, 198 Guard: newStubGuard(s.stub), 199 }) 200 c.Assert(err, jc.ErrorIsNil) 201 err = workertest.CheckKilled(c, worker) 202 c.Assert(err, gc.ErrorMatches, "watching for migration: boom") 203 } 204 205 func (s *Suite) TestStatusError(c *gc.C) { 206 masterClient := newStubMasterClient(s.stub) 207 masterClient.statusErr = errors.New("splat") 208 worker, err := migrationmaster.New(migrationmaster.Config{ 209 Facade: masterClient, 210 Guard: newStubGuard(s.stub), 211 }) 212 c.Assert(err, jc.ErrorIsNil) 213 s.triggerMigration(masterClient) 214 215 err = workertest.CheckKilled(c, worker) 216 217 s.stub.CheckCalls(c, []jujutesting.StubCall{ 218 {"masterClient.Watch", nil}, 219 {"masterClient.GetMigrationStatus", nil}, 220 }) 221 } 222 223 func (s *Suite) TestStatusNotFound(c *gc.C) { 224 masterClient := newStubMasterClient(s.stub) 225 masterClient.statusErr = ¶ms.Error{Code: params.CodeNotFound} 226 worker, err := migrationmaster.New(migrationmaster.Config{ 227 Facade: masterClient, 228 Guard: newStubGuard(s.stub), 229 }) 230 c.Assert(err, jc.ErrorIsNil) 231 s.triggerMigration(masterClient) 232 233 workertest.CheckAlive(c, worker) 234 workertest.CleanKill(c, worker) 235 236 s.stub.CheckCalls(c, []jujutesting.StubCall{ 237 {"masterClient.Watch", nil}, 238 {"masterClient.GetMigrationStatus", nil}, 239 {"guard.Unlock", nil}, 240 }) 241 } 242 243 func (s *Suite) TestUnlockError(c *gc.C) { 244 masterClient := newStubMasterClient(s.stub) 245 masterClient.statusErr = ¶ms.Error{Code: params.CodeNotFound} 246 guard := newStubGuard(s.stub) 247 guard.unlockErr = errors.New("pow") 248 worker, err := migrationmaster.New(migrationmaster.Config{ 249 Facade: masterClient, 250 Guard: guard, 251 }) 252 c.Assert(err, jc.ErrorIsNil) 253 s.triggerMigration(masterClient) 254 255 err = workertest.CheckKilled(c, worker) 256 c.Check(err, gc.ErrorMatches, "pow") 257 258 s.stub.CheckCalls(c, []jujutesting.StubCall{ 259 {"masterClient.Watch", nil}, 260 {"masterClient.GetMigrationStatus", nil}, 261 {"guard.Unlock", nil}, 262 }) 263 } 264 265 func (s *Suite) TestLockdownError(c *gc.C) { 266 masterClient := newStubMasterClient(s.stub) 267 guard := newStubGuard(s.stub) 268 guard.lockdownErr = errors.New("biff") 269 worker, err := migrationmaster.New(migrationmaster.Config{ 270 Facade: masterClient, 271 Guard: guard, 272 }) 273 c.Assert(err, jc.ErrorIsNil) 274 s.triggerMigration(masterClient) 275 276 err = workertest.CheckKilled(c, worker) 277 c.Check(err, gc.ErrorMatches, "biff") 278 279 s.stub.CheckCalls(c, []jujutesting.StubCall{ 280 {"masterClient.Watch", nil}, 281 {"masterClient.GetMigrationStatus", nil}, 282 {"guard.Lockdown", nil}, 283 }) 284 } 285 286 func (s *Suite) TestExportFailure(c *gc.C) { 287 masterClient := newStubMasterClient(s.stub) 288 masterClient.exportErr = errors.New("boom") 289 worker, err := migrationmaster.New(migrationmaster.Config{ 290 Facade: masterClient, 291 Guard: newStubGuard(s.stub), 292 }) 293 c.Assert(err, jc.ErrorIsNil) 294 s.triggerMigration(masterClient) 295 296 err = workertest.CheckKilled(c, worker) 297 c.Assert(err, gc.Equals, migrationmaster.ErrDoneForNow) 298 299 s.stub.CheckCalls(c, []jujutesting.StubCall{ 300 {"masterClient.Watch", nil}, 301 {"masterClient.GetMigrationStatus", nil}, 302 {"guard.Lockdown", nil}, 303 {"masterClient.SetPhase", []interface{}{migration.READONLY}}, 304 {"masterClient.SetPhase", []interface{}{migration.PRECHECK}}, 305 {"masterClient.SetPhase", []interface{}{migration.IMPORT}}, 306 {"masterClient.Export", nil}, 307 {"masterClient.SetPhase", []interface{}{migration.ABORT}}, 308 apiOpenCall, 309 abortCall, 310 connCloseCall, 311 {"masterClient.SetPhase", []interface{}{migration.ABORTDONE}}, 312 }) 313 } 314 315 func (s *Suite) TestAPIOpenFailure(c *gc.C) { 316 masterClient := newStubMasterClient(s.stub) 317 worker, err := migrationmaster.New(migrationmaster.Config{ 318 Facade: masterClient, 319 Guard: newStubGuard(s.stub), 320 }) 321 c.Assert(err, jc.ErrorIsNil) 322 s.connectionErr = errors.New("boom") 323 s.triggerMigration(masterClient) 324 325 err = workertest.CheckKilled(c, worker) 326 c.Assert(err, gc.Equals, migrationmaster.ErrDoneForNow) 327 328 s.stub.CheckCalls(c, []jujutesting.StubCall{ 329 {"masterClient.Watch", nil}, 330 {"masterClient.GetMigrationStatus", nil}, 331 {"guard.Lockdown", nil}, 332 {"masterClient.SetPhase", []interface{}{migration.READONLY}}, 333 {"masterClient.SetPhase", []interface{}{migration.PRECHECK}}, 334 {"masterClient.SetPhase", []interface{}{migration.IMPORT}}, 335 {"masterClient.Export", nil}, 336 apiOpenCall, 337 {"masterClient.SetPhase", []interface{}{migration.ABORT}}, 338 apiOpenCall, 339 {"masterClient.SetPhase", []interface{}{migration.ABORTDONE}}, 340 }) 341 } 342 343 func (s *Suite) TestImportFailure(c *gc.C) { 344 masterClient := newStubMasterClient(s.stub) 345 worker, err := migrationmaster.New(migrationmaster.Config{ 346 Facade: masterClient, 347 Guard: newStubGuard(s.stub), 348 }) 349 c.Assert(err, jc.ErrorIsNil) 350 s.connection.importErr = errors.New("boom") 351 s.triggerMigration(masterClient) 352 353 err = workertest.CheckKilled(c, worker) 354 c.Assert(err, gc.Equals, migrationmaster.ErrDoneForNow) 355 356 s.stub.CheckCalls(c, []jujutesting.StubCall{ 357 {"masterClient.Watch", nil}, 358 {"masterClient.GetMigrationStatus", nil}, 359 {"guard.Lockdown", nil}, 360 {"masterClient.SetPhase", []interface{}{migration.READONLY}}, 361 {"masterClient.SetPhase", []interface{}{migration.PRECHECK}}, 362 {"masterClient.SetPhase", []interface{}{migration.IMPORT}}, 363 {"masterClient.Export", nil}, 364 apiOpenCall, 365 importCall, 366 connCloseCall, 367 {"masterClient.SetPhase", []interface{}{migration.ABORT}}, 368 apiOpenCall, 369 abortCall, 370 connCloseCall, 371 {"masterClient.SetPhase", []interface{}{migration.ABORTDONE}}, 372 }) 373 } 374 375 func newStubGuard(stub *jujutesting.Stub) *stubGuard { 376 return &stubGuard{stub: stub} 377 } 378 379 type stubGuard struct { 380 stub *jujutesting.Stub 381 unlockErr error 382 lockdownErr error 383 } 384 385 func (g *stubGuard) Lockdown(fortress.Abort) error { 386 g.stub.AddCall("guard.Lockdown") 387 return g.lockdownErr 388 } 389 390 func (g *stubGuard) Unlock() error { 391 g.stub.AddCall("guard.Unlock") 392 return g.unlockErr 393 } 394 395 func newStubMasterClient(stub *jujutesting.Stub) *stubMasterClient { 396 return &stubMasterClient{ 397 stub: stub, 398 watcherChanges: make(chan struct{}, 1), 399 status: masterapi.MigrationStatus{ 400 ModelUUID: "model-uuid", 401 Attempt: 2, 402 Phase: migration.QUIESCE, 403 TargetInfo: migration.TargetInfo{ 404 ControllerTag: names.NewModelTag("controller-uuid"), 405 Addrs: []string{"1.2.3.4:5"}, 406 CACert: "cert", 407 AuthTag: names.NewUserTag("admin"), 408 Password: "secret", 409 }, 410 }, 411 } 412 } 413 414 type stubMasterClient struct { 415 masterapi.Client 416 stub *jujutesting.Stub 417 watcherChanges chan struct{} 418 watchErr error 419 status masterapi.MigrationStatus 420 statusErr error 421 exportErr error 422 } 423 424 func (c *stubMasterClient) Watch() (watcher.NotifyWatcher, error) { 425 c.stub.AddCall("masterClient.Watch") 426 if c.watchErr != nil { 427 return nil, c.watchErr 428 } 429 430 return newMockWatcher(c.watcherChanges), nil 431 } 432 433 func (c *stubMasterClient) GetMigrationStatus() (masterapi.MigrationStatus, error) { 434 c.stub.AddCall("masterClient.GetMigrationStatus") 435 if c.statusErr != nil { 436 return masterapi.MigrationStatus{}, c.statusErr 437 } 438 return c.status, nil 439 } 440 441 func (c *stubMasterClient) Export() ([]byte, error) { 442 c.stub.AddCall("masterClient.Export") 443 if c.exportErr != nil { 444 return nil, c.exportErr 445 } 446 return fakeSerializedModel, nil 447 } 448 449 func (c *stubMasterClient) SetPhase(phase migration.Phase) error { 450 c.stub.AddCall("masterClient.SetPhase", phase) 451 return nil 452 } 453 454 func newMockWatcher(changes chan struct{}) *mockWatcher { 455 return &mockWatcher{ 456 Worker: workertest.NewErrorWorker(nil), 457 changes: changes, 458 } 459 } 460 461 type mockWatcher struct { 462 worker.Worker 463 changes chan struct{} 464 } 465 466 func (w *mockWatcher) Changes() watcher.NotifyChannel { 467 return w.changes 468 } 469 470 type stubConnection struct { 471 api.Connection 472 stub *jujutesting.Stub 473 importErr error 474 } 475 476 func (c *stubConnection) BestFacadeVersion(string) int { 477 return 1 478 } 479 480 func (c *stubConnection) APICall(objType string, version int, id, request string, params, response interface{}) error { 481 c.stub.AddCall("APICall:"+objType+"."+request, params) 482 483 if objType == "MigrationTarget" { 484 switch request { 485 case "Import": 486 return c.importErr 487 case "Activate": 488 return nil 489 } 490 } 491 return errors.New("unexpected API call") 492 } 493 494 func (c *stubConnection) Close() error { 495 c.stub.AddCall("Connection.Close") 496 return nil 497 }