github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/migration_import_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/names"
    11  	jc "github.com/juju/testing/checkers"
    12  	"github.com/juju/utils"
    13  	"github.com/juju/version"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	"github.com/juju/juju/constraints"
    17  	"github.com/juju/juju/core/description"
    18  	"github.com/juju/juju/network"
    19  	"github.com/juju/juju/state"
    20  	"github.com/juju/juju/status"
    21  	"github.com/juju/juju/testing/factory"
    22  )
    23  
    24  type MigrationImportSuite struct {
    25  	MigrationSuite
    26  }
    27  
    28  var _ = gc.Suite(&MigrationImportSuite{})
    29  
    30  func (s *MigrationImportSuite) checkStatusHistory(c *gc.C, exported, imported status.StatusHistoryGetter, size int) {
    31  	exportedHistory, err := exported.StatusHistory(size)
    32  	c.Assert(err, jc.ErrorIsNil)
    33  	importedHistory, err := imported.StatusHistory(size)
    34  	c.Assert(err, jc.ErrorIsNil)
    35  	for i := 0; i < size; i++ {
    36  		c.Check(importedHistory[i].Status, gc.Equals, exportedHistory[i].Status)
    37  		c.Check(importedHistory[i].Message, gc.Equals, exportedHistory[i].Message)
    38  		c.Check(importedHistory[i].Data, jc.DeepEquals, exportedHistory[i].Data)
    39  		c.Check(importedHistory[i].Since, jc.DeepEquals, exportedHistory[i].Since)
    40  	}
    41  }
    42  
    43  func (s *MigrationImportSuite) TestExisting(c *gc.C) {
    44  	out, err := s.State.Export()
    45  	c.Assert(err, jc.ErrorIsNil)
    46  
    47  	_, _, err = s.State.Import(out)
    48  	c.Assert(err, jc.Satisfies, errors.IsAlreadyExists)
    49  }
    50  
    51  func (s *MigrationImportSuite) importModel(c *gc.C) (*state.Model, *state.State) {
    52  	out, err := s.State.Export()
    53  	c.Assert(err, jc.ErrorIsNil)
    54  
    55  	uuid := utils.MustNewUUID().String()
    56  	in := newModel(out, uuid, "new")
    57  
    58  	newModel, newSt, err := s.State.Import(in)
    59  	c.Assert(err, jc.ErrorIsNil)
    60  	return newModel, newSt
    61  }
    62  
    63  func (s *MigrationImportSuite) assertAnnotations(c *gc.C, newSt *state.State, entity state.GlobalEntity) {
    64  	annotations, err := newSt.Annotations(entity)
    65  	c.Assert(err, jc.ErrorIsNil)
    66  	c.Assert(annotations, jc.DeepEquals, testAnnotations)
    67  }
    68  
    69  func (s *MigrationImportSuite) TestNewModel(c *gc.C) {
    70  	cons := constraints.MustParse("arch=amd64 mem=8G")
    71  	latestTools := version.MustParse("2.0.1")
    72  	s.setLatestTools(c, latestTools)
    73  	c.Assert(s.State.SetModelConstraints(cons), jc.ErrorIsNil)
    74  	machineSeq := s.setRandSequenceValue(c, "machine")
    75  	fooSeq := s.setRandSequenceValue(c, "service-foo")
    76  	s.State.SwitchBlockOn(state.ChangeBlock, "locked down")
    77  
    78  	original, err := s.State.Model()
    79  	c.Assert(err, jc.ErrorIsNil)
    80  
    81  	err = s.State.SetAnnotations(original, testAnnotations)
    82  	c.Assert(err, jc.ErrorIsNil)
    83  
    84  	out, err := s.State.Export()
    85  	c.Assert(err, jc.ErrorIsNil)
    86  
    87  	uuid := utils.MustNewUUID().String()
    88  	in := newModel(out, uuid, "new")
    89  
    90  	newModel, newSt, err := s.State.Import(in)
    91  	c.Assert(err, jc.ErrorIsNil)
    92  	defer newSt.Close()
    93  
    94  	c.Assert(newModel.Owner(), gc.Equals, original.Owner())
    95  	c.Assert(newModel.LatestToolsVersion(), gc.Equals, latestTools)
    96  	c.Assert(newModel.MigrationMode(), gc.Equals, state.MigrationModeImporting)
    97  	s.assertAnnotations(c, newSt, newModel)
    98  
    99  	originalConfig, err := original.Config()
   100  	c.Assert(err, jc.ErrorIsNil)
   101  	originalAttrs := originalConfig.AllAttrs()
   102  
   103  	newConfig, err := newModel.Config()
   104  	c.Assert(err, jc.ErrorIsNil)
   105  	newAttrs := newConfig.AllAttrs()
   106  
   107  	c.Assert(newAttrs["uuid"], gc.Equals, uuid)
   108  	c.Assert(newAttrs["name"], gc.Equals, "new")
   109  
   110  	// Now drop the uuid and name and the rest of the attributes should match.
   111  	delete(newAttrs, "uuid")
   112  	delete(newAttrs, "name")
   113  	delete(originalAttrs, "uuid")
   114  	delete(originalAttrs, "name")
   115  	c.Assert(newAttrs, jc.DeepEquals, originalAttrs)
   116  
   117  	newCons, err := newSt.ModelConstraints()
   118  	c.Assert(err, jc.ErrorIsNil)
   119  	// Can't test the constraints directly, so go through the string repr.
   120  	c.Assert(newCons.String(), gc.Equals, cons.String())
   121  
   122  	seq, err := state.Sequence(newSt, "machine")
   123  	c.Assert(err, jc.ErrorIsNil)
   124  	c.Assert(seq, gc.Equals, machineSeq)
   125  	seq, err = state.Sequence(newSt, "service-foo")
   126  	c.Assert(err, jc.ErrorIsNil)
   127  	c.Assert(seq, gc.Equals, fooSeq)
   128  
   129  	blocks, err := newSt.AllBlocks()
   130  	c.Assert(err, jc.ErrorIsNil)
   131  	c.Assert(blocks, gc.HasLen, 1)
   132  	c.Assert(blocks[0].Type(), gc.Equals, state.ChangeBlock)
   133  	c.Assert(blocks[0].Message(), gc.Equals, "locked down")
   134  }
   135  
   136  func (s *MigrationImportSuite) newModelUser(c *gc.C, name string, readOnly bool, lastConnection time.Time) *state.ModelUser {
   137  	access := state.ModelAdminAccess
   138  	if readOnly {
   139  		access = state.ModelReadAccess
   140  	}
   141  	user, err := s.State.AddModelUser(state.ModelUserSpec{
   142  		User:      names.NewUserTag(name),
   143  		CreatedBy: s.Owner,
   144  		Access:    access,
   145  	})
   146  	c.Assert(err, jc.ErrorIsNil)
   147  	if !lastConnection.IsZero() {
   148  		err = state.UpdateModelUserLastConnection(user, lastConnection)
   149  		c.Assert(err, jc.ErrorIsNil)
   150  	}
   151  	return user
   152  }
   153  
   154  func (s *MigrationImportSuite) AssertUserEqual(c *gc.C, newUser, oldUser *state.ModelUser) {
   155  	c.Assert(newUser.UserName(), gc.Equals, oldUser.UserName())
   156  	c.Assert(newUser.DisplayName(), gc.Equals, oldUser.DisplayName())
   157  	c.Assert(newUser.CreatedBy(), gc.Equals, oldUser.CreatedBy())
   158  	c.Assert(newUser.DateCreated(), gc.Equals, oldUser.DateCreated())
   159  	c.Assert(newUser.ReadOnly(), gc.Equals, oldUser.ReadOnly())
   160  
   161  	connTime, err := oldUser.LastConnection()
   162  	if state.IsNeverConnectedError(err) {
   163  		_, err := newUser.LastConnection()
   164  		// The new user should also return an error for last connection.
   165  		c.Assert(err, jc.Satisfies, state.IsNeverConnectedError)
   166  	} else {
   167  		c.Assert(err, jc.ErrorIsNil)
   168  		newTime, err := newUser.LastConnection()
   169  		c.Assert(err, jc.ErrorIsNil)
   170  		c.Assert(newTime, gc.Equals, connTime)
   171  	}
   172  }
   173  
   174  func (s *MigrationImportSuite) TestModelUsers(c *gc.C) {
   175  	// To be sure with this test, we create three env users, and remove
   176  	// the owner.
   177  	err := s.State.RemoveModelUser(s.Owner)
   178  	c.Assert(err, jc.ErrorIsNil)
   179  
   180  	lastConnection := state.NowToTheSecond()
   181  
   182  	bravo := s.newModelUser(c, "bravo@external", false, lastConnection)
   183  	charlie := s.newModelUser(c, "charlie@external", true, lastConnection)
   184  	delta := s.newModelUser(c, "delta@external", true, time.Time{})
   185  
   186  	newModel, newSt := s.importModel(c)
   187  	defer newSt.Close()
   188  
   189  	// Check the import values of the users.
   190  	for _, user := range []*state.ModelUser{bravo, charlie, delta} {
   191  		newUser, err := newSt.ModelUser(user.UserTag())
   192  		c.Assert(err, jc.ErrorIsNil)
   193  		s.AssertUserEqual(c, newUser, user)
   194  	}
   195  
   196  	// Also make sure that there aren't any more.
   197  	allUsers, err := newModel.Users()
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	c.Assert(allUsers, gc.HasLen, 3)
   200  }
   201  
   202  func (s *MigrationImportSuite) AssertMachineEqual(c *gc.C, newMachine, oldMachine *state.Machine) {
   203  	c.Assert(newMachine.Id(), gc.Equals, oldMachine.Id())
   204  	c.Assert(newMachine.Principals(), jc.DeepEquals, oldMachine.Principals())
   205  	c.Assert(newMachine.Series(), gc.Equals, oldMachine.Series())
   206  	c.Assert(newMachine.ContainerType(), gc.Equals, oldMachine.ContainerType())
   207  	newHardware, err := newMachine.HardwareCharacteristics()
   208  	c.Assert(err, jc.ErrorIsNil)
   209  	oldHardware, err := oldMachine.HardwareCharacteristics()
   210  	c.Assert(err, jc.ErrorIsNil)
   211  	c.Assert(newHardware, jc.DeepEquals, oldHardware)
   212  	c.Assert(newMachine.Jobs(), jc.DeepEquals, oldMachine.Jobs())
   213  	c.Assert(newMachine.Life(), gc.Equals, oldMachine.Life())
   214  	newTools, err := newMachine.AgentTools()
   215  	c.Assert(err, jc.ErrorIsNil)
   216  	oldTools, err := oldMachine.AgentTools()
   217  	c.Assert(err, jc.ErrorIsNil)
   218  	c.Assert(newTools, jc.DeepEquals, oldTools)
   219  }
   220  
   221  func (s *MigrationImportSuite) TestMachines(c *gc.C) {
   222  	// Let's add a machine with an LXC container.
   223  	cons := constraints.MustParse("arch=amd64 mem=8G")
   224  	machine1 := s.Factory.MakeMachine(c, &factory.MachineParams{
   225  		Constraints: cons,
   226  	})
   227  	err := s.State.SetAnnotations(machine1, testAnnotations)
   228  	c.Assert(err, jc.ErrorIsNil)
   229  	s.primeStatusHistory(c, machine1, status.StatusStarted, 5)
   230  
   231  	// machine1 should have some instance data.
   232  	hardware, err := machine1.HardwareCharacteristics()
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	c.Assert(hardware, gc.NotNil)
   235  
   236  	_ = s.Factory.MakeMachineNested(c, machine1.Id(), nil)
   237  
   238  	allMachines, err := s.State.AllMachines()
   239  	c.Assert(err, jc.ErrorIsNil)
   240  	c.Assert(allMachines, gc.HasLen, 2)
   241  
   242  	_, newSt := s.importModel(c)
   243  	defer newSt.Close()
   244  
   245  	importedMachines, err := newSt.AllMachines()
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	c.Assert(importedMachines, gc.HasLen, 2)
   248  
   249  	// AllMachines returns the machines in the same order, yay us.
   250  	for i, newMachine := range importedMachines {
   251  		s.AssertMachineEqual(c, newMachine, allMachines[i])
   252  	}
   253  
   254  	// And a few extra checks.
   255  	parent := importedMachines[0]
   256  	container := importedMachines[1]
   257  	containers, err := parent.Containers()
   258  	c.Assert(err, jc.ErrorIsNil)
   259  	c.Assert(containers, jc.DeepEquals, []string{container.Id()})
   260  	parentId, isContainer := container.ParentId()
   261  	c.Assert(parentId, gc.Equals, parent.Id())
   262  	c.Assert(isContainer, jc.IsTrue)
   263  
   264  	s.assertAnnotations(c, newSt, parent)
   265  	s.checkStatusHistory(c, machine1, parent, 5)
   266  
   267  	newCons, err := parent.Constraints()
   268  	c.Assert(err, jc.ErrorIsNil)
   269  	// Can't test the constraints directly, so go through the string repr.
   270  	c.Assert(newCons.String(), gc.Equals, cons.String())
   271  }
   272  
   273  func (s *MigrationImportSuite) TestServices(c *gc.C) {
   274  	// Add a service with both settings and leadership settings.
   275  	cons := constraints.MustParse("arch=amd64 mem=8G")
   276  	service := s.Factory.MakeService(c, &factory.ServiceParams{
   277  		Settings: map[string]interface{}{
   278  			"foo": "bar",
   279  		},
   280  		Constraints: cons,
   281  	})
   282  	err := service.UpdateLeaderSettings(&goodToken{}, map[string]string{
   283  		"leader": "true",
   284  	})
   285  	c.Assert(err, jc.ErrorIsNil)
   286  	err = service.SetMetricCredentials([]byte("sekrit"))
   287  	c.Assert(err, jc.ErrorIsNil)
   288  	// Expose the service.
   289  	c.Assert(service.SetExposed(), jc.ErrorIsNil)
   290  	err = s.State.SetAnnotations(service, testAnnotations)
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	s.primeStatusHistory(c, service, status.StatusActive, 5)
   293  
   294  	allServices, err := s.State.AllServices()
   295  	c.Assert(err, jc.ErrorIsNil)
   296  	c.Assert(allServices, gc.HasLen, 1)
   297  
   298  	_, newSt := s.importModel(c)
   299  	defer newSt.Close()
   300  
   301  	importedServices, err := newSt.AllServices()
   302  	c.Assert(err, jc.ErrorIsNil)
   303  	c.Assert(importedServices, gc.HasLen, 1)
   304  
   305  	exported := allServices[0]
   306  	imported := importedServices[0]
   307  
   308  	c.Assert(imported.ServiceTag(), gc.Equals, exported.ServiceTag())
   309  	c.Assert(imported.Series(), gc.Equals, exported.Series())
   310  	c.Assert(imported.IsExposed(), gc.Equals, exported.IsExposed())
   311  	c.Assert(imported.MetricCredentials(), jc.DeepEquals, exported.MetricCredentials())
   312  
   313  	exportedConfig, err := exported.ConfigSettings()
   314  	c.Assert(err, jc.ErrorIsNil)
   315  	importedConfig, err := imported.ConfigSettings()
   316  	c.Assert(err, jc.ErrorIsNil)
   317  	c.Assert(importedConfig, jc.DeepEquals, exportedConfig)
   318  
   319  	exportedLeaderSettings, err := exported.LeaderSettings()
   320  	c.Assert(err, jc.ErrorIsNil)
   321  	importedLeaderSettings, err := imported.LeaderSettings()
   322  	c.Assert(err, jc.ErrorIsNil)
   323  	c.Assert(importedLeaderSettings, jc.DeepEquals, exportedLeaderSettings)
   324  
   325  	s.assertAnnotations(c, newSt, imported)
   326  	s.checkStatusHistory(c, service, imported, 5)
   327  
   328  	newCons, err := imported.Constraints()
   329  	c.Assert(err, jc.ErrorIsNil)
   330  	// Can't test the constraints directly, so go through the string repr.
   331  	c.Assert(newCons.String(), gc.Equals, cons.String())
   332  }
   333  
   334  func (s *MigrationImportSuite) TestServiceLeaders(c *gc.C) {
   335  	s.makeServiceWithLeader(c, "mysql", 2, 1)
   336  	s.makeServiceWithLeader(c, "wordpress", 4, 2)
   337  
   338  	_, newSt := s.importModel(c)
   339  	defer newSt.Close()
   340  
   341  	leaders := make(map[string]string)
   342  	for key, value := range state.LeadershipLeases(newSt) {
   343  		leaders[key] = value.Holder
   344  	}
   345  	c.Assert(leaders, jc.DeepEquals, map[string]string{
   346  		"mysql":     "mysql/1",
   347  		"wordpress": "wordpress/2",
   348  	})
   349  }
   350  
   351  func (s *MigrationImportSuite) TestUnits(c *gc.C) {
   352  	cons := constraints.MustParse("arch=amd64 mem=8G")
   353  	exported, pwd := s.Factory.MakeUnitReturningPassword(c, &factory.UnitParams{
   354  		Constraints: cons,
   355  	})
   356  	err := exported.SetMeterStatus("GREEN", "some info")
   357  	c.Assert(err, jc.ErrorIsNil)
   358  	err = s.State.SetAnnotations(exported, testAnnotations)
   359  	c.Assert(err, jc.ErrorIsNil)
   360  	s.primeStatusHistory(c, exported, status.StatusActive, 5)
   361  	s.primeStatusHistory(c, exported.Agent(), status.StatusIdle, 5)
   362  
   363  	_, newSt := s.importModel(c)
   364  	defer newSt.Close()
   365  
   366  	importedServices, err := newSt.AllServices()
   367  	c.Assert(err, jc.ErrorIsNil)
   368  	c.Assert(importedServices, gc.HasLen, 1)
   369  
   370  	importedUnits, err := importedServices[0].AllUnits()
   371  	c.Assert(err, jc.ErrorIsNil)
   372  	c.Assert(importedUnits, gc.HasLen, 1)
   373  	imported := importedUnits[0]
   374  
   375  	c.Assert(imported.UnitTag(), gc.Equals, exported.UnitTag())
   376  	c.Assert(imported.PasswordValid(pwd), jc.IsTrue)
   377  
   378  	exportedMachineId, err := exported.AssignedMachineId()
   379  	c.Assert(err, jc.ErrorIsNil)
   380  	importedMachineId, err := imported.AssignedMachineId()
   381  	c.Assert(err, jc.ErrorIsNil)
   382  	c.Assert(importedMachineId, gc.Equals, exportedMachineId)
   383  
   384  	// Confirm machine Principals are set.
   385  	exportedMachine, err := s.State.Machine(exportedMachineId)
   386  	c.Assert(err, jc.ErrorIsNil)
   387  	importedMachine, err := newSt.Machine(importedMachineId)
   388  	c.Assert(err, jc.ErrorIsNil)
   389  	s.AssertMachineEqual(c, importedMachine, exportedMachine)
   390  
   391  	meterStatus, err := imported.GetMeterStatus()
   392  	c.Assert(err, jc.ErrorIsNil)
   393  	c.Assert(meterStatus, gc.Equals, state.MeterStatus{state.MeterGreen, "some info"})
   394  	s.assertAnnotations(c, newSt, imported)
   395  	s.checkStatusHistory(c, exported, imported, 5)
   396  	s.checkStatusHistory(c, exported.Agent(), imported.Agent(), 5)
   397  
   398  	newCons, err := imported.Constraints()
   399  	c.Assert(err, jc.ErrorIsNil)
   400  	// Can't test the constraints directly, so go through the string repr.
   401  	c.Assert(newCons.String(), gc.Equals, cons.String())
   402  }
   403  
   404  func (s *MigrationImportSuite) TestRelations(c *gc.C) {
   405  	// Need to remove owner from service.
   406  	ignored := s.Owner
   407  	wordpress := state.AddTestingService(c, s.State, "wordpress", state.AddTestingCharm(c, s.State, "wordpress"), ignored)
   408  	state.AddTestingService(c, s.State, "mysql", state.AddTestingCharm(c, s.State, "mysql"), ignored)
   409  	eps, err := s.State.InferEndpoints("mysql", "wordpress")
   410  	c.Assert(err, jc.ErrorIsNil)
   411  	rel, err := s.State.AddRelation(eps...)
   412  	c.Assert(err, jc.ErrorIsNil)
   413  	wordpress_0 := s.Factory.MakeUnit(c, &factory.UnitParams{Service: wordpress})
   414  
   415  	ru, err := rel.Unit(wordpress_0)
   416  	c.Assert(err, jc.ErrorIsNil)
   417  	relSettings := map[string]interface{}{
   418  		"name": "wordpress/0",
   419  	}
   420  	err = ru.EnterScope(relSettings)
   421  	c.Assert(err, jc.ErrorIsNil)
   422  
   423  	_, newSt := s.importModel(c)
   424  	defer newSt.Close()
   425  
   426  	newWordpress, err := newSt.Service("wordpress")
   427  	c.Assert(err, jc.ErrorIsNil)
   428  	c.Assert(state.RelationCount(newWordpress), gc.Equals, 1)
   429  	rels, err := newWordpress.Relations()
   430  	c.Assert(err, jc.ErrorIsNil)
   431  	c.Assert(rels, gc.HasLen, 1)
   432  	units, err := newWordpress.AllUnits()
   433  	c.Assert(err, jc.ErrorIsNil)
   434  	c.Assert(units, gc.HasLen, 1)
   435  
   436  	ru, err = rels[0].Unit(units[0])
   437  	c.Assert(err, jc.ErrorIsNil)
   438  
   439  	settings, err := ru.Settings()
   440  	c.Assert(err, jc.ErrorIsNil)
   441  	c.Assert(settings.Map(), gc.DeepEquals, relSettings)
   442  }
   443  
   444  func (s *MigrationImportSuite) TestUnitsOpenPorts(c *gc.C) {
   445  	unit := s.Factory.MakeUnit(c, nil)
   446  	err := unit.OpenPorts("tcp", 1234, 2345)
   447  	c.Assert(err, jc.ErrorIsNil)
   448  
   449  	_, newSt := s.importModel(c)
   450  	defer newSt.Close()
   451  
   452  	// Even though the opened ports document is stored with the
   453  	// machine, the only way to easily access it is through the units.
   454  	imported, err := newSt.Unit(unit.Name())
   455  	c.Assert(err, jc.ErrorIsNil)
   456  
   457  	ports, err := imported.OpenedPorts()
   458  	c.Assert(err, jc.ErrorIsNil)
   459  	c.Assert(ports, gc.HasLen, 1)
   460  	c.Assert(ports[0], gc.Equals, network.PortRange{
   461  		FromPort: 1234,
   462  		ToPort:   2345,
   463  		Protocol: "tcp",
   464  	})
   465  }
   466  
   467  func (s *MigrationImportSuite) TestDestroyEmptyModel(c *gc.C) {
   468  	newModel, newSt := s.importModel(c)
   469  	defer newSt.Close()
   470  	s.assertDestroyModelAdvancesLife(c, newModel, state.Dead)
   471  }
   472  
   473  func (s *MigrationImportSuite) TestDestroyModelWithMachine(c *gc.C) {
   474  	s.Factory.MakeMachine(c, nil)
   475  	newModel, newSt := s.importModel(c)
   476  	defer newSt.Close()
   477  	s.assertDestroyModelAdvancesLife(c, newModel, state.Dying)
   478  }
   479  
   480  func (s *MigrationImportSuite) TestDestroyModelWithService(c *gc.C) {
   481  	s.Factory.MakeService(c, nil)
   482  	newModel, newSt := s.importModel(c)
   483  	defer newSt.Close()
   484  	s.assertDestroyModelAdvancesLife(c, newModel, state.Dying)
   485  }
   486  
   487  func (s *MigrationImportSuite) assertDestroyModelAdvancesLife(c *gc.C, m *state.Model, life state.Life) {
   488  	err := m.Destroy()
   489  	c.Assert(err, jc.ErrorIsNil)
   490  	err = m.Refresh()
   491  	c.Assert(err, jc.ErrorIsNil)
   492  	c.Assert(m.Life(), gc.Equals, life)
   493  }
   494  
   495  // newModel replaces the uuid and name of the config attributes so we
   496  // can use all the other data to validate imports. An owner and name of the
   497  // model are unique together in a controller.
   498  func newModel(m description.Model, uuid, name string) description.Model {
   499  	return &mockModel{m, uuid, name}
   500  }
   501  
   502  type mockModel struct {
   503  	description.Model
   504  	uuid string
   505  	name string
   506  }
   507  
   508  func (m *mockModel) Tag() names.ModelTag {
   509  	return names.NewModelTag(m.uuid)
   510  }
   511  
   512  func (m *mockModel) Config() map[string]interface{} {
   513  	c := m.Model.Config()
   514  	c["uuid"] = m.uuid
   515  	c["name"] = m.name
   516  	return c
   517  }