github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/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/errors" 11 "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/utils" 14 "github.com/juju/version" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/names.v2" 17 "gopkg.in/macaroon.v1" 18 19 "github.com/juju/juju/apiserver/common" 20 "github.com/juju/juju/apiserver/migrationmaster" 21 "github.com/juju/juju/apiserver/params" 22 apiservertesting "github.com/juju/juju/apiserver/testing" 23 "github.com/juju/juju/core/description" 24 coremigration "github.com/juju/juju/core/migration" 25 "github.com/juju/juju/migration" 26 "github.com/juju/juju/state" 27 coretesting "github.com/juju/juju/testing" 28 jujuversion "github.com/juju/juju/version" 29 ) 30 31 type Suite struct { 32 coretesting.BaseSuite 33 34 model description.Model 35 stub *testing.Stub 36 backend *stubBackend 37 resources *common.Resources 38 authorizer apiservertesting.FakeAuthorizer 39 } 40 41 var _ = gc.Suite(&Suite{}) 42 43 func (s *Suite) SetUpTest(c *gc.C) { 44 s.BaseSuite.SetUpTest(c) 45 46 s.model = description.NewModel(description.ModelArgs{ 47 Config: map[string]interface{}{"uuid": modelUUID}, 48 Owner: names.NewUserTag("admin"), 49 LatestToolsVersion: jujuversion.Current, 50 }) 51 s.stub = new(testing.Stub) 52 s.backend = &stubBackend{ 53 migration: &stubMigration{stub: s.stub}, 54 stub: s.stub, 55 model: s.model, 56 } 57 58 s.resources = common.NewResources() 59 s.AddCleanup(func(*gc.C) { s.resources.StopAll() }) 60 61 s.authorizer = apiservertesting.FakeAuthorizer{ 62 EnvironManager: true, 63 } 64 } 65 66 func (s *Suite) TestNotEnvironManager(c *gc.C) { 67 s.authorizer.EnvironManager = false 68 69 api, err := s.makeAPI() 70 c.Assert(api, gc.IsNil) 71 c.Assert(err, gc.Equals, common.ErrPerm) 72 } 73 74 func (s *Suite) TestWatch(c *gc.C) { 75 api := s.mustMakeAPI(c) 76 77 result := api.Watch() 78 c.Assert(result.Error, gc.IsNil) 79 80 resource := s.resources.Get(result.NotifyWatcherId) 81 watcher, _ := resource.(state.NotifyWatcher) 82 c.Assert(watcher, gc.NotNil) 83 84 select { 85 case <-watcher.Changes(): 86 c.Fatalf("initial event not consumed") 87 case <-time.After(coretesting.ShortWait): 88 } 89 } 90 91 func (s *Suite) TestMigrationStatus(c *gc.C) { 92 var expectedMacaroons = ` 93 [[{"caveats":[],"location":"location","identifier":"id","signature":"a9802bf274262733d6283a69c62805b5668dbf475bcd7edc25a962833f7c2cba"}]]`[1:] 94 95 api := s.mustMakeAPI(c) 96 status, err := api.MigrationStatus() 97 c.Assert(err, jc.ErrorIsNil) 98 99 c.Check(status, gc.DeepEquals, params.MasterMigrationStatus{ 100 Spec: params.MigrationSpec{ 101 ModelTag: names.NewModelTag(modelUUID).String(), 102 TargetInfo: params.MigrationTargetInfo{ 103 ControllerTag: names.NewControllerTag(controllerUUID).String(), 104 Addrs: []string{"1.1.1.1:1", "2.2.2.2:2"}, 105 CACert: "trust me", 106 AuthTag: names.NewUserTag("admin").String(), 107 Password: "secret", 108 Macaroons: expectedMacaroons, 109 }, 110 }, 111 MigrationId: "id", 112 Phase: "IMPORT", 113 PhaseChangedTime: s.backend.migration.PhaseChangedTime(), 114 }) 115 } 116 117 func (s *Suite) TestMigrationStatusExternalControl(c *gc.C) { 118 s.backend.migration.externalControl = true 119 status, err := s.mustMakeAPI(c).MigrationStatus() 120 c.Assert(err, jc.ErrorIsNil) 121 c.Check(status.Spec.ExternalControl, jc.IsTrue) 122 } 123 124 func (s *Suite) TestModelInfo(c *gc.C) { 125 api := s.mustMakeAPI(c) 126 model, err := api.ModelInfo() 127 c.Assert(err, jc.ErrorIsNil) 128 c.Assert(model.UUID, gc.Equals, "model-uuid") 129 c.Assert(model.Name, gc.Equals, "model-name") 130 c.Assert(model.OwnerTag, gc.Equals, names.NewUserTag("owner").String()) 131 c.Assert(model.AgentVersion, gc.Equals, version.MustParse("1.2.3")) 132 } 133 134 func (s *Suite) TestSetPhase(c *gc.C) { 135 api := s.mustMakeAPI(c) 136 137 err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"}) 138 c.Assert(err, jc.ErrorIsNil) 139 140 c.Assert(s.backend.migration.phaseSet, gc.Equals, coremigration.ABORT) 141 } 142 143 func (s *Suite) TestSetPhaseNoMigration(c *gc.C) { 144 s.backend.getErr = errors.New("boom") 145 api := s.mustMakeAPI(c) 146 147 err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"}) 148 c.Assert(err, gc.ErrorMatches, "could not get migration: boom") 149 } 150 151 func (s *Suite) TestSetPhaseBadPhase(c *gc.C) { 152 api := s.mustMakeAPI(c) 153 154 err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "wat"}) 155 c.Assert(err, gc.ErrorMatches, `invalid phase: "wat"`) 156 } 157 158 func (s *Suite) TestSetPhaseError(c *gc.C) { 159 s.backend.migration.setPhaseErr = errors.New("blam") 160 api := s.mustMakeAPI(c) 161 162 err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"}) 163 c.Assert(err, gc.ErrorMatches, "failed to set phase: blam") 164 } 165 166 func (s *Suite) TestSetStatusMessage(c *gc.C) { 167 api := s.mustMakeAPI(c) 168 169 err := api.SetStatusMessage(params.SetMigrationStatusMessageArgs{Message: "foo"}) 170 c.Assert(err, jc.ErrorIsNil) 171 c.Check(s.backend.migration.messageSet, gc.Equals, "foo") 172 } 173 174 func (s *Suite) TestSetStatusMessageNoMigration(c *gc.C) { 175 s.backend.getErr = errors.New("boom") 176 api := s.mustMakeAPI(c) 177 178 err := api.SetStatusMessage(params.SetMigrationStatusMessageArgs{Message: "foo"}) 179 c.Check(err, gc.ErrorMatches, "could not get migration: boom") 180 } 181 182 func (s *Suite) TestSetStatusMessageError(c *gc.C) { 183 s.backend.migration.setMessageErr = errors.New("blam") 184 api := s.mustMakeAPI(c) 185 186 err := api.SetStatusMessage(params.SetMigrationStatusMessageArgs{Message: "foo"}) 187 c.Assert(err, gc.ErrorMatches, "failed to set status message: blam") 188 } 189 190 func (s *Suite) TestPrechecks(c *gc.C) { 191 api := s.mustMakeAPI(c) 192 err := api.Prechecks() 193 c.Assert(err, gc.ErrorMatches, "retrieving model: boom") 194 } 195 196 func (s *Suite) TestExport(c *gc.C) { 197 s.model.AddApplication(description.ApplicationArgs{ 198 Tag: names.NewApplicationTag("foo"), 199 CharmURL: "cs:foo-0", 200 }) 201 const tools = "2.0.0-xenial-amd64" 202 m := s.model.AddMachine(description.MachineArgs{Id: names.NewMachineTag("9")}) 203 m.SetTools(description.AgentToolsArgs{ 204 Version: version.MustParseBinary(tools), 205 }) 206 api := s.mustMakeAPI(c) 207 208 serialized, err := api.Export() 209 210 c.Assert(err, jc.ErrorIsNil) 211 // We don't want to tie this test the serialisation output (that's 212 // tested elsewhere). Just check that at least one thing we expect 213 // is in the serialised output. 214 c.Assert(string(serialized.Bytes), jc.Contains, jujuversion.Current.String()) 215 c.Assert(serialized.Charms, gc.DeepEquals, []string{"cs:foo-0"}) 216 c.Assert(serialized.Tools, gc.DeepEquals, []params.SerializedModelTools{ 217 {tools, "/tools/" + tools}, 218 }) 219 } 220 221 func (s *Suite) TestReap(c *gc.C) { 222 api := s.mustMakeAPI(c) 223 224 err := api.Reap() 225 c.Check(err, jc.ErrorIsNil) 226 s.backend.stub.CheckCalls(c, []testing.StubCall{ 227 {"RemoveExportingModelDocs", []interface{}{}}, 228 }) 229 } 230 231 func (s *Suite) TestReapError(c *gc.C) { 232 s.backend.removeErr = errors.New("boom") 233 api := s.mustMakeAPI(c) 234 235 err := api.Reap() 236 c.Check(err, gc.ErrorMatches, "boom") 237 } 238 239 func (s *Suite) TestWatchMinionReports(c *gc.C) { 240 api := s.mustMakeAPI(c) 241 242 result := api.WatchMinionReports() 243 c.Assert(result.Error, gc.IsNil) 244 245 s.stub.CheckCallNames(c, 246 "LatestMigration", 247 "ModelMigration.WatchMinionReports", 248 ) 249 250 resource := s.resources.Get(result.NotifyWatcherId) 251 watcher, _ := resource.(state.NotifyWatcher) 252 c.Assert(watcher, gc.NotNil) 253 254 select { 255 case <-watcher.Changes(): 256 c.Fatalf("initial event not consumed") 257 case <-time.After(coretesting.ShortWait): 258 } 259 } 260 261 func (s *Suite) TestMinionReports(c *gc.C) { 262 // Report 16 unknowns. These are in reverse order in order to test 263 // sorting. 264 unknown := make([]names.Tag, 0, 16) 265 for i := cap(unknown) - 1; i >= 0; i-- { 266 unknown = append(unknown, names.NewMachineTag(fmt.Sprintf("%d", i))) 267 } 268 m50c0 := names.NewMachineTag("50/lxd/0") 269 m50c1 := names.NewMachineTag("50/lxd/1") 270 m50 := names.NewMachineTag("50") 271 m51 := names.NewMachineTag("51") 272 m52 := names.NewMachineTag("52") 273 u0 := names.NewUnitTag("foo/0") 274 u1 := names.NewUnitTag("foo/1") 275 s.backend.migration.minionReports = &state.MinionReports{ 276 Succeeded: []names.Tag{m50, m51, u0}, 277 Failed: []names.Tag{u1, m52, m50c1, m50c0}, 278 Unknown: unknown, 279 } 280 281 api := s.mustMakeAPI(c) 282 reports, err := api.MinionReports() 283 c.Assert(err, jc.ErrorIsNil) 284 285 // Expect the sample of unknowns to be in order and be limited to 286 // the first 10. 287 expectedSample := make([]string, 0, 10) 288 for i := 0; i < cap(expectedSample); i++ { 289 expectedSample = append(expectedSample, names.NewMachineTag(fmt.Sprintf("%d", i)).String()) 290 } 291 c.Assert(reports, gc.DeepEquals, params.MinionReports{ 292 MigrationId: "id", 293 Phase: "IMPORT", 294 SuccessCount: 3, 295 UnknownCount: len(unknown), 296 UnknownSample: expectedSample, 297 Failed: []string{ 298 // Note sorting 299 m50c0.String(), 300 m50c1.String(), 301 m52.String(), 302 u1.String(), 303 }, 304 }) 305 } 306 307 func (s *Suite) makeAPI() (*migrationmaster.API, error) { 308 return migrationmaster.NewAPI(s.backend, new(failingPrecheckBackend), 309 s.resources, s.authorizer) 310 } 311 312 func (s *Suite) mustMakeAPI(c *gc.C) *migrationmaster.API { 313 api, err := s.makeAPI() 314 c.Assert(err, jc.ErrorIsNil) 315 return api 316 } 317 318 type stubBackend struct { 319 migrationmaster.Backend 320 321 stub *testing.Stub 322 getErr error 323 removeErr error 324 migration *stubMigration 325 model description.Model 326 } 327 328 func (b *stubBackend) WatchForMigration() state.NotifyWatcher { 329 b.stub.AddCall("WatchForMigration") 330 return apiservertesting.NewFakeNotifyWatcher() 331 } 332 333 func (b *stubBackend) LatestMigration() (state.ModelMigration, error) { 334 b.stub.AddCall("LatestMigration") 335 if b.getErr != nil { 336 return nil, b.getErr 337 } 338 return b.migration, nil 339 } 340 341 func (b *stubBackend) ModelUUID() string { 342 return "model-uuid" 343 } 344 345 func (b *stubBackend) ModelName() (string, error) { 346 return "model-name", nil 347 } 348 349 func (b *stubBackend) ModelOwner() (names.UserTag, error) { 350 return names.NewUserTag("owner"), nil 351 } 352 353 func (b *stubBackend) AgentVersion() (version.Number, error) { 354 return version.MustParse("1.2.3"), nil 355 } 356 357 func (b *stubBackend) RemoveExportingModelDocs() error { 358 b.stub.AddCall("RemoveExportingModelDocs") 359 return b.removeErr 360 } 361 362 func (b *stubBackend) Export() (description.Model, error) { 363 b.stub.AddCall("Export") 364 return b.model, nil 365 } 366 367 type stubMigration struct { 368 state.ModelMigration 369 370 stub *testing.Stub 371 setPhaseErr error 372 phaseSet coremigration.Phase 373 setMessageErr error 374 messageSet string 375 minionReports *state.MinionReports 376 externalControl bool 377 } 378 379 func (m *stubMigration) Id() string { 380 return "id" 381 } 382 383 func (m *stubMigration) Phase() (coremigration.Phase, error) { 384 return coremigration.IMPORT, nil 385 } 386 387 func (m *stubMigration) PhaseChangedTime() time.Time { 388 return time.Date(2016, 6, 22, 16, 38, 0, 0, time.UTC) 389 } 390 391 func (m *stubMigration) Attempt() (int, error) { 392 return 1, nil 393 } 394 395 func (m *stubMigration) ModelUUID() string { 396 return modelUUID 397 } 398 399 func (m *stubMigration) ExternalControl() bool { 400 return m.externalControl 401 } 402 403 func (m *stubMigration) TargetInfo() (*coremigration.TargetInfo, error) { 404 mac, err := macaroon.New([]byte("secret"), "id", "location") 405 if err != nil { 406 panic(err) 407 } 408 return &coremigration.TargetInfo{ 409 ControllerTag: names.NewControllerTag(controllerUUID), 410 Addrs: []string{"1.1.1.1:1", "2.2.2.2:2"}, 411 CACert: "trust me", 412 AuthTag: names.NewUserTag("admin"), 413 Password: "secret", 414 Macaroons: []macaroon.Slice{{mac}}, 415 }, nil 416 } 417 418 func (m *stubMigration) SetPhase(phase coremigration.Phase) error { 419 if m.setPhaseErr != nil { 420 return m.setPhaseErr 421 } 422 m.phaseSet = phase 423 return nil 424 } 425 426 func (m *stubMigration) SetStatusMessage(message string) error { 427 if m.setMessageErr != nil { 428 return m.setMessageErr 429 } 430 m.messageSet = message 431 return nil 432 } 433 434 func (m *stubMigration) WatchMinionReports() (state.NotifyWatcher, error) { 435 m.stub.AddCall("ModelMigration.WatchMinionReports") 436 return apiservertesting.NewFakeNotifyWatcher(), nil 437 } 438 439 func (m *stubMigration) MinionReports() (*state.MinionReports, error) { 440 return m.minionReports, nil 441 } 442 443 var modelUUID string 444 var controllerUUID string 445 446 func init() { 447 modelUUID = utils.MustNewUUID().String() 448 controllerUUID = utils.MustNewUUID().String() 449 } 450 451 type failingPrecheckBackend struct { 452 migration.PrecheckBackend 453 } 454 455 func (b *failingPrecheckBackend) Model() (migration.PrecheckModel, error) { 456 return nil, errors.New("boom") 457 }