github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/client/modelmanager/modelinfo_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package modelmanager_test 5 6 import ( 7 "strings" 8 "time" 9 10 "github.com/juju/collections/set" 11 "github.com/juju/description/v5" 12 "github.com/juju/errors" 13 "github.com/juju/names/v5" 14 jujutesting "github.com/juju/testing" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/version/v2" 17 gc "gopkg.in/check.v1" 18 "gopkg.in/juju/environschema.v1" 19 20 "github.com/juju/juju/apiserver/common" 21 commonsecrets "github.com/juju/juju/apiserver/common/secrets" 22 "github.com/juju/juju/apiserver/facades/client/modelmanager" 23 apiservertesting "github.com/juju/juju/apiserver/testing" 24 "github.com/juju/juju/caas" 25 "github.com/juju/juju/cloud" 26 "github.com/juju/juju/controller" 27 "github.com/juju/juju/core/assumes" 28 "github.com/juju/juju/core/base" 29 "github.com/juju/juju/core/instance" 30 "github.com/juju/juju/core/life" 31 "github.com/juju/juju/core/network" 32 "github.com/juju/juju/core/permission" 33 "github.com/juju/juju/core/secrets" 34 "github.com/juju/juju/core/status" 35 "github.com/juju/juju/environs" 36 environscloudspec "github.com/juju/juju/environs/cloudspec" 37 "github.com/juju/juju/environs/config" 38 "github.com/juju/juju/environs/context" 39 "github.com/juju/juju/rpc/params" 40 "github.com/juju/juju/secrets/provider" 41 "github.com/juju/juju/state" 42 coretesting "github.com/juju/juju/testing" 43 ) 44 45 type modelInfoSuite struct { 46 coretesting.BaseSuite 47 authorizer apiservertesting.FakeAuthorizer 48 st *mockState 49 ctlrSt *mockState 50 modelmanager *modelmanager.ModelManagerAPI 51 52 callContext context.ProviderCallContext 53 } 54 55 func pUint64(v uint64) *uint64 { 56 return &v 57 } 58 59 var _ = gc.Suite(&modelInfoSuite{}) 60 61 func (s *modelInfoSuite) SetUpTest(c *gc.C) { 62 s.BaseSuite.SetUpTest(c) 63 s.authorizer = apiservertesting.FakeAuthorizer{ 64 Tag: names.NewUserTag("admin@local"), 65 } 66 s.st = &mockState{ 67 controllerUUID: coretesting.ControllerTag.Id(), 68 cloud: cloud.Cloud{ 69 Type: "dummy", 70 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType}, 71 }, 72 cfgDefaults: config.ModelDefaultAttributes{ 73 "attr": config.AttributeDefaultValues{ 74 Default: "", 75 Controller: "val", 76 Regions: []config.RegionDefaultValue{{ 77 Name: "dummy", 78 Value: "val++"}}}, 79 "attr2": config.AttributeDefaultValues{ 80 Controller: "val3", 81 Default: "val2", 82 Regions: []config.RegionDefaultValue{{ 83 Name: "left", 84 Value: "spam"}}}, 85 }, 86 } 87 controllerModel := &mockModel{ 88 owner: names.NewUserTag("admin@local"), 89 life: state.Alive, 90 cfg: coretesting.ModelConfig(c), 91 // This feels kind of wrong as both controller model and 92 // default model will end up with the same model tag. 93 tag: coretesting.ModelTag, 94 controllerUUID: s.st.controllerUUID, 95 isController: true, 96 status: status.StatusInfo{ 97 Status: status.Available, 98 Since: &time.Time{}, 99 }, 100 users: []*mockModelUser{{ 101 userName: "admin", 102 access: permission.AdminAccess, 103 }, { 104 userName: "otheruser", 105 access: permission.AdminAccess, 106 }}, 107 } 108 s.st.controllerModel = controllerModel 109 110 s.ctlrSt = &mockState{ 111 model: controllerModel, 112 controllerModel: controllerModel, 113 } 114 115 s.st.model = &mockModel{ 116 owner: names.NewUserTag("bob@local"), 117 cfg: coretesting.ModelConfig(c), 118 tag: coretesting.ModelTag, 119 controllerUUID: s.st.controllerUUID, 120 isController: false, 121 life: state.Dying, 122 cloud: cloud.Cloud{ 123 Type: "dummy", 124 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType}, 125 }, 126 status: status.StatusInfo{ 127 Status: status.Destroying, 128 Since: &time.Time{}, 129 }, 130 users: []*mockModelUser{{ 131 userName: "admin", 132 access: permission.AdminAccess, 133 }, { 134 userName: "bob", 135 displayName: "Bob", 136 access: permission.ReadAccess, 137 }, { 138 userName: "charlotte", 139 displayName: "Charlotte", 140 access: permission.ReadAccess, 141 }, { 142 userName: "mary", 143 displayName: "Mary", 144 access: permission.WriteAccess, 145 }}, 146 } 147 s.st.machines = []common.Machine{ 148 &mockMachine{ 149 id: "1", 150 containerType: "none", 151 life: state.Alive, 152 hw: &instance.HardwareCharacteristics{CpuCores: pUint64(1)}, 153 }, 154 &mockMachine{ 155 id: "2", 156 life: state.Alive, 157 containerType: "lxc", 158 }, 159 &mockMachine{ 160 id: "3", 161 life: state.Dead, 162 }, 163 } 164 s.st.controllerNodes = []common.ControllerNode{ 165 &mockControllerNode{ 166 id: "1", 167 hasVote: true, 168 wantsVote: true, 169 }, 170 &mockControllerNode{ 171 id: "2", 172 hasVote: false, 173 wantsVote: true, 174 }, 175 } 176 177 s.callContext = context.NewEmptyCloudCallContext() 178 179 var err error 180 s.modelmanager, err = modelmanager.NewModelManagerAPI( 181 s.st, s.ctlrSt, nil, nil, common.NewBlockChecker(s.st), 182 &s.authorizer, s.st.model, s.callContext, 183 ) 184 c.Assert(err, jc.ErrorIsNil) 185 186 var fs assumes.FeatureSet 187 fs.Add(assumes.Feature{Name: "example"}) 188 modelmanager.MockSupportedFeatures(fs) 189 } 190 191 func (s *modelInfoSuite) TearDownTest(c *gc.C) { 192 modelmanager.ResetSupportedFeaturesGetter() 193 } 194 195 func (s *modelInfoSuite) setAPIUser(c *gc.C, user names.UserTag) { 196 s.authorizer.Tag = user 197 var err error 198 s.modelmanager, err = modelmanager.NewModelManagerAPI( 199 s.st, s.ctlrSt, nil, nil, 200 common.NewBlockChecker(s.st), s.authorizer, s.st.model, s.callContext, 201 ) 202 c.Assert(err, jc.ErrorIsNil) 203 } 204 205 func (s *modelInfoSuite) expectedModelInfo(c *gc.C, credentialValidity *bool) params.ModelInfo { 206 expectedAgentVersion, exists := s.st.model.cfg.AgentVersion() 207 c.Assert(exists, jc.IsTrue) 208 info := params.ModelInfo{ 209 Name: "testmodel", 210 UUID: s.st.model.cfg.UUID(), 211 Type: string(s.st.model.Type()), 212 ControllerUUID: "deadbeef-1bad-500d-9000-4b1d0d06f00d", 213 IsController: false, 214 OwnerTag: "user-bob", 215 ProviderType: "someprovider", 216 CloudTag: "cloud-some-cloud", 217 CloudRegion: "some-region", 218 CloudCredentialTag: "cloudcred-some-cloud_bob_some-credential", 219 Life: life.Dying, 220 Status: params.EntityStatus{ 221 Status: status.Destroying, 222 Since: &time.Time{}, 223 }, 224 Users: []params.ModelUserInfo{{ 225 UserName: "admin", 226 LastConnection: &time.Time{}, 227 Access: params.ModelAdminAccess, 228 }, { 229 UserName: "bob", 230 DisplayName: "Bob", 231 LastConnection: &time.Time{}, 232 Access: params.ModelReadAccess, 233 }, { 234 UserName: "charlotte", 235 DisplayName: "Charlotte", 236 LastConnection: &time.Time{}, 237 Access: params.ModelReadAccess, 238 }, { 239 UserName: "mary", 240 DisplayName: "Mary", 241 LastConnection: &time.Time{}, 242 Access: params.ModelWriteAccess, 243 }}, 244 Machines: []params.ModelMachineInfo{{ 245 Id: "1", 246 Hardware: ¶ms.MachineHardware{Cores: pUint64(1)}, 247 HasVote: true, 248 WantsVote: true, 249 }, { 250 Id: "2", 251 WantsVote: true, 252 }}, 253 SecretBackends: []params.SecretBackendResult{{ 254 Result: params.SecretBackend{ 255 Name: "myvault", 256 BackendType: "vault", 257 Config: map[string]interface{}{ 258 "endpoint": "http://vault", 259 }, 260 }, 261 Status: "active", 262 NumSecrets: 2, 263 }}, 264 SLA: ¶ms.ModelSLAInfo{ 265 Level: "essential", 266 Owner: "user", 267 }, 268 AgentVersion: &expectedAgentVersion, 269 SupportedFeatures: []params.SupportedFeature{ 270 {Name: "example"}, 271 }, 272 } 273 info.CloudCredentialValidity = credentialValidity 274 return info 275 } 276 277 func (s *modelInfoSuite) TestModelInfo(c *gc.C) { 278 s.PatchValue(&commonsecrets.GetProvider, func(string) (provider.SecretBackendProvider, error) { 279 return mockSecretProvider{}, nil 280 }) 281 info := s.getModelInfo(c, s.modelmanager, s.st.model.cfg.UUID()) 282 _true := true 283 s.assertModelInfo(c, info, s.expectedModelInfo(c, &_true)) 284 s.st.CheckCalls(c, []jujutesting.StubCall{ 285 {"ControllerTag", nil}, 286 {"GetBackend", []interface{}{s.st.model.cfg.UUID()}}, 287 {"Model", nil}, 288 {"IsController", nil}, 289 {"AllMachines", nil}, 290 {"ControllerNodes", nil}, 291 {"HAPrimaryMachine", nil}, 292 {"ControllerUUID", nil}, 293 {"LatestMigration", nil}, 294 {"CloudCredential", []interface{}{names.NewCloudCredentialTag("some-cloud/bob/some-credential")}}, 295 }) 296 } 297 298 func (s *modelInfoSuite) TestModelInfoV9(c *gc.C) { 299 s.PatchValue(&commonsecrets.GetProvider, func(string) (provider.SecretBackendProvider, error) { 300 return mockSecretProvider{}, nil 301 }) 302 modelManagerV9 := &modelmanager.ModelManagerAPIV9{s.modelmanager} 303 info := s.getModelInfo(c, modelManagerV9, s.st.model.cfg.UUID()) 304 _true := true 305 expectedInfo := s.expectedModelInfo(c, &_true) 306 expectedInfo.DefaultSeries = "jammy" 307 expectedInfo.DefaultBase = "ubuntu@22.04/stable" 308 s.assertModelInfo(c, info, expectedInfo) 309 310 s.st.CheckCalls(c, []jujutesting.StubCall{ 311 {"ControllerTag", nil}, 312 {"GetBackend", []interface{}{s.st.model.cfg.UUID()}}, 313 {"Model", nil}, 314 {"IsController", nil}, 315 {"AllMachines", nil}, 316 {"ControllerNodes", nil}, 317 {"HAPrimaryMachine", nil}, 318 {"ControllerUUID", nil}, 319 {"LatestMigration", nil}, 320 {"CloudCredential", []interface{}{names.NewCloudCredentialTag("some-cloud/bob/some-credential")}}, 321 }) 322 } 323 324 func (s *modelInfoSuite) assertModelInfo(c *gc.C, got, expected params.ModelInfo) { 325 c.Assert(got, jc.DeepEquals, expected) 326 s.st.model.CheckCalls(c, []jujutesting.StubCall{ 327 {"Name", nil}, 328 {"Type", nil}, 329 {"UUID", nil}, 330 {"ControllerUUID", nil}, 331 {"UUID", nil}, 332 {"Owner", nil}, 333 {"Life", nil}, 334 {"CloudName", nil}, 335 {"CloudRegion", nil}, 336 {"CloudCredentialTag", nil}, 337 {"SLALevel", nil}, 338 {"SLAOwner", nil}, 339 {"Life", nil}, 340 {"Config", nil}, 341 {"Status", nil}, 342 {"Users", nil}, 343 {"ModelTag", nil}, 344 {"ModelTag", nil}, 345 {"ModelTag", nil}, 346 {"ModelTag", nil}, 347 {"LastModelConnection", []interface{}{names.NewUserTag("admin")}}, 348 {"LastModelConnection", []interface{}{names.NewLocalUserTag("bob")}}, 349 {"LastModelConnection", []interface{}{names.NewLocalUserTag("charlotte")}}, 350 {"LastModelConnection", []interface{}{names.NewLocalUserTag("mary")}}, 351 {"Type", nil}, 352 }) 353 } 354 355 func (s *modelInfoSuite) TestModelInfoWriteAccess(c *gc.C) { 356 mary := names.NewUserTag("mary@local") 357 s.authorizer.HasWriteTag = mary 358 s.setAPIUser(c, mary) 359 info := s.getModelInfo(c, s.modelmanager, s.st.model.cfg.UUID()) 360 c.Assert(info.Users, gc.HasLen, 1) 361 c.Assert(info.Users[0].UserName, gc.Equals, "mary") 362 c.Assert(info.Machines, gc.HasLen, 2) 363 } 364 365 func (s *modelInfoSuite) TestModelInfoNonOwner(c *gc.C) { 366 s.setAPIUser(c, names.NewUserTag("charlotte@local")) 367 info := s.getModelInfo(c, s.modelmanager, s.st.model.cfg.UUID()) 368 c.Assert(info.Users, gc.HasLen, 1) 369 c.Assert(info.Users[0].UserName, gc.Equals, "charlotte") 370 c.Assert(info.Machines, gc.HasLen, 0) 371 } 372 373 type modelInfo interface { 374 ModelInfo(params.Entities) (params.ModelInfoResults, error) 375 } 376 377 func (s *modelInfoSuite) getModelInfo(c *gc.C, modelInfo modelInfo, modelUUID string) params.ModelInfo { 378 results, err := modelInfo.ModelInfo(params.Entities{ 379 Entities: []params.Entity{{ 380 names.NewModelTag(modelUUID).String(), 381 }}, 382 }) 383 c.Assert(err, jc.ErrorIsNil) 384 c.Assert(results.Results, gc.HasLen, 1) 385 c.Check(results.Results[0].Result, gc.NotNil) 386 c.Check(results.Results[0].Error, gc.IsNil) 387 return *results.Results[0].Result 388 } 389 390 func (s *modelInfoSuite) TestModelInfoErrorInvalidTag(c *gc.C) { 391 s.testModelInfoError(c, "user-bob", `"user-bob" is not a valid model tag`) 392 } 393 394 func (s *modelInfoSuite) TestModelInfoErrorGetModelNotFound(c *gc.C) { 395 s.st.SetErrors(errors.NotFoundf("model")) 396 s.testModelInfoError(c, coretesting.ModelTag.String(), `permission denied`) 397 } 398 399 func (s *modelInfoSuite) TestModelInfoErrorModelConfig(c *gc.C) { 400 s.st.model.SetErrors(errors.Errorf("no config for you")) 401 s.testModelInfoError(c, coretesting.ModelTag.String(), `no config for you`) 402 } 403 404 func (s *modelInfoSuite) TestModelInfoErrorModelUsers(c *gc.C) { 405 s.st.model.SetErrors( 406 nil, //Config 407 nil, //Status 408 errors.Errorf("no users for you"), // Users 409 ) 410 s.testModelInfoError(c, coretesting.ModelTag.String(), `no users for you`) 411 } 412 413 func (s *modelInfoSuite) TestModelInfoErrorNoModelUsers(c *gc.C) { 414 s.st.model.users = nil 415 s.testModelInfoError(c, coretesting.ModelTag.String(), `permission denied`) 416 } 417 418 func (s *modelInfoSuite) TestModelInfoErrorNoAccess(c *gc.C) { 419 s.setAPIUser(c, names.NewUserTag("nemo@local")) 420 s.testModelInfoError(c, coretesting.ModelTag.String(), `permission denied`) 421 } 422 423 func (s *modelInfoSuite) TestRunningMigration(c *gc.C) { 424 start := time.Now().Add(-20 * time.Minute) 425 s.st.migration = &mockMigration{ 426 status: "computing optimal bin packing", 427 start: start, 428 } 429 430 results, err := s.modelmanager.ModelInfo(params.Entities{ 431 Entities: []params.Entity{{coretesting.ModelTag.String()}}, 432 }) 433 434 c.Assert(err, jc.ErrorIsNil) 435 migrationResult := results.Results[0].Result.Migration 436 c.Assert(migrationResult.Status, gc.Equals, "computing optimal bin packing") 437 c.Assert(*migrationResult.Start, gc.Equals, start) 438 c.Assert(migrationResult.End, gc.IsNil) 439 } 440 441 func (s *modelInfoSuite) TestFailedMigration(c *gc.C) { 442 start := time.Now().Add(-20 * time.Minute) 443 end := time.Now().Add(-10 * time.Minute) 444 s.st.migration = &mockMigration{ 445 status: "couldn't realign alternate time frames", 446 start: start, 447 end: end, 448 } 449 450 results, err := s.modelmanager.ModelInfo(params.Entities{ 451 Entities: []params.Entity{{coretesting.ModelTag.String()}}, 452 }) 453 454 c.Assert(err, jc.ErrorIsNil) 455 migrationResult := results.Results[0].Result.Migration 456 c.Assert(migrationResult.Status, gc.Equals, "couldn't realign alternate time frames") 457 c.Assert(*migrationResult.Start, gc.Equals, start) 458 c.Assert(*migrationResult.End, gc.Equals, end) 459 } 460 461 func (s *modelInfoSuite) TestNoMigration(c *gc.C) { 462 results, err := s.modelmanager.ModelInfo(params.Entities{ 463 Entities: []params.Entity{{coretesting.ModelTag.String()}}, 464 }) 465 c.Assert(err, jc.ErrorIsNil) 466 c.Assert(results.Results[0].Result.Migration, gc.IsNil) 467 } 468 469 func (s *modelInfoSuite) TestAliveModelGetsAllInfo(c *gc.C) { 470 s.assertSuccess(c, s.st.model.cfg.UUID(), state.Alive, life.Alive) 471 } 472 473 func (s *modelInfoSuite) TestAliveModelWithConfigFailure(c *gc.C) { 474 s.st.model.life = state.Alive 475 s.setModelConfigError() 476 s.testModelInfoError(c, s.st.model.tag.String(), "config not found") 477 } 478 479 func (s *modelInfoSuite) TestAliveModelWithStatusFailure(c *gc.C) { 480 s.st.model.life = state.Alive 481 s.setModelStatusError() 482 s.testModelInfoError(c, s.st.model.tag.String(), "status not found") 483 } 484 485 func (s *modelInfoSuite) TestAliveModelWithUsersFailure(c *gc.C) { 486 s.st.model.life = state.Alive 487 s.setModelUsersError() 488 s.testModelInfoError(c, s.st.model.tag.String(), "users not found") 489 } 490 491 func (s *modelInfoSuite) TestDeadModelGetsAllInfo(c *gc.C) { 492 s.assertSuccess(c, s.st.model.cfg.UUID(), state.Dead, life.Dead) 493 } 494 495 func (s *modelInfoSuite) TestDeadModelWithConfigFailure(c *gc.C) { 496 testData := incompleteModelInfoTest{ 497 failModel: s.setModelConfigError, 498 desiredLife: state.Dead, 499 expectedLife: life.Dead, 500 } 501 s.assertSuccessWithMissingData(c, testData) 502 } 503 504 func (s *modelInfoSuite) TestDeadModelWithStatusFailure(c *gc.C) { 505 testData := incompleteModelInfoTest{ 506 failModel: s.setModelStatusError, 507 desiredLife: state.Dead, 508 expectedLife: life.Dead, 509 } 510 s.assertSuccessWithMissingData(c, testData) 511 } 512 513 func (s *modelInfoSuite) TestDeadModelWithUsersFailure(c *gc.C) { 514 testData := incompleteModelInfoTest{ 515 failModel: s.setModelUsersError, 516 desiredLife: state.Dead, 517 expectedLife: life.Dead, 518 } 519 s.assertSuccessWithMissingData(c, testData) 520 } 521 522 func (s *modelInfoSuite) TestDyingModelWithConfigFailure(c *gc.C) { 523 testData := incompleteModelInfoTest{ 524 failModel: s.setModelConfigError, 525 desiredLife: state.Dying, 526 expectedLife: life.Dying, 527 } 528 s.assertSuccessWithMissingData(c, testData) 529 } 530 531 func (s *modelInfoSuite) TestDyingModelWithStatusFailure(c *gc.C) { 532 testData := incompleteModelInfoTest{ 533 failModel: s.setModelStatusError, 534 desiredLife: state.Dying, 535 expectedLife: life.Dying, 536 } 537 s.assertSuccessWithMissingData(c, testData) 538 } 539 540 func (s *modelInfoSuite) TestDyingModelWithUsersFailure(c *gc.C) { 541 testData := incompleteModelInfoTest{ 542 failModel: s.setModelUsersError, 543 desiredLife: state.Dying, 544 expectedLife: life.Dying, 545 } 546 s.assertSuccessWithMissingData(c, testData) 547 } 548 549 func (s *modelInfoSuite) TestImportingModelGetsAllInfo(c *gc.C) { 550 s.st.model.migrationStatus = state.MigrationModeImporting 551 s.assertSuccess(c, s.st.model.cfg.UUID(), state.Alive, life.Alive) 552 } 553 554 func (s *modelInfoSuite) TestImportingModelWithConfigFailure(c *gc.C) { 555 s.st.model.migrationStatus = state.MigrationModeImporting 556 testData := incompleteModelInfoTest{ 557 failModel: s.setModelConfigError, 558 desiredLife: state.Alive, 559 expectedLife: life.Alive, 560 } 561 s.assertSuccessWithMissingData(c, testData) 562 } 563 564 func (s *modelInfoSuite) TestImportingModelWithStatusFailure(c *gc.C) { 565 s.st.model.migrationStatus = state.MigrationModeImporting 566 testData := incompleteModelInfoTest{ 567 failModel: s.setModelStatusError, 568 desiredLife: state.Alive, 569 expectedLife: life.Alive, 570 } 571 s.assertSuccessWithMissingData(c, testData) 572 } 573 574 func (s *modelInfoSuite) TestImportingModelWithUsersFailure(c *gc.C) { 575 s.st.model.migrationStatus = state.MigrationModeImporting 576 testData := incompleteModelInfoTest{ 577 failModel: s.setModelUsersError, 578 desiredLife: state.Alive, 579 expectedLife: life.Alive, 580 } 581 s.assertSuccessWithMissingData(c, testData) 582 } 583 584 type incompleteModelInfoTest struct { 585 failModel func() 586 desiredLife state.Life 587 expectedLife life.Value 588 } 589 590 func (s *modelInfoSuite) setModelConfigError() { 591 s.st.model.SetErrors(errors.NotFoundf("config")) 592 } 593 594 func (s *modelInfoSuite) setModelStatusError() { 595 s.st.model.SetErrors( 596 nil, //Config 597 errors.NotFoundf("status"), //Status 598 ) 599 } 600 601 func (s *modelInfoSuite) setModelUsersError() { 602 s.st.model.SetErrors( 603 nil, //Config 604 nil, //Status 605 errors.NotFoundf("users"), //Users 606 ) 607 } 608 609 func (s *modelInfoSuite) assertSuccessWithMissingData(c *gc.C, test incompleteModelInfoTest) { 610 test.failModel() 611 // We do not expect any errors to surface and still want to get basic model info. 612 s.assertSuccess(c, s.st.model.cfg.UUID(), test.desiredLife, test.expectedLife) 613 } 614 615 func (s *modelInfoSuite) assertSuccess(c *gc.C, modelUUID string, desiredLife state.Life, expectedLife life.Value) { 616 s.st.model.life = desiredLife 617 // should get no errors 618 info := s.getModelInfo(c, s.modelmanager, modelUUID) 619 c.Assert(info.UUID, gc.Equals, modelUUID) 620 c.Assert(info.Life, gc.Equals, expectedLife) 621 } 622 623 func (s *modelInfoSuite) testModelInfoError(c *gc.C, modelTag, expectedErr string) { 624 results, err := s.modelmanager.ModelInfo(params.Entities{ 625 Entities: []params.Entity{{modelTag}}, 626 }) 627 c.Assert(err, jc.ErrorIsNil) 628 c.Assert(results.Results, gc.HasLen, 1) 629 c.Assert(results.Results[0].Result, gc.IsNil) 630 c.Assert(results.Results[0].Error, gc.ErrorMatches, expectedErr) 631 } 632 633 type unitRetriever interface { 634 Unit(name string) (*state.Unit, error) 635 } 636 637 // metricSender defines methods required by the metricsender package. 638 type metricSender interface { 639 MetricsManager() (*state.MetricsManager, error) 640 MetricsToSend(batchSize int) ([]*state.MetricBatch, error) 641 SetMetricBatchesSent(batchUUIDs []string) error 642 CountOfUnsentMetrics() (int, error) 643 CountOfSentMetrics() (int, error) 644 CleanupOldMetrics() error 645 } 646 647 type mockCaasBroker struct { 648 jujutesting.Stub 649 caas.Broker 650 651 namespace string 652 } 653 654 func (m *mockCaasBroker) Create(context.ProviderCallContext, environs.CreateParams) error { 655 m.MethodCall(m, "Create") 656 if m.namespace == "existing-ns" { 657 return errors.AlreadyExistsf("namespace %q", m.namespace) 658 } 659 return nil 660 } 661 662 type mockState struct { 663 jujutesting.Stub 664 665 environs.EnvironConfigGetter 666 common.APIHostPortsForAgentsGetter 667 common.ToolsStorageGetter 668 common.BlockGetter 669 metricSender 670 unitRetriever 671 672 controllerCfg *controller.Config 673 controllerUUID string 674 cloud cloud.Cloud 675 clouds map[names.CloudTag]cloud.Cloud 676 cloudUsers map[string]permission.Access 677 model *mockModel 678 controllerModel *mockModel 679 users []permission.UserAccess 680 cred state.Credential 681 machines []common.Machine 682 controllerNodes []common.ControllerNode 683 cfgDefaults config.ModelDefaultAttributes 684 blockMsg string 685 block state.BlockType 686 migration *mockMigration 687 modelConfig *config.Config 688 689 modelDetailsForUser func() ([]state.ModelSummary, error) 690 } 691 692 type fakeModelDescription struct { 693 description.Model `yaml:"-"` 694 695 UUID string `yaml:"model-uuid"` 696 } 697 698 func (st *mockState) ModelUUID() string { 699 st.MethodCall(st, "ModelUUID") 700 return st.model.UUID() 701 } 702 703 func (st *mockState) Name() string { 704 st.MethodCall(st, "Name") 705 return "test-model" 706 } 707 708 func (st *mockState) ControllerModelUUID() string { 709 st.MethodCall(st, "ControllerModelUUID") 710 return st.controllerModel.tag.Id() 711 } 712 713 func (st *mockState) ControllerModelTag() names.ModelTag { 714 st.MethodCall(st, "ControllerModelTag") 715 return st.controllerModel.tag 716 } 717 718 func (st *mockState) Export(leaders map[string]string) (description.Model, error) { 719 st.MethodCall(st, "Export", leaders) 720 return &fakeModelDescription{UUID: st.model.UUID()}, nil 721 } 722 723 func (st *mockState) ExportPartial(cfg state.ExportConfig) (description.Model, error) { 724 st.MethodCall(st, "ExportPartial", cfg) 725 if !cfg.IgnoreIncompleteModel { 726 return nil, errors.New("expected IgnoreIncompleteModel=true") 727 } 728 return &fakeModelDescription{UUID: st.model.UUID()}, nil 729 } 730 731 func (st *mockState) AllModelUUIDs() ([]string, error) { 732 st.MethodCall(st, "AllModelUUIDs") 733 return []string{st.model.UUID()}, st.NextErr() 734 } 735 736 func (st *mockState) GetBackend(modelUUID string) (common.ModelManagerBackend, func() bool, error) { 737 st.MethodCall(st, "GetBackend", modelUUID) 738 return st, func() bool { return true }, st.NextErr() 739 } 740 741 func (st *mockState) GetModel(modelUUID string) (common.Model, func() bool, error) { 742 st.MethodCall(st, "GetModel", modelUUID) 743 return st.model, func() bool { return true }, st.NextErr() 744 } 745 746 func (st *mockState) ModelUUIDsForUser(user names.UserTag) ([]string, error) { 747 st.MethodCall(st, "ModelUUIDsForUser", user) 748 return nil, st.NextErr() 749 } 750 751 func (st *mockState) AllApplications() ([]common.Application, error) { 752 st.MethodCall(st, "AllApplications") 753 return nil, st.NextErr() 754 } 755 756 func (st *mockState) AllVolumes() ([]state.Volume, error) { 757 st.MethodCall(st, "AllVolumes") 758 return nil, st.NextErr() 759 } 760 761 func (st *mockState) AllFilesystems() ([]state.Filesystem, error) { 762 st.MethodCall(st, "AllFilesystems") 763 return nil, st.NextErr() 764 } 765 766 func (st *mockState) IsControllerAdmin(user names.UserTag) (bool, error) { 767 st.MethodCall(st, "IsControllerAdmin", user) 768 if st.controllerModel == nil { 769 return user.Id() == "admin", st.NextErr() 770 } 771 if st.controllerModel.users == nil { 772 return user.Id() == "admin", st.NextErr() 773 } 774 775 for _, u := range st.controllerModel.users { 776 if user.Name() == u.userName && u.access == permission.AdminAccess { 777 nextErr := st.NextErr() 778 if user.Name() != "admin" { 779 panic(user.Name()) 780 } 781 return true, nextErr 782 } 783 } 784 return false, st.NextErr() 785 } 786 787 func (st *mockState) GetCloudAccess(cloud string, user names.UserTag) (permission.Access, error) { 788 st.MethodCall(st, "GetCloudAccess", user) 789 if perm, ok := st.cloudUsers[user.Id()]; ok { 790 return perm, nil 791 } 792 return permission.NoAccess, errors.NotFoundf("user %v", user.Id()) 793 } 794 795 func (st *mockState) NewModel(args state.ModelArgs) (common.Model, common.ModelManagerBackend, error) { 796 st.MethodCall(st, "NewModel", args) 797 st.model.tag = names.NewModelTag(args.Config.UUID()) 798 return st.model, st, st.NextErr() 799 } 800 801 func (st *mockState) ControllerModel() (common.Model, error) { 802 st.MethodCall(st, "ControllerModel") 803 return st.controllerModel, st.NextErr() 804 } 805 806 func (st *mockState) ControllerTag() names.ControllerTag { 807 st.MethodCall(st, "ControllerTag") 808 return names.NewControllerTag(st.controllerUUID) 809 } 810 811 func (st *mockState) ComposeNewModelConfig(modelAttr map[string]interface{}, regionSpec *environscloudspec.CloudRegionSpec) (map[string]interface{}, error) { 812 st.MethodCall(st, "ComposeNewModelConfig") 813 attr := make(map[string]interface{}) 814 for attrName, val := range modelAttr { 815 attr[attrName] = val 816 } 817 attr["something"] = "value" 818 return attr, st.NextErr() 819 } 820 821 func (st *mockState) ControllerUUID() string { 822 st.MethodCall(st, "ControllerUUID") 823 return st.controllerUUID 824 } 825 826 func (st *mockState) IsController() bool { 827 st.MethodCall(st, "IsController") 828 return st.controllerUUID == st.model.UUID() 829 } 830 831 func (st *mockState) ControllerConfig() (controller.Config, error) { 832 st.MethodCall(st, "ControllerConfig") 833 if st.controllerCfg != nil { 834 return *st.controllerCfg, st.NextErr() 835 } 836 837 return controller.Config{ 838 controller.ControllerUUIDKey: "deadbeef-1bad-500d-9000-4b1d0d06f00d", 839 }, st.NextErr() 840 } 841 842 func (st *mockState) ControllerNodes() ([]common.ControllerNode, error) { 843 st.MethodCall(st, "ControllerNodes") 844 return st.controllerNodes, st.NextErr() 845 } 846 847 func (st *mockState) Model() (common.Model, error) { 848 st.MethodCall(st, "Model") 849 return st.model, st.NextErr() 850 } 851 852 func (st *mockState) ModelTag() names.ModelTag { 853 st.MethodCall(st, "ModelTag") 854 return st.model.ModelTag() 855 } 856 857 func (st *mockState) AllMachines() ([]common.Machine, error) { 858 st.MethodCall(st, "AllMachines") 859 return st.machines, st.NextErr() 860 } 861 862 func (st *mockState) Clouds() (map[names.CloudTag]cloud.Cloud, error) { 863 st.MethodCall(st, "Clouds") 864 return st.clouds, st.NextErr() 865 } 866 867 func (st *mockState) SetModelAgentVersion(newVersion version.Number, stream *string, ignoreAgentVersions bool) error { 868 return errors.NotImplementedf("SetModelAgentVersion") 869 } 870 871 func (st *mockState) AbortCurrentUpgrade() error { 872 return errors.NotImplementedf("AbortCurrentUpgrade") 873 } 874 875 func (st *mockState) Cloud(name string) (cloud.Cloud, error) { 876 st.MethodCall(st, "Cloud", name) 877 return st.cloud, st.NextErr() 878 } 879 880 func (st *mockState) CloudCredential(tag names.CloudCredentialTag) (state.Credential, error) { 881 st.MethodCall(st, "CloudCredential", tag) 882 return st.cred, st.NextErr() 883 } 884 885 func (st *mockState) Close() error { 886 st.MethodCall(st, "Close") 887 return st.NextErr() 888 } 889 890 func (st *mockState) AddControllerUser(spec state.UserAccessSpec) (permission.UserAccess, error) { 891 st.MethodCall(st, "AddControllerUser", spec) 892 return permission.UserAccess{}, st.NextErr() 893 } 894 895 func (st *mockState) UserAccess(tag names.UserTag, target names.Tag) (permission.UserAccess, error) { 896 st.MethodCall(st, "ModelUser", tag, target) 897 for _, user := range st.users { 898 if user.UserTag != tag { 899 continue 900 } 901 nextErr := st.NextErr() 902 if nextErr != nil { 903 return permission.UserAccess{}, nextErr 904 } 905 return user, nil 906 } 907 return permission.UserAccess{}, st.NextErr() 908 } 909 910 func (st *mockState) ModelSummariesForUser(user names.UserTag, isSuperuser bool) ([]state.ModelSummary, error) { 911 st.MethodCall(st, "ModelSummariesForUser", user, isSuperuser) 912 return st.modelDetailsForUser() 913 } 914 915 func (st *mockState) ModelBasicInfoForUser(user names.UserTag, isSuperuser bool) ([]state.ModelAccessInfo, error) { 916 st.MethodCall(st, "ModelBasicInfoForUser", user, isSuperuser) 917 return []state.ModelAccessInfo{}, st.NextErr() 918 } 919 920 func (st *mockState) RemoveUserAccess(subject names.UserTag, target names.Tag) error { 921 st.MethodCall(st, "RemoveUserAccess", subject, target) 922 return st.NextErr() 923 } 924 925 func (st *mockState) SetUserAccess(subject names.UserTag, target names.Tag, access permission.Access) (permission.UserAccess, error) { 926 st.MethodCall(st, "SetUserAccess", subject, target, access) 927 return permission.UserAccess{}, st.NextErr() 928 } 929 930 func (st *mockState) ModelConfigDefaultValues(cloud string) (config.ModelDefaultAttributes, error) { 931 st.MethodCall(st, "ModelConfigDefaultValues", cloud) 932 return st.cfgDefaults, nil 933 } 934 935 func (st *mockState) UpdateModelConfigDefaultValues(update map[string]interface{}, remove []string, rspec *environscloudspec.CloudRegionSpec) error { 936 st.MethodCall(st, "UpdateModelConfigDefaultValues", update, remove, rspec) 937 for k, v := range update { 938 if rspec != nil { 939 adv := st.cfgDefaults[k] 940 adv.Regions = append(adv.Regions, config.RegionDefaultValue{ 941 Name: rspec.Region, 942 Value: v}) 943 944 } else { 945 st.cfgDefaults[k] = config.AttributeDefaultValues{Controller: v} 946 } 947 } 948 for _, n := range remove { 949 if rspec != nil { 950 for i, r := range st.cfgDefaults[n].Regions { 951 if r.Name == rspec.Region { 952 adv := st.cfgDefaults[n] 953 adv.Regions = append(adv.Regions[:i], adv.Regions[i+1:]...) 954 st.cfgDefaults[n] = adv 955 } 956 } 957 } else { 958 if len(st.cfgDefaults[n].Regions) == 0 { 959 delete(st.cfgDefaults, n) 960 } else { 961 962 st.cfgDefaults[n] = config.AttributeDefaultValues{ 963 Regions: st.cfgDefaults[n].Regions} 964 } 965 } 966 } 967 return nil 968 } 969 970 func (st *mockState) GetBlockForType(t state.BlockType) (state.Block, bool, error) { 971 st.MethodCall(st, "GetBlockForType", t) 972 if st.block == t { 973 return &mockBlock{t: t, m: st.blockMsg}, true, nil 974 } else { 975 return nil, false, nil 976 } 977 } 978 979 func (st *mockState) SaveProviderSubnets(subnets []network.SubnetInfo, spaceID string) error { 980 st.MethodCall(st, "SaveProviderSubnets", subnets, spaceID) 981 return st.NextErr() 982 } 983 984 func (st *mockState) DumpAll() (map[string]interface{}, error) { 985 st.MethodCall(st, "DumpAll") 986 return map[string]interface{}{ 987 "models": "lots of data", 988 }, st.NextErr() 989 } 990 991 func (st *mockState) LatestMigration() (state.ModelMigration, error) { 992 st.MethodCall(st, "LatestMigration") 993 if st.migration == nil { 994 // Handle nil->notfound directly here rather than having to 995 // count errors. 996 return nil, errors.NotFoundf("") 997 } 998 return st.migration, st.NextErr() 999 } 1000 1001 func (st *mockState) SetModelMeterStatus(level, message string) error { 1002 st.MethodCall(st, "SetModelMeterStatus", level, message) 1003 return st.NextErr() 1004 } 1005 1006 func (st *mockState) ModelConfig() (*config.Config, error) { 1007 st.MethodCall(st, "ModelConfig") 1008 return st.modelConfig, st.NextErr() 1009 } 1010 1011 func (st *mockState) MetricsManager() (*state.MetricsManager, error) { 1012 st.MethodCall(st, "MetricsManager") 1013 return nil, errors.New("nope") 1014 } 1015 1016 func (st *mockState) HAPrimaryMachine() (names.MachineTag, error) { 1017 st.MethodCall(st, "HAPrimaryMachine") 1018 return names.MachineTag{}, nil 1019 } 1020 1021 func (st *mockState) AddSpace(name string, provider network.Id, subnetIds []string, public bool) (*state.Space, error) { 1022 st.MethodCall(st, "AddSpace", name, provider, subnetIds, public) 1023 return nil, st.NextErr() 1024 } 1025 1026 func (st *mockState) AllEndpointBindingsSpaceNames() (set.Strings, error) { 1027 st.MethodCall(st, "AllEndpointBindingsSpaceNames") 1028 return set.NewStrings(), nil 1029 } 1030 1031 func (st *mockState) DefaultEndpointBindingSpace() (string, error) { 1032 st.MethodCall(st, "DefaultEndpointBindingSpace") 1033 return "alpha", nil 1034 } 1035 1036 func (st *mockState) AllSpaces() ([]*state.Space, error) { 1037 st.MethodCall(st, "AllSpaces") 1038 return nil, st.NextErr() 1039 } 1040 1041 func (st *mockState) ConstraintsBySpaceName(spaceName string) ([]*state.Constraints, error) { 1042 st.MethodCall(st, "ConstraintsBySpaceName", spaceName) 1043 return nil, st.NextErr() 1044 } 1045 1046 func (st *mockState) ListModelSecrets(all bool) (map[string]set.Strings, error) { 1047 return map[string]set.Strings{ 1048 "backend-id": set.NewStrings("a", "b"), 1049 }, nil 1050 } 1051 1052 func (st *mockState) GetSecretBackendByID(id string) (*secrets.SecretBackend, error) { 1053 if id != "backend-id" { 1054 return nil, errors.NotFoundf("backend %q", id) 1055 } 1056 return &secrets.SecretBackend{ 1057 ID: "backend-id", 1058 Name: "myvault", 1059 BackendType: "vault", 1060 Config: map[string]interface{}{ 1061 "endpoint": "http://vault", 1062 "token": "secret", 1063 }, 1064 }, nil 1065 } 1066 1067 func (st *mockState) ListSecretBackends() ([]*secrets.SecretBackend, error) { 1068 return []*secrets.SecretBackend{{ 1069 ID: "backend-id", 1070 Name: "myvault", 1071 BackendType: "vault", 1072 Config: map[string]interface{}{ 1073 "endpoint": "http://vault", 1074 "token": "secret", 1075 }, 1076 }}, nil 1077 } 1078 1079 type mockSecretProvider struct { 1080 provider.ProviderConfig 1081 provider.SecretBackendProvider 1082 } 1083 1084 func (mockSecretProvider) Type() string { 1085 return "vault" 1086 } 1087 1088 func (mockSecretProvider) NewBackend(cfg *provider.ModelBackendConfig) (provider.SecretsBackend, error) { 1089 return mockSecretBackend{}, nil 1090 } 1091 1092 func (mockSecretProvider) ConfigSchema() environschema.Fields { 1093 return environschema.Fields{ 1094 "token": { 1095 Secret: true, 1096 }, 1097 } 1098 } 1099 1100 type mockSecretBackend struct { 1101 provider.SecretsBackend 1102 } 1103 1104 func (mockSecretBackend) Ping() error { 1105 return nil 1106 } 1107 1108 type mockBlock struct { 1109 state.Block 1110 t state.BlockType 1111 m string 1112 } 1113 1114 func (m mockBlock) Id() string { return "" } 1115 1116 func (m mockBlock) Tag() (names.Tag, error) { return names.NewModelTag("mocktesting"), nil } 1117 1118 func (m mockBlock) Type() state.BlockType { return m.t } 1119 1120 func (m mockBlock) Message() string { return m.m } 1121 1122 func (m mockBlock) ModelUUID() string { return "" } 1123 1124 type mockControllerNode struct { 1125 id string 1126 hasVote bool 1127 wantsVote bool 1128 } 1129 1130 func (m *mockControllerNode) Id() string { 1131 return m.id 1132 } 1133 1134 func (m *mockControllerNode) WantsVote() bool { 1135 return m.wantsVote 1136 } 1137 1138 func (m *mockControllerNode) HasVote() bool { 1139 return m.hasVote 1140 } 1141 1142 type mockMachine struct { 1143 common.Machine 1144 id string 1145 life state.Life 1146 containerType instance.ContainerType 1147 hw *instance.HardwareCharacteristics 1148 } 1149 1150 func (m *mockMachine) Id() string { 1151 return m.id 1152 } 1153 1154 func (m *mockMachine) Life() state.Life { 1155 return m.life 1156 } 1157 1158 func (m *mockMachine) ContainerType() instance.ContainerType { 1159 return m.containerType 1160 } 1161 1162 func (m *mockMachine) HardwareCharacteristics() (*instance.HardwareCharacteristics, error) { 1163 return m.hw, nil 1164 } 1165 1166 func (m *mockMachine) InstanceId() (instance.Id, error) { 1167 return "", nil 1168 } 1169 1170 func (m *mockMachine) InstanceNames() (instance.Id, string, error) { 1171 return "", "", nil 1172 } 1173 1174 func (m *mockMachine) HasVote() bool { 1175 return false 1176 } 1177 1178 func (m *mockMachine) Status() (status.StatusInfo, error) { 1179 return status.StatusInfo{}, nil 1180 } 1181 1182 type mockModel struct { 1183 jujutesting.Stub 1184 owner names.UserTag 1185 life state.Life 1186 tag names.ModelTag 1187 status status.StatusInfo 1188 cfg *config.Config 1189 users []*mockModelUser 1190 migrationStatus state.MigrationMode 1191 controllerUUID string 1192 isController bool 1193 cloud cloud.Cloud 1194 cred state.Credential 1195 setCloudCredentialF func(tag names.CloudCredentialTag) (bool, error) 1196 } 1197 1198 func (m *mockModel) Config() (*config.Config, error) { 1199 m.MethodCall(m, "Config") 1200 return m.cfg, m.NextErr() 1201 } 1202 1203 func (m *mockModel) Owner() names.UserTag { 1204 m.MethodCall(m, "Owner") 1205 return m.owner 1206 } 1207 1208 func (m *mockModel) ModelTag() names.ModelTag { 1209 m.MethodCall(m, "ModelTag") 1210 return m.tag 1211 } 1212 1213 func (m *mockModel) Type() state.ModelType { 1214 m.MethodCall(m, "Type") 1215 return state.ModelTypeIAAS 1216 } 1217 1218 func (m *mockModel) Life() state.Life { 1219 m.MethodCall(m, "Life") 1220 return m.life 1221 } 1222 1223 func (m *mockModel) Status() (status.StatusInfo, error) { 1224 m.MethodCall(m, "Status") 1225 return m.status, m.NextErr() 1226 } 1227 1228 func (m *mockModel) CloudName() string { 1229 m.MethodCall(m, "CloudName") 1230 return "some-cloud" 1231 } 1232 1233 func (m *mockModel) Cloud() (cloud.Cloud, error) { 1234 m.MethodCall(m, "CloudValue") 1235 return m.cloud, nil 1236 } 1237 1238 func (m *mockModel) CloudRegion() string { 1239 m.MethodCall(m, "CloudRegion") 1240 return "some-region" 1241 } 1242 1243 func (m *mockModel) CloudCredentialTag() (names.CloudCredentialTag, bool) { 1244 m.MethodCall(m, "CloudCredentialTag") 1245 return names.NewCloudCredentialTag("some-cloud/bob/some-credential"), true 1246 } 1247 1248 func (m *mockModel) CloudCredential() (state.Credential, bool, error) { 1249 m.MethodCall(m, "CloudCredential") 1250 return m.cred, true, nil 1251 } 1252 1253 func (m *mockModel) Users() ([]permission.UserAccess, error) { 1254 m.MethodCall(m, "Users") 1255 if err := m.NextErr(); err != nil { 1256 return nil, err 1257 } 1258 users := make([]permission.UserAccess, len(m.users)) 1259 for i, user := range m.users { 1260 users[i] = permission.UserAccess{ 1261 UserID: strings.ToLower(user.userName), 1262 UserTag: names.NewUserTag(user.userName), 1263 Object: m.ModelTag(), 1264 Access: user.access, 1265 DisplayName: user.displayName, 1266 UserName: user.userName, 1267 } 1268 } 1269 return users, nil 1270 } 1271 1272 func (m *mockModel) Destroy(args state.DestroyModelParams) error { 1273 m.MethodCall(m, "Destroy", args) 1274 return m.NextErr() 1275 } 1276 1277 func (m *mockModel) SLALevel() string { 1278 m.MethodCall(m, "SLALevel") 1279 return "essential" 1280 } 1281 1282 func (m *mockModel) SLAOwner() string { 1283 m.MethodCall(m, "SLAOwner") 1284 return "user" 1285 } 1286 1287 func (m *mockModel) ControllerUUID() string { 1288 m.MethodCall(m, "ControllerUUID") 1289 return m.controllerUUID 1290 } 1291 1292 func (m *mockModel) UUID() string { 1293 m.MethodCall(m, "UUID") 1294 return m.cfg.UUID() 1295 } 1296 1297 func (m *mockModel) Name() string { 1298 m.MethodCall(m, "Name") 1299 return m.cfg.Name() 1300 } 1301 1302 func (m *mockModel) MigrationMode() state.MigrationMode { 1303 m.MethodCall(m, "MigrationMode") 1304 return m.migrationStatus 1305 } 1306 1307 func (m *mockModel) AddUser(spec state.UserAccessSpec) (permission.UserAccess, error) { 1308 m.MethodCall(m, "AddUser", spec) 1309 return permission.UserAccess{}, m.NextErr() 1310 } 1311 func (m *mockModel) LastModelConnection(user names.UserTag) (time.Time, error) { 1312 m.MethodCall(m, "LastModelConnection", user) 1313 return time.Time{}, m.NextErr() 1314 } 1315 1316 func (m *mockModel) AutoConfigureContainerNetworking(environ environs.BootstrapEnviron) error { 1317 m.MethodCall(m, "AutoConfigureContainerNetworking", environ) 1318 return m.NextErr() 1319 } 1320 1321 func (m *mockModel) getModelDetails() state.ModelSummary { 1322 cred, _ := m.CloudCredentialTag() 1323 return state.ModelSummary{ 1324 Name: m.Name(), 1325 UUID: m.UUID(), 1326 Type: m.Type(), 1327 Life: m.Life(), 1328 Owner: m.Owner().Id(), 1329 ControllerUUID: m.ControllerUUID(), 1330 SLALevel: m.SLALevel(), 1331 SLAOwner: m.SLAOwner(), 1332 DefaultSeries: "jammy", 1333 DefaultBase: base.MustParseBaseFromString("ubuntu@22.04"), 1334 CloudTag: m.CloudName(), 1335 CloudRegion: m.CloudRegion(), 1336 CloudCredentialTag: cred.String(), 1337 } 1338 } 1339 1340 func (m *mockModel) SetCloudCredential(tag names.CloudCredentialTag) (bool, error) { 1341 m.MethodCall(m, "SetCloudCredential", tag) 1342 return m.setCloudCredentialF(tag) 1343 } 1344 1345 type mockModelUser struct { 1346 jujutesting.Stub 1347 userName string 1348 displayName string 1349 access permission.Access 1350 } 1351 1352 type mockMigration struct { 1353 state.ModelMigration 1354 1355 status string 1356 start time.Time 1357 end time.Time 1358 } 1359 1360 func (m *mockMigration) StatusMessage() string { 1361 return m.status 1362 } 1363 1364 func (m *mockMigration) StartTime() time.Time { 1365 return m.start 1366 } 1367 1368 func (m *mockMigration) EndTime() time.Time { 1369 return m.end 1370 }