github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/controller/migrationtarget/migrationtarget_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package migrationtarget_test 5 6 import ( 7 "io/ioutil" 8 "time" 9 10 "github.com/juju/description" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils" 16 "github.com/juju/version" 17 gc "gopkg.in/check.v1" 18 "gopkg.in/juju/names.v2" 19 20 "github.com/juju/juju/apiserver" 21 "github.com/juju/juju/apiserver/common" 22 "github.com/juju/juju/apiserver/facade/facadetest" 23 "github.com/juju/juju/apiserver/facades/controller/migrationtarget" 24 "github.com/juju/juju/apiserver/params" 25 apiservertesting "github.com/juju/juju/apiserver/testing" 26 "github.com/juju/juju/caas" 27 "github.com/juju/juju/core/instance" 28 "github.com/juju/juju/core/leadership" 29 "github.com/juju/juju/core/lease" 30 "github.com/juju/juju/environs" 31 "github.com/juju/juju/environs/context" 32 "github.com/juju/juju/environs/instances" 33 "github.com/juju/juju/provider/dummy" 34 "github.com/juju/juju/state" 35 "github.com/juju/juju/state/stateenvirons" 36 statetesting "github.com/juju/juju/state/testing" 37 jujutesting "github.com/juju/juju/testing" 38 "github.com/juju/juju/testing/factory" 39 ) 40 41 type Suite struct { 42 statetesting.StateSuite 43 resources *common.Resources 44 authorizer *apiservertesting.FakeAuthorizer 45 46 facadeContext facadetest.Context 47 callContext context.ProviderCallContext 48 } 49 50 var _ = gc.Suite(&Suite{}) 51 52 func (s *Suite) SetUpTest(c *gc.C) { 53 // Set up InitialConfig with a dummy provider configuration. This 54 // is required to allow model import test to work. 55 s.InitialConfig = jujutesting.CustomModelConfig(c, dummy.SampleConfig()) 56 57 // The call up to StateSuite's SetUpTest uses s.InitialConfig so 58 // it has to happen here. 59 s.StateSuite.SetUpTest(c) 60 61 s.resources = common.NewResources() 62 s.AddCleanup(func(*gc.C) { s.resources.StopAll() }) 63 64 s.authorizer = &apiservertesting.FakeAuthorizer{ 65 Tag: s.Owner, 66 AdminTag: s.Owner, 67 } 68 s.callContext = context.NewCloudCallContext() 69 s.facadeContext = facadetest.Context{ 70 State_: s.State, 71 StatePool_: s.StatePool, 72 Resources_: s.resources, 73 Auth_: s.authorizer, 74 } 75 } 76 77 func (s *Suite) TestFacadeRegistered(c *gc.C) { 78 factory, err := apiserver.AllFacades().GetFactory("MigrationTarget", 1) 79 c.Assert(err, jc.ErrorIsNil) 80 81 api, err := factory(&facadetest.Context{ 82 State_: s.State, 83 Resources_: s.resources, 84 Auth_: s.authorizer, 85 }) 86 c.Assert(err, jc.ErrorIsNil) 87 c.Assert(api, gc.FitsTypeOf, new(migrationtarget.API)) 88 } 89 90 func (s *Suite) TestNotUser(c *gc.C) { 91 s.authorizer.Tag = names.NewMachineTag("0") 92 _, err := s.newAPI(nil, nil) 93 c.Assert(errors.Cause(err), gc.Equals, common.ErrPerm) 94 } 95 96 func (s *Suite) TestNotControllerAdmin(c *gc.C) { 97 s.authorizer.Tag = names.NewUserTag("jrandomuser") 98 _, err := s.newAPI(nil, nil) 99 c.Assert(errors.Cause(err), gc.Equals, common.ErrPerm) 100 } 101 102 func (s *Suite) importModel(c *gc.C, api *migrationtarget.API) names.ModelTag { 103 uuid, bytes := s.makeExportedModel(c) 104 err := api.Import(params.SerializedModel{Bytes: bytes}) 105 c.Assert(err, jc.ErrorIsNil) 106 return names.NewModelTag(uuid) 107 } 108 109 func (s *Suite) TestPrechecks(c *gc.C) { 110 api := s.mustNewAPI(c) 111 args := params.MigrationModelInfo{ 112 UUID: "uuid", 113 Name: "some-model", 114 OwnerTag: names.NewUserTag("someone").String(), 115 AgentVersion: s.controllerVersion(c), 116 ControllerAgentVersion: s.controllerVersion(c), 117 } 118 err := api.Prechecks(args) 119 c.Assert(err, jc.ErrorIsNil) 120 } 121 122 func (s *Suite) TestCACert(c *gc.C) { 123 api := s.mustNewAPI(c) 124 r, err := api.CACert() 125 c.Assert(err, jc.ErrorIsNil) 126 c.Assert(string(r.Result), gc.Equals, jujutesting.CACert) 127 } 128 129 func (s *Suite) TestPrechecksFail(c *gc.C) { 130 controllerVersion := s.controllerVersion(c) 131 132 // Set the model version ahead of the controller. 133 modelVersion := controllerVersion 134 modelVersion.Minor++ 135 136 api := s.mustNewAPI(c) 137 args := params.MigrationModelInfo{ 138 AgentVersion: modelVersion, 139 } 140 err := api.Prechecks(args) 141 c.Assert(err, gc.NotNil) 142 } 143 144 func (s *Suite) TestImport(c *gc.C) { 145 api := s.mustNewAPI(c) 146 tag := s.importModel(c, api) 147 // Check the model was imported. 148 model, ph, err := s.StatePool.GetModel(tag.Id()) 149 c.Assert(err, jc.ErrorIsNil) 150 defer ph.Release() 151 c.Assert(model.Name(), gc.Equals, "some-model") 152 c.Assert(model.MigrationMode(), gc.Equals, state.MigrationModeImporting) 153 } 154 155 func (s *Suite) TestImportLeadership(c *gc.C) { 156 application := s.Factory.MakeApplication(c, &factory.ApplicationParams{ 157 Charm: s.Factory.MakeCharm(c, &factory.CharmParams{ 158 Name: "wordpress", 159 }), 160 }) 161 for i := 0; i < 3; i++ { 162 s.Factory.MakeUnit(c, &factory.UnitParams{Application: application}) 163 } 164 target := s.State.LeaseNotifyTarget( 165 ioutil.Discard, 166 loggo.GetLogger("migrationtarget_test"), 167 ) 168 target.Claimed( 169 lease.Key{"application-leadership", s.State.ModelUUID(), "wordpress"}, 170 "wordpress/2", 171 ) 172 173 var claimer fakeClaimer 174 s.facadeContext.LeadershipClaimer_ = &claimer 175 api := s.mustNewAPI(c) 176 s.importModel(c, api) 177 178 c.Assert(claimer.stub.Calls(), gc.HasLen, 1) 179 claimer.stub.CheckCall(c, 0, "ClaimLeadership", "wordpress", "wordpress/2", time.Minute) 180 } 181 182 func (s *Suite) TestAbort(c *gc.C) { 183 api := s.mustNewAPI(c) 184 tag := s.importModel(c, api) 185 186 err := api.Abort(params.ModelArgs{ModelTag: tag.String()}) 187 c.Assert(err, jc.ErrorIsNil) 188 189 // The model should no longer exist. 190 exists, err := s.State.ModelExists(tag.Id()) 191 c.Assert(err, jc.ErrorIsNil) 192 c.Check(exists, jc.IsFalse) 193 } 194 195 func (s *Suite) TestAbortNotATag(c *gc.C) { 196 api := s.mustNewAPI(c) 197 err := api.Abort(params.ModelArgs{ModelTag: "not-a-tag"}) 198 c.Assert(err, gc.ErrorMatches, `"not-a-tag" is not a valid tag`) 199 } 200 201 func (s *Suite) TestAbortMissingModel(c *gc.C) { 202 api := s.mustNewAPI(c) 203 newUUID := utils.MustNewUUID().String() 204 err := api.Abort(params.ModelArgs{ModelTag: names.NewModelTag(newUUID).String()}) 205 c.Assert(err, gc.ErrorMatches, `model "`+newUUID+`" not found`) 206 } 207 208 func (s *Suite) TestAbortNotImportingModel(c *gc.C) { 209 st := s.Factory.MakeModel(c, nil) 210 defer st.Close() 211 model, err := st.Model() 212 c.Assert(err, jc.ErrorIsNil) 213 214 api := s.mustNewAPI(c) 215 err = api.Abort(params.ModelArgs{ModelTag: model.ModelTag().String()}) 216 c.Assert(err, gc.ErrorMatches, `migration mode for the model is not importing`) 217 } 218 219 func (s *Suite) TestActivate(c *gc.C) { 220 api := s.mustNewAPI(c) 221 tag := s.importModel(c, api) 222 223 err := api.Activate(params.ModelArgs{ModelTag: tag.String()}) 224 c.Assert(err, jc.ErrorIsNil) 225 226 model, ph, err := s.StatePool.GetModel(tag.Id()) 227 c.Assert(err, jc.ErrorIsNil) 228 defer ph.Release() 229 c.Assert(model.MigrationMode(), gc.Equals, state.MigrationModeNone) 230 } 231 232 func (s *Suite) TestActivateNotATag(c *gc.C) { 233 api := s.mustNewAPI(c) 234 err := api.Activate(params.ModelArgs{ModelTag: "not-a-tag"}) 235 c.Assert(err, gc.ErrorMatches, `"not-a-tag" is not a valid tag`) 236 } 237 238 func (s *Suite) TestActivateMissingModel(c *gc.C) { 239 api := s.mustNewAPI(c) 240 newUUID := utils.MustNewUUID().String() 241 err := api.Activate(params.ModelArgs{ModelTag: names.NewModelTag(newUUID).String()}) 242 c.Assert(err, gc.ErrorMatches, `model "`+newUUID+`" not found`) 243 } 244 245 func (s *Suite) TestActivateNotImportingModel(c *gc.C) { 246 st := s.Factory.MakeModel(c, nil) 247 defer st.Close() 248 model, err := st.Model() 249 c.Assert(err, jc.ErrorIsNil) 250 251 api := s.mustNewAPI(c) 252 err = api.Activate(params.ModelArgs{ModelTag: model.ModelTag().String()}) 253 c.Assert(err, gc.ErrorMatches, `migration mode for the model is not importing`) 254 } 255 256 func (s *Suite) TestLatestLogTime(c *gc.C) { 257 st := s.Factory.MakeModel(c, nil) 258 defer st.Close() 259 model, err := st.Model() 260 c.Assert(err, jc.ErrorIsNil) 261 262 t := time.Date(2016, 11, 30, 18, 14, 0, 100, time.UTC) 263 tracker := state.NewLastSentLogTracker(st, model.UUID(), "migration-logtransfer") 264 defer tracker.Close() 265 err = tracker.Set(0, t.UnixNano()) 266 c.Assert(err, jc.ErrorIsNil) 267 268 api := s.mustNewAPI(c) 269 latest, err := api.LatestLogTime(params.ModelArgs{ModelTag: model.ModelTag().String()}) 270 c.Assert(err, jc.ErrorIsNil) 271 c.Assert(latest, gc.Equals, t) 272 } 273 274 func (s *Suite) TestLatestLogTimeNeverSet(c *gc.C) { 275 st := s.Factory.MakeModel(c, nil) 276 defer st.Close() 277 model, err := st.Model() 278 c.Assert(err, jc.ErrorIsNil) 279 280 api := s.mustNewAPI(c) 281 latest, err := api.LatestLogTime(params.ModelArgs{ModelTag: model.ModelTag().String()}) 282 c.Assert(err, jc.ErrorIsNil) 283 c.Assert(latest, gc.Equals, time.Time{}) 284 } 285 286 func (s *Suite) TestAdoptIAASResources(c *gc.C) { 287 st := s.Factory.MakeModel(c, nil) 288 defer st.Close() 289 290 env := mockEnv{Stub: &testing.Stub{}} 291 api, err := s.newAPI(func(modelSt *state.State) (environs.Environ, error) { 292 c.Assert(modelSt.ModelUUID(), gc.Equals, st.ModelUUID()) 293 return &env, nil 294 }, func(modelSt *state.State) (caas.Broker, error) { 295 return nil, errors.New("should not be called") 296 }) 297 c.Assert(err, jc.ErrorIsNil) 298 299 m, err := st.Model() 300 c.Assert(err, jc.ErrorIsNil) 301 302 err = api.AdoptResources(params.AdoptResourcesArgs{ 303 ModelTag: m.ModelTag().String(), 304 SourceControllerVersion: version.MustParse("3.2.1"), 305 }) 306 c.Assert(err, jc.ErrorIsNil) 307 308 c.Assert(env.Stub.Calls(), gc.HasLen, 1) 309 env.Stub.CheckCall(c, 0, "AdoptResources", s.callContext, st.ControllerUUID(), version.MustParse("3.2.1")) 310 } 311 312 func (s *Suite) TestAdoptCAASResources(c *gc.C) { 313 st := s.Factory.MakeCAASModel(c, nil) 314 defer st.Close() 315 316 broker := mockBroker{Stub: &testing.Stub{}} 317 api, err := s.newAPI(func(modelSt *state.State) (environs.Environ, error) { 318 return nil, errors.New("should not be called") 319 }, func(modelSt *state.State) (caas.Broker, error) { 320 c.Assert(modelSt.ModelUUID(), gc.Equals, st.ModelUUID()) 321 return &broker, nil 322 }) 323 c.Assert(err, jc.ErrorIsNil) 324 325 m, err := st.Model() 326 c.Assert(err, jc.ErrorIsNil) 327 328 err = api.AdoptResources(params.AdoptResourcesArgs{ 329 ModelTag: m.ModelTag().String(), 330 SourceControllerVersion: version.MustParse("3.2.1"), 331 }) 332 c.Assert(err, jc.ErrorIsNil) 333 334 c.Assert(broker.Stub.Calls(), gc.HasLen, 1) 335 broker.Stub.CheckCall(c, 0, "AdoptResources", s.callContext, st.ControllerUUID(), version.MustParse("3.2.1")) 336 } 337 338 func (s *Suite) TestCheckMachinesSuccess(c *gc.C) { 339 st := s.Factory.MakeModel(c, nil) 340 defer st.Close() 341 342 fact := factory.NewFactory(st, s.StatePool) 343 fact.MakeMachine(c, &factory.MachineParams{ 344 InstanceId: "eriatarka", 345 }) 346 m := fact.MakeMachine(c, &factory.MachineParams{ 347 InstanceId: "volta", 348 }) 349 c.Assert(m.Id(), gc.Equals, "1") 350 351 mockEnv := mockEnv{ 352 Stub: &testing.Stub{}, 353 instances: []*mockInstance{ 354 {id: "volta"}, 355 {id: "eriatarka"}, 356 }, 357 } 358 api := s.mustNewAPIWithModel(c, &mockEnv, &mockBroker{}) 359 model, err := st.Model() 360 c.Assert(err, jc.ErrorIsNil) 361 results, err := api.CheckMachines( 362 params.ModelArgs{ModelTag: model.ModelTag().String()}) 363 c.Assert(err, jc.ErrorIsNil) 364 c.Assert(results, gc.DeepEquals, params.ErrorResults{}) 365 } 366 367 func (s *Suite) TestCheckMachinesHandlesContainers(c *gc.C) { 368 st := s.Factory.MakeModel(c, nil) 369 defer st.Close() 370 371 fact := factory.NewFactory(st, s.StatePool) 372 m := fact.MakeMachine(c, &factory.MachineParams{ 373 InstanceId: "birds", 374 }) 375 fact.MakeMachineNested(c, m.Id(), nil) 376 377 mockEnv := mockEnv{ 378 Stub: &testing.Stub{}, 379 instances: []*mockInstance{{id: "birds"}}, 380 } 381 api := s.mustNewAPIWithModel(c, &mockEnv, &mockBroker{}) 382 model, err := st.Model() 383 c.Assert(err, jc.ErrorIsNil) 384 results, err := api.CheckMachines( 385 params.ModelArgs{ModelTag: model.ModelTag().String()}) 386 c.Assert(err, jc.ErrorIsNil) 387 c.Assert(results, gc.DeepEquals, params.ErrorResults{}) 388 } 389 390 func (s *Suite) TestCheckMachinesHandlesManual(c *gc.C) { 391 st := s.Factory.MakeModel(c, nil) 392 defer st.Close() 393 394 fact := factory.NewFactory(st, s.StatePool) 395 fact.MakeMachine(c, &factory.MachineParams{ 396 InstanceId: "birds", 397 }) 398 fact.MakeMachine(c, &factory.MachineParams{ 399 Nonce: "manual:flibbertigibbert", 400 }) 401 402 mockEnv := mockEnv{ 403 Stub: &testing.Stub{}, 404 instances: []*mockInstance{{id: "birds"}}, 405 } 406 api := s.mustNewAPIWithModel(c, &mockEnv, &mockBroker{}) 407 408 model, err := st.Model() 409 c.Assert(err, jc.ErrorIsNil) 410 results, err := api.CheckMachines( 411 params.ModelArgs{ModelTag: model.ModelTag().String()}) 412 c.Assert(err, jc.ErrorIsNil) 413 c.Assert(results, gc.DeepEquals, params.ErrorResults{}) 414 } 415 416 func (s *Suite) newAPI(environFunc stateenvirons.NewEnvironFunc, brokerFunc stateenvirons.NewCAASBrokerFunc) (*migrationtarget.API, error) { 417 api, err := migrationtarget.NewAPI(&s.facadeContext, environFunc, brokerFunc, s.callContext) 418 return api, err 419 } 420 421 func (s *Suite) mustNewAPI(c *gc.C) *migrationtarget.API { 422 api, err := s.newAPI(nil, nil) 423 c.Assert(err, jc.ErrorIsNil) 424 return api 425 } 426 427 func (s *Suite) mustNewAPIWithModel(c *gc.C, env environs.Environ, broker caas.Broker) *migrationtarget.API { 428 api, err := s.newAPI(func(*state.State) (environs.Environ, error) { 429 return env, nil 430 }, func(*state.State) (caas.Broker, error) { 431 return broker, nil 432 }) 433 c.Assert(err, jc.ErrorIsNil) 434 return api 435 } 436 437 func (s *Suite) makeExportedModel(c *gc.C) (string, []byte) { 438 model, err := s.State.Export() 439 c.Assert(err, jc.ErrorIsNil) 440 441 newUUID := utils.MustNewUUID().String() 442 model.UpdateConfig(map[string]interface{}{ 443 "name": "some-model", 444 "uuid": newUUID, 445 }) 446 447 bytes, err := description.Serialize(model) 448 c.Assert(err, jc.ErrorIsNil) 449 return newUUID, bytes 450 } 451 452 func (s *Suite) controllerVersion(c *gc.C) version.Number { 453 cfg, err := s.Model.ModelConfig() 454 c.Assert(err, jc.ErrorIsNil) 455 vers, ok := cfg.AgentVersion() 456 c.Assert(ok, jc.IsTrue) 457 return vers 458 } 459 460 type mockEnv struct { 461 environs.Environ 462 *testing.Stub 463 464 instances []*mockInstance 465 } 466 467 func (e *mockEnv) AdoptResources(ctx context.ProviderCallContext, controllerUUID string, sourceVersion version.Number) error { 468 e.MethodCall(e, "AdoptResources", ctx, controllerUUID, sourceVersion) 469 return e.NextErr() 470 } 471 472 func (e *mockEnv) AllInstances(ctx context.ProviderCallContext) ([]instances.Instance, error) { 473 e.MethodCall(e, "AllInstances", ctx) 474 results := make([]instances.Instance, len(e.instances)) 475 for i, instance := range e.instances { 476 results[i] = instance 477 } 478 return results, e.NextErr() 479 } 480 481 type mockBroker struct { 482 caas.Broker 483 *testing.Stub 484 } 485 486 func (e *mockBroker) AdoptResources(ctx context.ProviderCallContext, controllerUUID string, sourceVersion version.Number) error { 487 e.MethodCall(e, "AdoptResources", ctx, controllerUUID, sourceVersion) 488 return e.NextErr() 489 } 490 491 type mockInstance struct { 492 instances.Instance 493 id string 494 } 495 496 func (i *mockInstance) Id() instance.Id { 497 return instance.Id(i.id) 498 } 499 500 type fakeClaimer struct { 501 leadership.Claimer 502 stub testing.Stub 503 } 504 505 func (c *fakeClaimer) ClaimLeadership(application, unit string, duration time.Duration) error { 506 c.stub.AddCall("ClaimLeadership", application, unit, duration) 507 return c.stub.NextErr() 508 }