github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/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/errors"
    11  	gitjujutesting "github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils/series"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/juju/names.v2"
    16  
    17  	"github.com/juju/juju/apiserver/common"
    18  	"github.com/juju/juju/apiserver/metricsender"
    19  	"github.com/juju/juju/apiserver/modelmanager"
    20  	"github.com/juju/juju/apiserver/params"
    21  	apiservertesting "github.com/juju/juju/apiserver/testing"
    22  	"github.com/juju/juju/cloud"
    23  	"github.com/juju/juju/controller"
    24  	"github.com/juju/juju/core/description"
    25  	"github.com/juju/juju/environs"
    26  	"github.com/juju/juju/environs/config"
    27  	"github.com/juju/juju/instance"
    28  	"github.com/juju/juju/permission"
    29  	"github.com/juju/juju/state"
    30  	"github.com/juju/juju/status"
    31  	coretesting "github.com/juju/juju/testing"
    32  )
    33  
    34  type modelInfoSuite struct {
    35  	coretesting.BaseSuite
    36  	authorizer   apiservertesting.FakeAuthorizer
    37  	st           *mockState
    38  	modelmanager *modelmanager.ModelManagerAPI
    39  }
    40  
    41  func pUint64(v uint64) *uint64 {
    42  	return &v
    43  }
    44  
    45  var _ = gc.Suite(&modelInfoSuite{})
    46  
    47  func (s *modelInfoSuite) SetUpTest(c *gc.C) {
    48  	s.BaseSuite.SetUpTest(c)
    49  	s.authorizer = apiservertesting.FakeAuthorizer{
    50  		Tag: names.NewUserTag("admin@local"),
    51  	}
    52  	s.st = &mockState{
    53  		modelUUID:      coretesting.ModelTag.Id(),
    54  		controllerUUID: coretesting.ControllerTag.Id(),
    55  		cloud: cloud.Cloud{
    56  			Type:      "dummy",
    57  			AuthTypes: []cloud.AuthType{cloud.EmptyAuthType},
    58  		},
    59  		cfgDefaults: config.ModelDefaultAttributes{
    60  			"attr": config.AttributeDefaultValues{
    61  				Default:    "",
    62  				Controller: "val",
    63  				Regions: []config.RegionDefaultValue{{
    64  					Name:  "dummy",
    65  					Value: "val++"}}},
    66  			"attr2": config.AttributeDefaultValues{
    67  				Controller: "val3",
    68  				Default:    "val2",
    69  				Regions: []config.RegionDefaultValue{{
    70  					Name:  "left",
    71  					Value: "spam"}}},
    72  		},
    73  	}
    74  	s.st.controllerModel = &mockModel{
    75  		owner: names.NewUserTag("admin@local"),
    76  		life:  state.Alive,
    77  		cfg:   coretesting.ModelConfig(c),
    78  		status: status.StatusInfo{
    79  			Status: status.Available,
    80  			Since:  &time.Time{},
    81  		},
    82  		users: []*mockModelUser{{
    83  			userName: "admin",
    84  			access:   permission.AdminAccess,
    85  		}, {
    86  			userName: "otheruser",
    87  			access:   permission.AdminAccess,
    88  		}},
    89  	}
    90  
    91  	s.st.model = &mockModel{
    92  		owner: names.NewUserTag("bob@local"),
    93  		cfg:   coretesting.ModelConfig(c),
    94  		life:  state.Dying,
    95  		status: status.StatusInfo{
    96  			Status: status.Destroying,
    97  			Since:  &time.Time{},
    98  		},
    99  
   100  		users: []*mockModelUser{{
   101  			userName: "admin",
   102  			access:   permission.AdminAccess,
   103  		}, {
   104  			userName:    "bob@local",
   105  			displayName: "Bob",
   106  			access:      permission.ReadAccess,
   107  		}, {
   108  			userName:    "charlotte@local",
   109  			displayName: "Charlotte",
   110  			access:      permission.ReadAccess,
   111  		}, {
   112  			userName:    "mary@local",
   113  			displayName: "Mary",
   114  			access:      permission.WriteAccess,
   115  		}},
   116  	}
   117  	s.st.machines = []common.Machine{
   118  		&mockMachine{
   119  			id:            "1",
   120  			containerType: "none",
   121  			life:          state.Alive,
   122  			hw:            &instance.HardwareCharacteristics{CpuCores: pUint64(1)},
   123  		},
   124  		&mockMachine{
   125  			id:            "2",
   126  			life:          state.Alive,
   127  			containerType: "lxc",
   128  		},
   129  		&mockMachine{
   130  			id:   "3",
   131  			life: state.Dead,
   132  		},
   133  	}
   134  
   135  	var err error
   136  	s.modelmanager, err = modelmanager.NewModelManagerAPI(s.st, nil, &s.authorizer)
   137  	c.Assert(err, jc.ErrorIsNil)
   138  }
   139  
   140  func (s *modelInfoSuite) setAPIUser(c *gc.C, user names.UserTag) {
   141  	s.authorizer.Tag = user
   142  	var err error
   143  	s.modelmanager, err = modelmanager.NewModelManagerAPI(s.st, nil, s.authorizer)
   144  	c.Assert(err, jc.ErrorIsNil)
   145  }
   146  
   147  func (s *modelInfoSuite) TestModelInfo(c *gc.C) {
   148  	info := s.getModelInfo(c)
   149  	c.Assert(info, jc.DeepEquals, params.ModelInfo{
   150  		Name:               "testenv",
   151  		UUID:               s.st.model.cfg.UUID(),
   152  		ControllerUUID:     "deadbeef-1bad-500d-9000-4b1d0d06f00d",
   153  		OwnerTag:           "user-bob@local",
   154  		ProviderType:       "someprovider",
   155  		CloudTag:           "cloud-some-cloud",
   156  		CloudRegion:        "some-region",
   157  		CloudCredentialTag: "cloudcred-some-cloud_bob@local_some-credential",
   158  		DefaultSeries:      series.LatestLts(),
   159  		Life:               params.Dying,
   160  		Status: params.EntityStatus{
   161  			Status: status.Destroying,
   162  			Since:  &time.Time{},
   163  		},
   164  		Users: []params.ModelUserInfo{{
   165  			UserName:       "admin",
   166  			LastConnection: &time.Time{},
   167  			Access:         params.ModelAdminAccess,
   168  		}, {
   169  			UserName:       "bob@local",
   170  			DisplayName:    "Bob",
   171  			LastConnection: &time.Time{},
   172  			Access:         params.ModelReadAccess,
   173  		}, {
   174  			UserName:       "charlotte@local",
   175  			DisplayName:    "Charlotte",
   176  			LastConnection: &time.Time{},
   177  			Access:         params.ModelReadAccess,
   178  		}, {
   179  			UserName:       "mary@local",
   180  			DisplayName:    "Mary",
   181  			LastConnection: &time.Time{},
   182  			Access:         params.ModelWriteAccess,
   183  		}},
   184  		Machines: []params.ModelMachineInfo{{
   185  			Id:       "1",
   186  			Hardware: &params.MachineHardware{Cores: pUint64(1)},
   187  		}, {
   188  			Id: "2",
   189  		}},
   190  	})
   191  	s.st.CheckCalls(c, []gitjujutesting.StubCall{
   192  		{"ControllerTag", nil},
   193  		{"ModelUUID", nil},
   194  		{"ForModel", []interface{}{names.NewModelTag(s.st.model.cfg.UUID())}},
   195  		{"Model", nil},
   196  		{"ControllerConfig", nil},
   197  		{"LastModelConnection", []interface{}{names.NewUserTag("admin")}},
   198  		{"LastModelConnection", []interface{}{names.NewLocalUserTag("bob")}},
   199  		{"LastModelConnection", []interface{}{names.NewLocalUserTag("charlotte")}},
   200  		{"LastModelConnection", []interface{}{names.NewLocalUserTag("mary")}},
   201  		{"AllMachines", nil},
   202  		{"Close", nil},
   203  	})
   204  	s.st.model.CheckCalls(c, []gitjujutesting.StubCall{
   205  		{"Config", nil},
   206  		{"Users", nil},
   207  		{"ModelTag", nil},
   208  		{"ModelTag", nil},
   209  		{"ModelTag", nil},
   210  		{"ModelTag", nil},
   211  		{"Status", nil},
   212  		{"Owner", nil},
   213  		{"Life", nil},
   214  		{"Cloud", nil},
   215  		{"CloudRegion", nil},
   216  		{"CloudCredential", nil},
   217  	})
   218  }
   219  
   220  func (s *modelInfoSuite) TestModelInfoOwner(c *gc.C) {
   221  	s.setAPIUser(c, names.NewUserTag("bob@local"))
   222  	info := s.getModelInfo(c)
   223  	c.Assert(info.Users, gc.HasLen, 4)
   224  	c.Assert(info.Machines, gc.HasLen, 2)
   225  }
   226  
   227  func (s *modelInfoSuite) TestModelInfoWriteAccess(c *gc.C) {
   228  	mary := names.NewUserTag("mary@local")
   229  	s.authorizer.HasWriteTag = mary
   230  	s.setAPIUser(c, mary)
   231  	info := s.getModelInfo(c)
   232  	c.Assert(info.Users, gc.HasLen, 1)
   233  	c.Assert(info.Users[0].UserName, gc.Equals, "mary@local")
   234  	c.Assert(info.Machines, gc.HasLen, 2)
   235  }
   236  
   237  func (s *modelInfoSuite) TestModelInfoNonOwner(c *gc.C) {
   238  	s.setAPIUser(c, names.NewUserTag("charlotte@local"))
   239  	info := s.getModelInfo(c)
   240  	c.Assert(info.Users, gc.HasLen, 1)
   241  	c.Assert(info.Users[0].UserName, gc.Equals, "charlotte@local")
   242  	c.Assert(info.Machines, gc.HasLen, 0)
   243  }
   244  
   245  func (s *modelInfoSuite) getModelInfo(c *gc.C) params.ModelInfo {
   246  	results, err := s.modelmanager.ModelInfo(params.Entities{
   247  		Entities: []params.Entity{{
   248  			names.NewModelTag(s.st.model.cfg.UUID()).String(),
   249  		}},
   250  	})
   251  	c.Assert(err, jc.ErrorIsNil)
   252  	c.Assert(results.Results, gc.HasLen, 1)
   253  	c.Assert(results.Results[0].Result, gc.NotNil)
   254  	c.Assert(results.Results[0].Error, gc.IsNil)
   255  	return *results.Results[0].Result
   256  }
   257  
   258  func (s *modelInfoSuite) TestModelInfoErrorInvalidTag(c *gc.C) {
   259  	s.testModelInfoError(c, "user-bob", `"user-bob" is not a valid model tag`)
   260  }
   261  
   262  func (s *modelInfoSuite) TestModelInfoErrorGetModelNotFound(c *gc.C) {
   263  	s.st.SetErrors(errors.NotFoundf("model"))
   264  	s.testModelInfoError(c, coretesting.ModelTag.String(), `permission denied`)
   265  }
   266  
   267  func (s *modelInfoSuite) TestModelInfoErrorModelConfig(c *gc.C) {
   268  	s.st.model.SetErrors(errors.Errorf("no config for you"))
   269  	s.testModelInfoError(c, coretesting.ModelTag.String(), `no config for you`)
   270  }
   271  
   272  func (s *modelInfoSuite) TestModelInfoErrorModelUsers(c *gc.C) {
   273  	s.st.model.SetErrors(errors.Errorf("no users for you"))
   274  	s.testModelInfoError(c, coretesting.ModelTag.String(), `no users for you`)
   275  }
   276  
   277  func (s *modelInfoSuite) TestModelInfoErrorNoModelUsers(c *gc.C) {
   278  	s.st.model.users = nil
   279  	s.testModelInfoError(c, coretesting.ModelTag.String(), `permission denied`)
   280  }
   281  
   282  func (s *modelInfoSuite) TestModelInfoErrorNoAccess(c *gc.C) {
   283  	s.setAPIUser(c, names.NewUserTag("nemo@local"))
   284  	s.testModelInfoError(c, coretesting.ModelTag.String(), `permission denied`)
   285  }
   286  
   287  func (s *modelInfoSuite) testModelInfoError(c *gc.C, modelTag, expectedErr string) {
   288  	results, err := s.modelmanager.ModelInfo(params.Entities{
   289  		Entities: []params.Entity{{modelTag}},
   290  	})
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	c.Assert(results.Results, gc.HasLen, 1)
   293  	c.Assert(results.Results[0].Result, gc.IsNil)
   294  	c.Assert(results.Results[0].Error, gc.ErrorMatches, expectedErr)
   295  }
   296  
   297  type unitRetriever interface {
   298  	Unit(name string) (*state.Unit, error)
   299  }
   300  
   301  type mockState struct {
   302  	gitjujutesting.Stub
   303  
   304  	environs.EnvironConfigGetter
   305  	common.APIHostPortsGetter
   306  	common.ToolsStorageGetter
   307  	common.BlockGetter
   308  	metricsender.MetricsSenderBackend
   309  	unitRetriever
   310  
   311  	modelUUID       string
   312  	controllerUUID  string
   313  	cloud           cloud.Cloud
   314  	clouds          map[names.CloudTag]cloud.Cloud
   315  	model           *mockModel
   316  	controllerModel *mockModel
   317  	users           []permission.UserAccess
   318  	cred            cloud.Credential
   319  	machines        []common.Machine
   320  	cfgDefaults     config.ModelDefaultAttributes
   321  	blockMsg        string
   322  	block           state.BlockType
   323  }
   324  
   325  type fakeModelDescription struct {
   326  	description.Model `yaml:"-"`
   327  
   328  	UUID string `yaml:"model-uuid"`
   329  }
   330  
   331  func (st *mockState) Export() (description.Model, error) {
   332  	return &fakeModelDescription{UUID: st.modelUUID}, nil
   333  }
   334  
   335  func (st *mockState) ModelUUID() string {
   336  	st.MethodCall(st, "ModelUUID")
   337  	return st.modelUUID
   338  }
   339  
   340  func (st *mockState) ModelsForUser(user names.UserTag) ([]*state.UserModel, error) {
   341  	st.MethodCall(st, "ModelsForUser", user)
   342  	return nil, st.NextErr()
   343  }
   344  
   345  func (st *mockState) IsControllerAdmin(user names.UserTag) (bool, error) {
   346  	st.MethodCall(st, "IsControllerAdmin", user)
   347  	if st.controllerModel == nil {
   348  		return user.Canonical() == "admin@local", st.NextErr()
   349  	}
   350  	if st.controllerModel.users == nil {
   351  		return user.Canonical() == "admin@local", st.NextErr()
   352  	}
   353  
   354  	for _, u := range st.controllerModel.users {
   355  		if user.Name() == u.userName && u.access == permission.AdminAccess {
   356  			nextErr := st.NextErr()
   357  			if user.Name() != "admin" {
   358  				panic(user.Name())
   359  			}
   360  			return true, nextErr
   361  		}
   362  	}
   363  	return false, st.NextErr()
   364  }
   365  
   366  func (st *mockState) NewModel(args state.ModelArgs) (common.Model, common.ModelManagerBackend, error) {
   367  	st.MethodCall(st, "NewModel", args)
   368  	st.model.tag = names.NewModelTag(args.Config.UUID())
   369  	return st.model, st, st.NextErr()
   370  }
   371  
   372  func (st *mockState) ControllerModel() (common.Model, error) {
   373  	st.MethodCall(st, "ControllerModel")
   374  	return st.controllerModel, st.NextErr()
   375  }
   376  
   377  func (st *mockState) ControllerTag() names.ControllerTag {
   378  	st.MethodCall(st, "ControllerTag")
   379  	return names.NewControllerTag(st.controllerUUID)
   380  }
   381  
   382  func (st *mockState) ComposeNewModelConfig(modelAttr map[string]interface{}, regionSpec *environs.RegionSpec) (map[string]interface{}, error) {
   383  	st.MethodCall(st, "ComposeNewModelConfig")
   384  	attr := make(map[string]interface{})
   385  	for attrName, val := range modelAttr {
   386  		attr[attrName] = val
   387  	}
   388  	attr["something"] = "value"
   389  	return attr, st.NextErr()
   390  }
   391  
   392  func (st *mockState) ControllerUUID() string {
   393  	st.MethodCall(st, "ControllerUUID")
   394  	return st.controllerUUID
   395  }
   396  
   397  func (st *mockState) ControllerConfig() (controller.Config, error) {
   398  	st.MethodCall(st, "ControllerConfig")
   399  	return controller.Config{
   400  		controller.ControllerUUIDKey: "deadbeef-1bad-500d-9000-4b1d0d06f00d",
   401  	}, st.NextErr()
   402  }
   403  
   404  func (st *mockState) ForModel(tag names.ModelTag) (common.ModelManagerBackend, error) {
   405  	st.MethodCall(st, "ForModel", tag)
   406  	return st, st.NextErr()
   407  }
   408  
   409  func (st *mockState) GetModel(tag names.ModelTag) (common.Model, error) {
   410  	st.MethodCall(st, "GetModel", tag)
   411  	return st.model, st.NextErr()
   412  }
   413  
   414  func (st *mockState) Model() (common.Model, error) {
   415  	st.MethodCall(st, "Model")
   416  	return st.model, st.NextErr()
   417  }
   418  
   419  func (st *mockState) ModelTag() names.ModelTag {
   420  	st.MethodCall(st, "ModelTag")
   421  	return st.model.ModelTag()
   422  }
   423  
   424  func (st *mockState) AllModels() ([]common.Model, error) {
   425  	st.MethodCall(st, "AllModels")
   426  	return []common.Model{st.model}, st.NextErr()
   427  }
   428  
   429  func (st *mockState) AllMachines() ([]common.Machine, error) {
   430  	st.MethodCall(st, "AllMachines")
   431  	return st.machines, st.NextErr()
   432  }
   433  
   434  func (st *mockState) Clouds() (map[names.CloudTag]cloud.Cloud, error) {
   435  	st.MethodCall(st, "Clouds")
   436  	return st.clouds, st.NextErr()
   437  }
   438  
   439  func (st *mockState) Cloud(name string) (cloud.Cloud, error) {
   440  	st.MethodCall(st, "Cloud", name)
   441  	return st.cloud, st.NextErr()
   442  }
   443  
   444  func (st *mockState) CloudCredential(tag names.CloudCredentialTag) (cloud.Credential, error) {
   445  	st.MethodCall(st, "CloudCredential", tag)
   446  	return st.cred, st.NextErr()
   447  }
   448  
   449  func (st *mockState) Close() error {
   450  	st.MethodCall(st, "Close")
   451  	return st.NextErr()
   452  }
   453  
   454  func (st *mockState) AddModelUser(modelUUID string, spec state.UserAccessSpec) (permission.UserAccess, error) {
   455  	st.MethodCall(st, "AddModelUser", modelUUID, spec)
   456  	return permission.UserAccess{}, st.NextErr()
   457  }
   458  
   459  func (st *mockState) AddControllerUser(spec state.UserAccessSpec) (permission.UserAccess, error) {
   460  	st.MethodCall(st, "AddControllerUser", spec)
   461  	return permission.UserAccess{}, st.NextErr()
   462  }
   463  
   464  func (st *mockState) RemoveModelUser(tag names.UserTag) error {
   465  	st.MethodCall(st, "RemoveModelUser", tag)
   466  	return st.NextErr()
   467  }
   468  
   469  func (st *mockState) UserAccess(tag names.UserTag, target names.Tag) (permission.UserAccess, error) {
   470  	st.MethodCall(st, "ModelUser", tag, target)
   471  	return permission.UserAccess{}, st.NextErr()
   472  }
   473  
   474  func (st *mockState) LastModelConnection(user names.UserTag) (time.Time, error) {
   475  	st.MethodCall(st, "LastModelConnection", user)
   476  	return time.Time{}, st.NextErr()
   477  }
   478  
   479  func (st *mockState) RemoveUserAccess(subject names.UserTag, target names.Tag) error {
   480  	st.MethodCall(st, "RemoveUserAccess", subject, target)
   481  	return st.NextErr()
   482  }
   483  
   484  func (st *mockState) SetUserAccess(subject names.UserTag, target names.Tag, access permission.Access) (permission.UserAccess, error) {
   485  	st.MethodCall(st, "SetUserAccess", subject, target, access)
   486  	return permission.UserAccess{}, st.NextErr()
   487  }
   488  
   489  func (st *mockState) ModelConfigDefaultValues() (config.ModelDefaultAttributes, error) {
   490  	st.MethodCall(st, "ModelConfigDefaultValues")
   491  	return st.cfgDefaults, nil
   492  }
   493  
   494  func (st *mockState) UpdateModelConfigDefaultValues(update map[string]interface{}, remove []string, rspec *environs.RegionSpec) error {
   495  	st.MethodCall(st, "UpdateModelConfigDefaultValues", update, remove, rspec)
   496  	for k, v := range update {
   497  		if rspec != nil {
   498  			adv := st.cfgDefaults[k]
   499  			adv.Regions = append(adv.Regions, config.RegionDefaultValue{
   500  				Name:  rspec.Region,
   501  				Value: v})
   502  
   503  		} else {
   504  			st.cfgDefaults[k] = config.AttributeDefaultValues{Controller: v}
   505  		}
   506  	}
   507  	for _, n := range remove {
   508  		if rspec != nil {
   509  			for i, r := range st.cfgDefaults[n].Regions {
   510  				if r.Name == rspec.Region {
   511  					adv := st.cfgDefaults[n]
   512  					adv.Regions = append(adv.Regions[:i], adv.Regions[i+1:]...)
   513  					st.cfgDefaults[n] = adv
   514  				}
   515  			}
   516  		} else {
   517  			if len(st.cfgDefaults[n].Regions) == 0 {
   518  				delete(st.cfgDefaults, n)
   519  			} else {
   520  
   521  				st.cfgDefaults[n] = config.AttributeDefaultValues{
   522  					Regions: st.cfgDefaults[n].Regions}
   523  			}
   524  		}
   525  	}
   526  	return nil
   527  }
   528  
   529  func (st *mockState) GetBlockForType(t state.BlockType) (state.Block, bool, error) {
   530  	st.MethodCall(st, "GetBlockForType", t)
   531  	if st.block == t {
   532  		return &mockBlock{t: t, m: st.blockMsg}, true, nil
   533  	} else {
   534  		return nil, false, nil
   535  	}
   536  }
   537  
   538  func (st *mockState) DumpAll() (map[string]interface{}, error) {
   539  	st.MethodCall(st, "DumpAll")
   540  	return map[string]interface{}{
   541  		"models": "lots of data",
   542  	}, st.NextErr()
   543  }
   544  
   545  type mockBlock struct {
   546  	state.Block
   547  	t state.BlockType
   548  	m string
   549  }
   550  
   551  func (m mockBlock) Id() string { return "" }
   552  
   553  func (m mockBlock) Tag() (names.Tag, error) { return names.NewModelTag("mocktesting"), nil }
   554  
   555  func (m mockBlock) Type() state.BlockType { return m.t }
   556  
   557  func (m mockBlock) Message() string { return m.m }
   558  
   559  func (m mockBlock) ModelUUID() string { return "" }
   560  
   561  type mockMachine struct {
   562  	common.Machine
   563  	id            string
   564  	life          state.Life
   565  	containerType instance.ContainerType
   566  	hw            *instance.HardwareCharacteristics
   567  }
   568  
   569  func (m *mockMachine) Id() string {
   570  	return m.id
   571  }
   572  
   573  func (m *mockMachine) Life() state.Life {
   574  	return m.life
   575  }
   576  
   577  func (m *mockMachine) ContainerType() instance.ContainerType {
   578  	return m.containerType
   579  }
   580  
   581  func (m *mockMachine) HardwareCharacteristics() (*instance.HardwareCharacteristics, error) {
   582  	return m.hw, nil
   583  }
   584  
   585  func (m *mockMachine) AgentPresence() (bool, error) {
   586  	return true, nil
   587  }
   588  
   589  func (m *mockMachine) InstanceId() (instance.Id, error) {
   590  	return "", nil
   591  }
   592  
   593  func (m *mockMachine) WantsVote() bool {
   594  	return false
   595  }
   596  
   597  func (m *mockMachine) HasVote() bool {
   598  	return false
   599  }
   600  
   601  func (m *mockMachine) Status() (status.StatusInfo, error) {
   602  	return status.StatusInfo{}, nil
   603  }
   604  
   605  type mockModel struct {
   606  	gitjujutesting.Stub
   607  	owner  names.UserTag
   608  	life   state.Life
   609  	tag    names.ModelTag
   610  	status status.StatusInfo
   611  	cfg    *config.Config
   612  	users  []*mockModelUser
   613  }
   614  
   615  func (m *mockModel) Config() (*config.Config, error) {
   616  	m.MethodCall(m, "Config")
   617  	return m.cfg, m.NextErr()
   618  }
   619  
   620  func (m *mockModel) Owner() names.UserTag {
   621  	m.MethodCall(m, "Owner")
   622  	m.PopNoErr()
   623  	return m.owner
   624  }
   625  
   626  func (m *mockModel) ModelTag() names.ModelTag {
   627  	m.MethodCall(m, "ModelTag")
   628  	m.PopNoErr()
   629  	return m.tag
   630  }
   631  
   632  func (m *mockModel) Life() state.Life {
   633  	m.MethodCall(m, "Life")
   634  	m.PopNoErr()
   635  	return m.life
   636  }
   637  
   638  func (m *mockModel) Status() (status.StatusInfo, error) {
   639  	m.MethodCall(m, "Status")
   640  	return m.status, m.NextErr()
   641  }
   642  
   643  func (m *mockModel) Cloud() string {
   644  	m.MethodCall(m, "Cloud")
   645  	m.PopNoErr()
   646  	return "some-cloud"
   647  }
   648  
   649  func (m *mockModel) CloudRegion() string {
   650  	m.MethodCall(m, "CloudRegion")
   651  	m.PopNoErr()
   652  	return "some-region"
   653  }
   654  
   655  func (m *mockModel) CloudCredential() (names.CloudCredentialTag, bool) {
   656  	m.MethodCall(m, "CloudCredential")
   657  	m.PopNoErr()
   658  	return names.NewCloudCredentialTag("some-cloud/bob@local/some-credential"), true
   659  }
   660  
   661  func (m *mockModel) Users() ([]permission.UserAccess, error) {
   662  	m.MethodCall(m, "Users")
   663  	if err := m.NextErr(); err != nil {
   664  		return nil, err
   665  	}
   666  	users := make([]permission.UserAccess, len(m.users))
   667  	for i, user := range m.users {
   668  		users[i] = permission.UserAccess{
   669  			UserID:      strings.ToLower(user.userName),
   670  			UserTag:     names.NewUserTag(user.userName),
   671  			Object:      m.ModelTag(),
   672  			Access:      user.access,
   673  			DisplayName: user.displayName,
   674  			UserName:    user.userName,
   675  		}
   676  	}
   677  	return users, nil
   678  }
   679  
   680  func (m *mockModel) Destroy() error {
   681  	m.MethodCall(m, "Destroy")
   682  	return m.NextErr()
   683  }
   684  
   685  func (m *mockModel) DestroyIncludingHosted() error {
   686  	m.MethodCall(m, "DestroyIncludingHosted")
   687  	return m.NextErr()
   688  }
   689  
   690  type mockModelUser struct {
   691  	gitjujutesting.Stub
   692  	userName       string
   693  	displayName    string
   694  	lastConnection time.Time
   695  	access         permission.Access
   696  }