github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/controller/migrationmaster/facade_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 "fmt" 8 "time" 9 10 "github.com/juju/description" 11 "github.com/juju/errors" 12 "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils" 15 "github.com/juju/version" 16 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/names.v2" 18 "gopkg.in/macaroon.v2-unstable" 19 20 "github.com/juju/juju/apiserver/common" 21 "github.com/juju/juju/apiserver/facade" 22 "github.com/juju/juju/apiserver/facades/controller/migrationmaster" 23 "github.com/juju/juju/apiserver/params" 24 apiservertesting "github.com/juju/juju/apiserver/testing" 25 coremigration "github.com/juju/juju/core/migration" 26 "github.com/juju/juju/core/presence" 27 "github.com/juju/juju/migration" 28 "github.com/juju/juju/state" 29 coretesting "github.com/juju/juju/testing" 30 jujuversion "github.com/juju/juju/version" 31 ) 32 33 type Suite struct { 34 coretesting.BaseSuite 35 36 model description.Model 37 stub *testing.Stub 38 backend *stubBackend 39 resources *common.Resources 40 authorizer apiservertesting.FakeAuthorizer 41 } 42 43 var _ = gc.Suite(&Suite{}) 44 45 func (s *Suite) SetUpTest(c *gc.C) { 46 s.BaseSuite.SetUpTest(c) 47 48 s.model = description.NewModel(description.ModelArgs{ 49 Type: "iaas", 50 Config: map[string]interface{}{"uuid": modelUUID}, 51 Owner: names.NewUserTag("admin"), 52 LatestToolsVersion: jujuversion.Current, 53 }) 54 s.stub = new(testing.Stub) 55 s.backend = &stubBackend{ 56 migration: &stubMigration{stub: s.stub}, 57 stub: s.stub, 58 model: s.model, 59 } 60 61 s.resources = common.NewResources() 62 s.AddCleanup(func(*gc.C) { s.resources.StopAll() }) 63 64 s.authorizer = apiservertesting.FakeAuthorizer{ 65 Controller: true, 66 } 67 } 68 69 func (s *Suite) TestNotController(c *gc.C) { 70 s.authorizer.Controller = false 71 72 api, err := s.makeAPI() 73 c.Assert(api, gc.IsNil) 74 c.Assert(err, gc.Equals, common.ErrPerm) 75 } 76 77 func (s *Suite) TestWatch(c *gc.C) { 78 api := s.mustMakeAPI(c) 79 80 result := api.Watch() 81 c.Assert(result.Error, gc.IsNil) 82 83 resource := s.resources.Get(result.NotifyWatcherId) 84 watcher, _ := resource.(state.NotifyWatcher) 85 c.Assert(watcher, gc.NotNil) 86 87 select { 88 case <-watcher.Changes(): 89 c.Fatalf("initial event not consumed") 90 case <-time.After(coretesting.ShortWait): 91 } 92 } 93 94 func (s *Suite) TestMigrationStatus(c *gc.C) { 95 var expectedMacaroons = ` 96 [[{"caveats":[],"location":"location","identifier":"id","signature":"a9802bf274262733d6283a69c62805b5668dbf475bcd7edc25a962833f7c2cba"}]]`[1:] 97 98 api := s.mustMakeAPI(c) 99 status, err := api.MigrationStatus() 100 c.Assert(err, jc.ErrorIsNil) 101 102 c.Check(status, gc.DeepEquals, params.MasterMigrationStatus{ 103 Spec: params.MigrationSpec{ 104 ModelTag: names.NewModelTag(modelUUID).String(), 105 TargetInfo: params.MigrationTargetInfo{ 106 ControllerTag: names.NewControllerTag(controllerUUID).String(), 107 Addrs: []string{"1.1.1.1:1", "2.2.2.2:2"}, 108 CACert: "trust me", 109 AuthTag: names.NewUserTag("admin").String(), 110 Password: "secret", 111 Macaroons: expectedMacaroons, 112 }, 113 }, 114 MigrationId: "id", 115 Phase: "IMPORT", 116 PhaseChangedTime: s.backend.migration.PhaseChangedTime(), 117 }) 118 } 119 120 func (s *Suite) TestModelInfo(c *gc.C) { 121 api := s.mustMakeAPI(c) 122 model, err := api.ModelInfo() 123 c.Assert(err, jc.ErrorIsNil) 124 c.Assert(model.UUID, gc.Equals, "model-uuid") 125 c.Assert(model.Name, gc.Equals, "model-name") 126 c.Assert(model.OwnerTag, gc.Equals, names.NewUserTag("owner").String()) 127 c.Assert(model.AgentVersion, gc.Equals, version.MustParse("1.2.3")) 128 } 129 130 func (s *Suite) TestSetPhase(c *gc.C) { 131 api := s.mustMakeAPI(c) 132 133 err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"}) 134 c.Assert(err, jc.ErrorIsNil) 135 136 c.Assert(s.backend.migration.phaseSet, gc.Equals, coremigration.ABORT) 137 } 138 139 func (s *Suite) TestSetPhaseNoMigration(c *gc.C) { 140 s.backend.getErr = errors.New("boom") 141 api := s.mustMakeAPI(c) 142 143 err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"}) 144 c.Assert(err, gc.ErrorMatches, "could not get migration: boom") 145 } 146 147 func (s *Suite) TestSetPhaseBadPhase(c *gc.C) { 148 api := s.mustMakeAPI(c) 149 150 err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "wat"}) 151 c.Assert(err, gc.ErrorMatches, `invalid phase: "wat"`) 152 } 153 154 func (s *Suite) TestSetPhaseError(c *gc.C) { 155 s.backend.migration.setPhaseErr = errors.New("blam") 156 api := s.mustMakeAPI(c) 157 158 err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"}) 159 c.Assert(err, gc.ErrorMatches, "failed to set phase: blam") 160 } 161 162 func (s *Suite) TestSetStatusMessage(c *gc.C) { 163 api := s.mustMakeAPI(c) 164 165 err := api.SetStatusMessage(params.SetMigrationStatusMessageArgs{Message: "foo"}) 166 c.Assert(err, jc.ErrorIsNil) 167 c.Check(s.backend.migration.messageSet, gc.Equals, "foo") 168 } 169 170 func (s *Suite) TestSetStatusMessageNoMigration(c *gc.C) { 171 s.backend.getErr = errors.New("boom") 172 api := s.mustMakeAPI(c) 173 174 err := api.SetStatusMessage(params.SetMigrationStatusMessageArgs{Message: "foo"}) 175 c.Check(err, gc.ErrorMatches, "could not get migration: boom") 176 } 177 178 func (s *Suite) TestSetStatusMessageError(c *gc.C) { 179 s.backend.migration.setMessageErr = errors.New("blam") 180 api := s.mustMakeAPI(c) 181 182 err := api.SetStatusMessage(params.SetMigrationStatusMessageArgs{Message: "foo"}) 183 c.Assert(err, gc.ErrorMatches, "failed to set status message: blam") 184 } 185 186 func (s *Suite) TestPrechecks(c *gc.C) { 187 api := s.mustMakeAPI(c) 188 err := api.Prechecks() 189 c.Assert(err, gc.ErrorMatches, "retrieving model: boom") 190 } 191 192 func (s *Suite) TestExportIAAS(c *gc.C) { 193 s.assertExport(c, "iaas") 194 } 195 196 func (s *Suite) TestExportCAAS(c *gc.C) { 197 s.model = description.NewModel(description.ModelArgs{ 198 Type: "caas", 199 Config: map[string]interface{}{"uuid": modelUUID}, 200 Owner: names.NewUserTag("admin"), 201 LatestToolsVersion: jujuversion.Current, 202 }) 203 s.backend.model = s.model 204 s.assertExport(c, "caas") 205 } 206 207 func (s *Suite) assertExport(c *gc.C, modelType string) { 208 app := s.model.AddApplication(description.ApplicationArgs{ 209 Tag: names.NewApplicationTag("foo"), 210 CharmURL: "cs:foo-0", 211 }) 212 213 const tools0 = "2.0.0-xenial-amd64" 214 const tools1 = "2.0.1-xenial-amd64" 215 m := s.model.AddMachine(description.MachineArgs{Id: names.NewMachineTag("9")}) 216 m.SetTools(description.AgentToolsArgs{ 217 Version: version.MustParseBinary(tools1), 218 }) 219 220 res := app.AddResource(description.ResourceArgs{"bin"}) 221 appRev := res.SetApplicationRevision(description.ResourceRevisionArgs{ 222 Revision: 2, 223 Type: "file", 224 Path: "bin.tar.gz", 225 Description: "who knows", 226 Origin: "upload", 227 FingerprintHex: "abcd", 228 Size: 123, 229 Timestamp: time.Now(), 230 Username: "bob", 231 }) 232 csRev := res.SetCharmStoreRevision(description.ResourceRevisionArgs{ 233 Revision: 3, 234 Type: "file", 235 Path: "fink.tar.gz", 236 Description: "knows who", 237 Origin: "store", 238 FingerprintHex: "deaf", 239 Size: 321, 240 Timestamp: time.Now(), 241 Username: "xena", 242 }) 243 244 unit := app.AddUnit(description.UnitArgs{ 245 Tag: names.NewUnitTag("foo/0"), 246 }) 247 unit.SetTools(description.AgentToolsArgs{ 248 Version: version.MustParseBinary(tools0), 249 }) 250 unitRes := unit.AddResource(description.UnitResourceArgs{ 251 Name: "bin", 252 RevisionArgs: description.ResourceRevisionArgs{ 253 Revision: 1, 254 Type: "file", 255 Path: "bin.tar.gz", 256 Description: "nose knows", 257 Origin: "upload", 258 FingerprintHex: "beef", 259 Size: 222, 260 Timestamp: time.Now(), 261 Username: "bambam", 262 }, 263 }) 264 unitRev := unitRes.Revision() 265 266 api := s.mustMakeAPI(c) 267 serialized, err := api.Export() 268 c.Assert(err, jc.ErrorIsNil) 269 270 // We don't want to tie this test the serialisation output (that's 271 // tested elsewhere). Just check that at least one thing we expect 272 // is in the serialised output. 273 c.Check(string(serialized.Bytes), jc.Contains, jujuversion.Current.String()) 274 275 c.Check(serialized.Charms, gc.DeepEquals, []string{"cs:foo-0"}) 276 if modelType == "caas" { 277 c.Check(serialized.Tools, gc.HasLen, 0) 278 } else { 279 c.Check(serialized.Tools, jc.SameContents, []params.SerializedModelTools{ 280 {tools0, "/tools/" + tools0}, 281 {tools1, "/tools/" + tools1}, 282 }) 283 } 284 c.Check(serialized.Resources, gc.DeepEquals, []params.SerializedModelResource{{ 285 Application: "foo", 286 Name: "bin", 287 ApplicationRevision: params.SerializedModelResourceRevision{ 288 Revision: appRev.Revision(), 289 Type: appRev.Type(), 290 Path: appRev.Path(), 291 Description: appRev.Description(), 292 Origin: appRev.Origin(), 293 FingerprintHex: appRev.FingerprintHex(), 294 Size: appRev.Size(), 295 Timestamp: appRev.Timestamp(), 296 Username: appRev.Username(), 297 }, 298 CharmStoreRevision: params.SerializedModelResourceRevision{ 299 Revision: csRev.Revision(), 300 Type: csRev.Type(), 301 Path: csRev.Path(), 302 Description: csRev.Description(), 303 Origin: csRev.Origin(), 304 FingerprintHex: csRev.FingerprintHex(), 305 Size: csRev.Size(), 306 Timestamp: csRev.Timestamp(), 307 Username: csRev.Username(), 308 }, 309 UnitRevisions: map[string]params.SerializedModelResourceRevision{ 310 "foo/0": { 311 Revision: unitRev.Revision(), 312 Type: unitRev.Type(), 313 Path: unitRev.Path(), 314 Description: unitRev.Description(), 315 Origin: unitRev.Origin(), 316 FingerprintHex: unitRev.FingerprintHex(), 317 Size: unitRev.Size(), 318 Timestamp: unitRev.Timestamp(), 319 Username: unitRev.Username(), 320 }, 321 }, 322 }}) 323 324 } 325 326 func (s *Suite) TestReap(c *gc.C) { 327 api := s.mustMakeAPI(c) 328 s.backend.migration = &stubMigration{} 329 330 err := api.Reap() 331 c.Check(err, jc.ErrorIsNil) 332 // Reaping should set the migration phase to DONE - otherwise 333 // there's a race between the migrationmaster worker updating the 334 // phase and being stopped because the model's gone. This leaves 335 // the migration as active in the source controller, which will 336 // prevent the model from being migrated back. 337 s.backend.stub.CheckCalls(c, []testing.StubCall{ 338 {"LatestMigration", []interface{}{}}, 339 {"RemoveExportingModelDocs", []interface{}{}}, 340 }) 341 c.Assert(s.backend.migration.phaseSet, gc.Equals, coremigration.DONE) 342 } 343 344 func (s *Suite) TestReapError(c *gc.C) { 345 s.backend.removeErr = errors.New("boom") 346 api := s.mustMakeAPI(c) 347 348 err := api.Reap() 349 c.Check(err, gc.ErrorMatches, "boom") 350 } 351 352 func (s *Suite) TestWatchMinionReports(c *gc.C) { 353 api := s.mustMakeAPI(c) 354 355 result := api.WatchMinionReports() 356 c.Assert(result.Error, gc.IsNil) 357 358 s.stub.CheckCallNames(c, 359 "LatestMigration", 360 "ModelMigration.WatchMinionReports", 361 ) 362 363 resource := s.resources.Get(result.NotifyWatcherId) 364 watcher, _ := resource.(state.NotifyWatcher) 365 c.Assert(watcher, gc.NotNil) 366 367 select { 368 case <-watcher.Changes(): 369 c.Fatalf("initial event not consumed") 370 case <-time.After(coretesting.ShortWait): 371 } 372 } 373 374 func (s *Suite) TestMinionReports(c *gc.C) { 375 // Report 16 unknowns. These are in reverse order in order to test 376 // sorting. 377 unknown := make([]names.Tag, 0, 16) 378 for i := cap(unknown) - 1; i >= 0; i-- { 379 unknown = append(unknown, names.NewMachineTag(fmt.Sprintf("%d", i))) 380 } 381 m50c0 := names.NewMachineTag("50/lxd/0") 382 m50c1 := names.NewMachineTag("50/lxd/1") 383 m50 := names.NewMachineTag("50") 384 m51 := names.NewMachineTag("51") 385 m52 := names.NewMachineTag("52") 386 u0 := names.NewUnitTag("foo/0") 387 u1 := names.NewUnitTag("foo/1") 388 s.backend.migration.minionReports = &state.MinionReports{ 389 Succeeded: []names.Tag{m50, m51, u0}, 390 Failed: []names.Tag{u1, m52, m50c1, m50c0}, 391 Unknown: unknown, 392 } 393 394 api := s.mustMakeAPI(c) 395 reports, err := api.MinionReports() 396 c.Assert(err, jc.ErrorIsNil) 397 398 // Expect the sample of unknowns to be in order and be limited to 399 // the first 10. 400 expectedSample := make([]string, 0, 10) 401 for i := 0; i < cap(expectedSample); i++ { 402 expectedSample = append(expectedSample, names.NewMachineTag(fmt.Sprintf("%d", i)).String()) 403 } 404 c.Assert(reports, gc.DeepEquals, params.MinionReports{ 405 MigrationId: "id", 406 Phase: "IMPORT", 407 SuccessCount: 3, 408 UnknownCount: len(unknown), 409 UnknownSample: expectedSample, 410 Failed: []string{ 411 // Note sorting 412 m50c0.String(), 413 m50c1.String(), 414 m52.String(), 415 u1.String(), 416 }, 417 }) 418 } 419 420 func (s *Suite) makeAPI() (*migrationmaster.API, error) { 421 return migrationmaster.NewAPI( 422 s.backend, 423 new(failingPrecheckBackend), 424 nil, // pool 425 s.resources, 426 s.authorizer, 427 &fakePresence{}, 428 ) 429 } 430 431 func (s *Suite) mustMakeAPI(c *gc.C) *migrationmaster.API { 432 api, err := s.makeAPI() 433 c.Assert(err, jc.ErrorIsNil) 434 return api 435 } 436 437 type stubBackend struct { 438 migrationmaster.Backend 439 440 stub *testing.Stub 441 getErr error 442 removeErr error 443 migration *stubMigration 444 model description.Model 445 } 446 447 func (b *stubBackend) WatchForMigration() state.NotifyWatcher { 448 b.stub.AddCall("WatchForMigration") 449 return apiservertesting.NewFakeNotifyWatcher() 450 } 451 452 func (b *stubBackend) LatestMigration() (state.ModelMigration, error) { 453 b.stub.AddCall("LatestMigration") 454 if b.getErr != nil { 455 return nil, b.getErr 456 } 457 return b.migration, nil 458 } 459 460 func (b *stubBackend) ModelUUID() string { 461 return "model-uuid" 462 } 463 464 func (b *stubBackend) ModelName() (string, error) { 465 return "model-name", nil 466 } 467 468 func (b *stubBackend) ModelOwner() (names.UserTag, error) { 469 return names.NewUserTag("owner"), nil 470 } 471 472 func (b *stubBackend) AgentVersion() (version.Number, error) { 473 return version.MustParse("1.2.3"), nil 474 } 475 476 func (b *stubBackend) RemoveExportingModelDocs() error { 477 b.stub.AddCall("RemoveExportingModelDocs") 478 return b.removeErr 479 } 480 481 func (b *stubBackend) Export() (description.Model, error) { 482 b.stub.AddCall("Export") 483 return b.model, nil 484 } 485 486 type stubMigration struct { 487 state.ModelMigration 488 489 stub *testing.Stub 490 setPhaseErr error 491 phaseSet coremigration.Phase 492 setMessageErr error 493 messageSet string 494 minionReports *state.MinionReports 495 externalControl bool 496 } 497 498 func (m *stubMigration) Id() string { 499 return "id" 500 } 501 502 func (m *stubMigration) Phase() (coremigration.Phase, error) { 503 return coremigration.IMPORT, nil 504 } 505 506 func (m *stubMigration) PhaseChangedTime() time.Time { 507 return time.Date(2016, 6, 22, 16, 38, 0, 0, time.UTC) 508 } 509 510 func (m *stubMigration) ModelUUID() string { 511 return modelUUID 512 } 513 514 func (m *stubMigration) TargetInfo() (*coremigration.TargetInfo, error) { 515 mac, err := macaroon.New([]byte("secret"), []byte("id"), "location") 516 if err != nil { 517 panic(err) 518 } 519 return &coremigration.TargetInfo{ 520 ControllerTag: names.NewControllerTag(controllerUUID), 521 Addrs: []string{"1.1.1.1:1", "2.2.2.2:2"}, 522 CACert: "trust me", 523 AuthTag: names.NewUserTag("admin"), 524 Password: "secret", 525 Macaroons: []macaroon.Slice{{mac}}, 526 }, nil 527 } 528 529 func (m *stubMigration) SetPhase(phase coremigration.Phase) error { 530 if m.setPhaseErr != nil { 531 return m.setPhaseErr 532 } 533 m.phaseSet = phase 534 return nil 535 } 536 537 func (m *stubMigration) SetStatusMessage(message string) error { 538 if m.setMessageErr != nil { 539 return m.setMessageErr 540 } 541 m.messageSet = message 542 return nil 543 } 544 545 func (m *stubMigration) WatchMinionReports() (state.NotifyWatcher, error) { 546 m.stub.AddCall("ModelMigration.WatchMinionReports") 547 return apiservertesting.NewFakeNotifyWatcher(), nil 548 } 549 550 func (m *stubMigration) MinionReports() (*state.MinionReports, error) { 551 return m.minionReports, nil 552 } 553 554 var modelUUID string 555 var controllerUUID string 556 557 func init() { 558 modelUUID = utils.MustNewUUID().String() 559 controllerUUID = utils.MustNewUUID().String() 560 } 561 562 type failingPrecheckBackend struct { 563 migration.PrecheckBackend 564 } 565 566 func (b *failingPrecheckBackend) Model() (migration.PrecheckModel, error) { 567 return nil, errors.New("boom") 568 } 569 570 type fakePresence struct { 571 } 572 573 func (f *fakePresence) ModelPresence(modelUUID string) facade.ModelPresence { 574 return f 575 } 576 577 func (f *fakePresence) AgentStatus(agent string) (presence.Status, error) { 578 return presence.Alive, nil 579 }