github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/testing/factory/factory.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package factory
     5  
     6  import (
     7  	"fmt"
     8  	"math/rand"
     9  	"strconv"
    10  	"sync/atomic"
    11  	"time"
    12  
    13  	"github.com/juju/charm/v12"
    14  	charmresource "github.com/juju/charm/v12/resource"
    15  	"github.com/juju/names/v5"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/utils/v3"
    18  	"github.com/juju/version/v2"
    19  	gc "gopkg.in/check.v1"
    20  	"gopkg.in/juju/environschema.v1"
    21  
    22  	"github.com/juju/juju/cloud"
    23  	"github.com/juju/juju/core/arch"
    24  	corebase "github.com/juju/juju/core/base"
    25  	coreconfig "github.com/juju/juju/core/config"
    26  	"github.com/juju/juju/core/constraints"
    27  	"github.com/juju/juju/core/instance"
    28  	"github.com/juju/juju/core/network"
    29  	"github.com/juju/juju/core/permission"
    30  	"github.com/juju/juju/core/status"
    31  	"github.com/juju/juju/environs/config"
    32  	"github.com/juju/juju/state"
    33  	"github.com/juju/juju/storage"
    34  	"github.com/juju/juju/storage/provider"
    35  	"github.com/juju/juju/testcharms"
    36  	"github.com/juju/juju/testing"
    37  	jujuversion "github.com/juju/juju/version"
    38  )
    39  
    40  const (
    41  	symbols = "abcdefghijklmopqrstuvwxyz"
    42  )
    43  
    44  type Factory struct {
    45  	pool *state.StatePool
    46  	st   *state.State
    47  }
    48  
    49  var index uint32
    50  
    51  func NewFactory(st *state.State, pool *state.StatePool) *Factory {
    52  	return &Factory{
    53  		st:   st,
    54  		pool: pool,
    55  	}
    56  }
    57  
    58  // UserParams defines the parameters for creating a user with MakeUser.
    59  type UserParams struct {
    60  	Name        string
    61  	DisplayName string
    62  	Password    string
    63  	Creator     names.Tag
    64  	NoModelUser bool
    65  	Disabled    bool
    66  	Access      permission.Access
    67  }
    68  
    69  // ModelUserParams defines the parameters for creating an environment user.
    70  type ModelUserParams struct {
    71  	User        string
    72  	DisplayName string
    73  	CreatedBy   names.Tag
    74  	Access      permission.Access
    75  }
    76  
    77  // CharmParams defines the parameters for creating a charm.
    78  type CharmParams struct {
    79  	Name         string
    80  	Series       string
    81  	Revision     string
    82  	Architecture string
    83  	URL          string
    84  }
    85  
    86  // Params for creating a machine.
    87  type MachineParams struct {
    88  	Base            state.Base
    89  	Jobs            []state.MachineJob
    90  	Password        string
    91  	Nonce           string
    92  	Constraints     constraints.Value
    93  	InstanceId      instance.Id
    94  	DisplayName     string
    95  	Characteristics *instance.HardwareCharacteristics
    96  	Addresses       network.SpaceAddresses
    97  	Volumes         []state.HostVolumeParams
    98  	Filesystems     []state.HostFilesystemParams
    99  }
   100  
   101  // ApplicationParams is used when specifying parameters for a new application.
   102  type ApplicationParams struct {
   103  	Name                    string
   104  	Charm                   *state.Charm
   105  	CharmOrigin             *state.CharmOrigin
   106  	Status                  *status.StatusInfo
   107  	ApplicationConfig       map[string]interface{}
   108  	ApplicationConfigFields environschema.Fields
   109  	CharmConfig             map[string]interface{}
   110  	Storage                 map[string]state.StorageConstraints
   111  	Constraints             constraints.Value
   112  	EndpointBindings        map[string]string
   113  	Password                string
   114  	Placement               []*instance.Placement
   115  	DesiredScale            int
   116  }
   117  
   118  // UnitParams are used to create units.
   119  type UnitParams struct {
   120  	Application *state.Application
   121  	Machine     *state.Machine
   122  	Password    string
   123  	SetCharmURL bool
   124  	Status      *status.StatusInfo
   125  	Constraints constraints.Value
   126  }
   127  
   128  // RelationParams are used to create relations.
   129  type RelationParams struct {
   130  	Endpoints []state.Endpoint
   131  }
   132  
   133  type MetricParams struct {
   134  	Unit       *state.Unit
   135  	Time       *time.Time
   136  	Metrics    []state.Metric
   137  	Sent       bool
   138  	DeleteTime *time.Time
   139  }
   140  
   141  type ModelParams struct {
   142  	Type                    state.ModelType
   143  	Name                    string
   144  	Owner                   names.Tag
   145  	ConfigAttrs             testing.Attrs
   146  	CloudName               string
   147  	CloudRegion             string
   148  	CloudCredential         names.CloudCredentialTag
   149  	StorageProviderRegistry storage.ProviderRegistry
   150  	EnvironVersion          int
   151  }
   152  
   153  type SpaceParams struct {
   154  	Name       string
   155  	ProviderID network.Id
   156  	SubnetIDs  []string
   157  	IsPublic   bool
   158  }
   159  
   160  // RandomSuffix adds a random 5 character suffix to the presented string.
   161  func (*Factory) RandomSuffix(prefix string) string {
   162  	result := prefix
   163  	for i := 0; i < 5; i++ {
   164  		result += string(symbols[rand.Intn(len(symbols))])
   165  	}
   166  	return result
   167  }
   168  
   169  func uniqueInteger() int {
   170  	return int(atomic.AddUint32(&index, 1))
   171  }
   172  
   173  func uniqueString(prefix string) string {
   174  	if prefix == "" {
   175  		prefix = "no-prefix"
   176  	}
   177  	return fmt.Sprintf("%s-%d", prefix, uniqueInteger())
   178  }
   179  
   180  // MakeUser will create a user with values defined by the params.
   181  // For attributes of UserParams that are the default empty values,
   182  // some meaningful valid values are used instead.
   183  // If params is not specified, defaults are used.
   184  // If params.NoModelUser is false, the user will also be created
   185  // in the current model.
   186  func (factory *Factory) MakeUser(c *gc.C, params *UserParams) *state.User {
   187  	if params == nil {
   188  		params = &UserParams{}
   189  	}
   190  	if params.Name == "" {
   191  		params.Name = uniqueString("username")
   192  	}
   193  	if params.DisplayName == "" {
   194  		params.DisplayName = uniqueString("display name")
   195  	}
   196  	if params.Password == "" {
   197  		params.Password = "password"
   198  	}
   199  	if params.Creator == nil {
   200  		env, err := factory.st.Model()
   201  		c.Assert(err, jc.ErrorIsNil)
   202  		params.Creator = env.Owner()
   203  	}
   204  	if params.Access == permission.NoAccess {
   205  		params.Access = permission.AdminAccess
   206  	}
   207  	creatorUserTag := params.Creator.(names.UserTag)
   208  	user, err := factory.st.AddUser(
   209  		params.Name, params.DisplayName, params.Password, creatorUserTag.Name())
   210  	c.Assert(err, jc.ErrorIsNil)
   211  	if !params.NoModelUser {
   212  		model, err := factory.st.Model()
   213  		c.Assert(err, jc.ErrorIsNil)
   214  		_, err = model.AddUser(state.UserAccessSpec{
   215  			User:        user.UserTag(),
   216  			CreatedBy:   names.NewUserTag(user.CreatedBy()),
   217  			DisplayName: params.DisplayName,
   218  			Access:      params.Access,
   219  		})
   220  		c.Assert(err, jc.ErrorIsNil)
   221  	}
   222  	if params.Disabled {
   223  		err := user.Disable()
   224  		c.Assert(err, jc.ErrorIsNil)
   225  	}
   226  	return user
   227  }
   228  
   229  // MakeModelUser will create a modelUser with values defined by the params. For
   230  // attributes of ModelUserParams that are the default empty values, some
   231  // meaningful valid values are used instead. If params is not specified,
   232  // defaults are used.
   233  func (factory *Factory) MakeModelUser(c *gc.C, params *ModelUserParams) permission.UserAccess {
   234  	if params == nil {
   235  		params = &ModelUserParams{}
   236  	}
   237  	if params.User == "" {
   238  		user := factory.MakeUser(c, &UserParams{NoModelUser: true})
   239  		params.User = user.UserTag().Id()
   240  	}
   241  	if params.DisplayName == "" {
   242  		params.DisplayName = uniqueString("display name")
   243  	}
   244  	if params.Access == permission.NoAccess {
   245  		params.Access = permission.AdminAccess
   246  	}
   247  	if params.CreatedBy == nil {
   248  		env, err := factory.st.Model()
   249  		c.Assert(err, jc.ErrorIsNil)
   250  		params.CreatedBy = env.Owner()
   251  	}
   252  	model, err := factory.st.Model()
   253  	c.Assert(err, jc.ErrorIsNil)
   254  
   255  	createdByUserTag := params.CreatedBy.(names.UserTag)
   256  	modelUser, err := model.AddUser(state.UserAccessSpec{
   257  		User:        names.NewUserTag(params.User),
   258  		CreatedBy:   createdByUserTag,
   259  		DisplayName: params.DisplayName,
   260  		Access:      params.Access,
   261  	})
   262  	c.Assert(err, jc.ErrorIsNil)
   263  	return modelUser
   264  }
   265  
   266  func (factory *Factory) paramsFillDefaults(c *gc.C, params *MachineParams) *MachineParams {
   267  	if params == nil {
   268  		params = &MachineParams{}
   269  	}
   270  	if params.Base.String() == "" {
   271  		params.Base = state.UbuntuBase("12.10")
   272  	}
   273  	if params.Nonce == "" {
   274  		params.Nonce = "nonce"
   275  	}
   276  	if len(params.Jobs) == 0 {
   277  		params.Jobs = []state.MachineJob{state.JobHostUnits}
   278  	}
   279  	if params.InstanceId == "" {
   280  		params.InstanceId = instance.Id(uniqueString("id"))
   281  	}
   282  	if params.Password == "" {
   283  		var err error
   284  		params.Password, err = utils.RandomPassword()
   285  		c.Assert(err, jc.ErrorIsNil)
   286  	}
   287  	if params.Characteristics == nil {
   288  		arch := arch.DefaultArchitecture
   289  		mem := uint64(64 * 1024 * 1024 * 1024)
   290  		hardware := instance.HardwareCharacteristics{
   291  			Arch: &arch,
   292  			Mem:  &mem,
   293  		}
   294  		params.Characteristics = &hardware
   295  	}
   296  
   297  	return params
   298  }
   299  
   300  // MakeMachineNested will make a machine nested in the machine with ID given.
   301  func (factory *Factory) MakeMachineNested(c *gc.C, parentId string, params *MachineParams) *state.Machine {
   302  	params = factory.paramsFillDefaults(c, params)
   303  	machineTemplate := state.MachineTemplate{
   304  		Base:        params.Base,
   305  		Jobs:        params.Jobs,
   306  		Volumes:     params.Volumes,
   307  		Filesystems: params.Filesystems,
   308  		Constraints: params.Constraints,
   309  	}
   310  
   311  	m, err := factory.st.AddMachineInsideMachine(
   312  		machineTemplate,
   313  		parentId,
   314  		instance.LXD,
   315  	)
   316  	c.Assert(err, jc.ErrorIsNil)
   317  	err = m.SetProvisioned(params.InstanceId, params.DisplayName, params.Nonce, params.Characteristics)
   318  	c.Assert(err, jc.ErrorIsNil)
   319  	current := testing.CurrentVersion()
   320  	err = m.SetAgentVersion(current)
   321  	c.Assert(err, jc.ErrorIsNil)
   322  	return m
   323  }
   324  
   325  // MakeMachine will add a machine with values defined in params. For some
   326  // values in params, if they are missing, some meaningful empty values will be
   327  // set.
   328  // If params is not specified, defaults are used.
   329  func (factory *Factory) MakeMachine(c *gc.C, params *MachineParams) *state.Machine {
   330  	machine, _ := factory.MakeMachineReturningPassword(c, params)
   331  	return machine
   332  }
   333  
   334  // MakeMachineReturningPassword will add a machine with values defined in
   335  // params. For some values in params, if they are missing, some meaningful
   336  // empty values will be set. If params is not specified, defaults are used.
   337  // The machine and its password are returned.
   338  func (factory *Factory) MakeMachineReturningPassword(c *gc.C, params *MachineParams) (*state.Machine, string) {
   339  	params = factory.paramsFillDefaults(c, params)
   340  	return factory.makeMachineReturningPassword(c, params, true)
   341  }
   342  
   343  // MakeUnprovisionedMachineReturningPassword will add a machine with values
   344  // defined in params. For some values in params, if they are missing, some
   345  // meaningful empty values will be set. If params is not specified, defaults
   346  // are used. The machine and its password are returned; the machine will not
   347  // be provisioned.
   348  func (factory *Factory) MakeUnprovisionedMachineReturningPassword(c *gc.C, params *MachineParams) (*state.Machine, string) {
   349  	if params != nil {
   350  		c.Assert(params.Nonce, gc.Equals, "")
   351  		c.Assert(params.InstanceId, gc.Equals, instance.Id(""))
   352  		c.Assert(params.Characteristics, gc.IsNil)
   353  	}
   354  	params = factory.paramsFillDefaults(c, params)
   355  	params.Nonce = ""
   356  	params.InstanceId = ""
   357  	params.Characteristics = nil
   358  	return factory.makeMachineReturningPassword(c, params, false)
   359  }
   360  
   361  func (factory *Factory) makeMachineReturningPassword(c *gc.C, params *MachineParams, setProvisioned bool) (*state.Machine, string) {
   362  	machineTemplate := state.MachineTemplate{
   363  		Base:        params.Base,
   364  		Jobs:        params.Jobs,
   365  		Volumes:     params.Volumes,
   366  		Filesystems: params.Filesystems,
   367  		Constraints: params.Constraints,
   368  	}
   369  
   370  	if params.Characteristics != nil {
   371  		machineTemplate.HardwareCharacteristics = *params.Characteristics
   372  	}
   373  	machine, err := factory.st.AddOneMachine(machineTemplate)
   374  	c.Assert(err, jc.ErrorIsNil)
   375  	if setProvisioned {
   376  		err = machine.SetProvisioned(params.InstanceId, params.DisplayName, params.Nonce, params.Characteristics)
   377  		c.Assert(err, jc.ErrorIsNil)
   378  	}
   379  	err = machine.SetPassword(params.Password)
   380  	c.Assert(err, jc.ErrorIsNil)
   381  	if len(params.Addresses) > 0 {
   382  		err := machine.SetProviderAddresses(params.Addresses...)
   383  		c.Assert(err, jc.ErrorIsNil)
   384  	}
   385  	current := testing.CurrentVersion()
   386  	err = machine.SetAgentVersion(current)
   387  	c.Assert(err, jc.ErrorIsNil)
   388  	return machine, params.Password
   389  }
   390  
   391  // MakeCharm creates a charm with the values specified in params.
   392  // Sensible default values are substituted for missing ones.
   393  // Supported charms depend on the charm/testing package.
   394  // Currently supported charms:
   395  //
   396  //	all-hooks, category, dummy, logging, monitoring, mysql,
   397  //	mysql-alternative, riak, terracotta, upgrade1, upgrade2, varnish,
   398  //	varnish-alternative, wordpress.
   399  //
   400  // If params is not specified, defaults are used.
   401  func (factory *Factory) MakeCharm(c *gc.C, params *CharmParams) *state.Charm {
   402  	if params == nil {
   403  		params = &CharmParams{}
   404  	}
   405  	if params.Name == "" {
   406  		params.Name = "mysql"
   407  	}
   408  	if params.Series == "" {
   409  		params.Series = "quantal"
   410  	}
   411  	if params.Revision == "" {
   412  		params.Revision = fmt.Sprintf("%d", uniqueInteger())
   413  	}
   414  	if params.URL == "" {
   415  		params.URL = fmt.Sprintf("ch:amd64/%s/%s-%s", params.Series, params.Name, params.Revision)
   416  	}
   417  
   418  	ch := testcharms.RepoForSeries(params.Series).CharmDir(params.Name)
   419  
   420  	bundleSHA256 := uniqueString("bundlesha")
   421  	info := state.CharmInfo{
   422  		Charm:       ch,
   423  		ID:          params.URL,
   424  		StoragePath: "fake-storage-path",
   425  		SHA256:      bundleSHA256,
   426  	}
   427  	charm, err := factory.st.AddCharm(info)
   428  	c.Assert(err, jc.ErrorIsNil)
   429  	return charm
   430  }
   431  
   432  func (factory *Factory) MakeCharmV2(c *gc.C, params *CharmParams) *state.Charm {
   433  	if params == nil {
   434  		params = &CharmParams{}
   435  	}
   436  	if params.Name == "" {
   437  		params.Name = "snappass-test"
   438  	}
   439  	if params.Series == "" {
   440  		params.Series = "quantal"
   441  	}
   442  	if params.Architecture == "" {
   443  		params.Architecture = "amd64"
   444  	}
   445  	if params.Revision == "" {
   446  		params.Revision = fmt.Sprintf("%d", uniqueInteger())
   447  	}
   448  	if params.URL == "" {
   449  		params.URL = fmt.Sprintf("ch:%s/%s/%s-%s", params.Architecture, params.Series, params.Name, params.Revision)
   450  	}
   451  
   452  	ch := testcharms.Hub.CharmDir(params.Name)
   453  
   454  	bundleSHA256 := uniqueString("bundlesha")
   455  	info := state.CharmInfo{
   456  		Charm:       ch,
   457  		ID:          params.URL,
   458  		StoragePath: "fake-storage-path",
   459  		SHA256:      bundleSHA256,
   460  	}
   461  	charm, err := factory.st.AddCharm(info)
   462  	c.Assert(err, jc.ErrorIsNil)
   463  	return charm
   464  }
   465  
   466  // MakeApplication creates an application with the specified parameters, substituting
   467  // sane defaults for missing values.
   468  // If params is not specified, defaults are used.
   469  func (factory *Factory) MakeApplication(c *gc.C, params *ApplicationParams) *state.Application {
   470  	app, _ := factory.MakeApplicationReturningPassword(c, params)
   471  	return app
   472  }
   473  
   474  // MakeApplicationReturningPassword creates an application with the specified parameters, substituting
   475  // sane defaults for missing values.
   476  // If params is not specified, defaults are used.
   477  // It returns the application and its password.
   478  func (factory *Factory) MakeApplicationReturningPassword(c *gc.C, params *ApplicationParams) (*state.Application, string) {
   479  	if params == nil {
   480  		params = &ApplicationParams{}
   481  	}
   482  	if params.Charm == nil {
   483  		params.Charm = factory.MakeCharm(c, nil)
   484  	}
   485  	if params.Name == "" {
   486  		params.Name = params.Charm.Meta().Name
   487  	}
   488  	if params.Password == "" {
   489  		var err error
   490  		params.Password, err = utils.RandomPassword()
   491  		c.Assert(err, jc.ErrorIsNil)
   492  	}
   493  	if params.CharmOrigin == nil {
   494  		curl := charm.MustParseURL(params.Charm.URL())
   495  		chSeries := curl.Series
   496  		// Legacy k8s charms - assume ubuntu focal.
   497  		if chSeries == "kubernetes" {
   498  			chSeries = corebase.LegacyKubernetesSeries()
   499  		}
   500  		base, err := corebase.GetBaseFromSeries(chSeries)
   501  		c.Assert(err, jc.ErrorIsNil)
   502  		var channel *state.Channel
   503  		var source string
   504  		// local charms cannot have a channel
   505  		if charm.CharmHub.Matches(curl.Schema) {
   506  			channel = &state.Channel{Risk: "stable"}
   507  			source = "charm-hub"
   508  		} else if charm.Local.Matches(curl.Schema) {
   509  			source = "local"
   510  		}
   511  		params.CharmOrigin = &state.CharmOrigin{
   512  			Channel: channel,
   513  			Source:  source,
   514  			Platform: &state.Platform{
   515  				Architecture: curl.Architecture,
   516  				OS:           base.OS,
   517  				Channel:      base.Channel.String(),
   518  			}}
   519  	}
   520  
   521  	rSt := factory.st.Resources()
   522  
   523  	resourceMap := make(map[string]string)
   524  	for name, res := range params.Charm.Meta().Resources {
   525  		pendingID, err := rSt.AddPendingResource(params.Name, "", charmresource.Resource{
   526  			Meta:   res,
   527  			Origin: charmresource.OriginUpload,
   528  		})
   529  		c.Assert(err, jc.ErrorIsNil)
   530  		resourceMap[name] = pendingID
   531  	}
   532  
   533  	appConfig, err := coreconfig.NewConfig(params.ApplicationConfig, params.ApplicationConfigFields, nil)
   534  	c.Assert(err, jc.ErrorIsNil)
   535  	application, err := factory.st.AddApplication(state.AddApplicationArgs{
   536  		Name:              params.Name,
   537  		Charm:             params.Charm,
   538  		CharmOrigin:       params.CharmOrigin,
   539  		CharmConfig:       params.CharmConfig,
   540  		ApplicationConfig: appConfig,
   541  		Storage:           params.Storage,
   542  		Constraints:       params.Constraints,
   543  		Resources:         resourceMap,
   544  		EndpointBindings:  params.EndpointBindings,
   545  		Placement:         params.Placement,
   546  	})
   547  	c.Assert(err, jc.ErrorIsNil)
   548  	err = application.SetPassword(params.Password)
   549  	c.Assert(err, jc.ErrorIsNil)
   550  
   551  	model, err := factory.st.Model()
   552  	c.Assert(err, jc.ErrorIsNil)
   553  	isCAAS := model.Type() == state.ModelTypeCAAS
   554  	if isCAAS {
   555  		err = application.SetScale(params.DesiredScale, 0, true)
   556  		c.Assert(err, jc.ErrorIsNil)
   557  	}
   558  
   559  	if params.Status != nil {
   560  		now := time.Now()
   561  		s := status.StatusInfo{
   562  			Status:  params.Status.Status,
   563  			Message: params.Status.Message,
   564  			Data:    params.Status.Data,
   565  			Since:   &now,
   566  		}
   567  		err = application.SetStatus(s)
   568  		c.Assert(err, jc.ErrorIsNil)
   569  	}
   570  
   571  	if isCAAS {
   572  		agentTools := version.Binary{
   573  			Number:  jujuversion.Current,
   574  			Arch:    arch.HostArch(),
   575  			Release: application.CharmOrigin().Platform.OS,
   576  		}
   577  		err = application.SetAgentVersion(agentTools)
   578  		c.Assert(err, jc.ErrorIsNil)
   579  	}
   580  
   581  	return application, params.Password
   582  }
   583  
   584  // MakeUnit creates an application unit with specified params, filling in
   585  // sane defaults for missing values. If params is not specified, defaults
   586  // are used.
   587  //
   588  // If the unit is being added to an IAAS model, then it will be assigned
   589  // to a machine.
   590  func (factory *Factory) MakeUnit(c *gc.C, params *UnitParams) *state.Unit {
   591  	unit, _ := factory.MakeUnitReturningPassword(c, params)
   592  	return unit
   593  }
   594  
   595  // MakeUnitReturningPassword creates an application unit with specified params,
   596  // filling in sane defaults for missing values. If params is not specified,
   597  // defaults are used. The unit and its password are returned.
   598  //
   599  // If the unit is being added to an IAAS model, then it will be assigned to a
   600  // machine.
   601  func (factory *Factory) MakeUnitReturningPassword(c *gc.C, params *UnitParams) (*state.Unit, string) {
   602  	if params == nil {
   603  		params = &UnitParams{}
   604  	}
   605  	model, err := factory.st.Model()
   606  	c.Assert(err, jc.ErrorIsNil)
   607  	switch model.Type() {
   608  	case state.ModelTypeIAAS:
   609  		if params.Machine == nil {
   610  			var mParams *MachineParams
   611  			if params.Application != nil {
   612  				platform := params.Application.CharmOrigin().Platform
   613  				mParams = &MachineParams{
   614  					Base: state.Base{OS: platform.OS, Channel: platform.Channel},
   615  				}
   616  			}
   617  			params.Machine = factory.MakeMachine(c, mParams)
   618  		}
   619  	default:
   620  		if params.Machine != nil {
   621  			c.Fatalf("machines not supported by model of type %q", model.Type())
   622  		}
   623  	}
   624  	if params.Application == nil {
   625  		series := "quantal"
   626  		if model.Type() == state.ModelTypeCAAS {
   627  			series = "kubernetes"
   628  		}
   629  		ch := factory.MakeCharm(c, &CharmParams{Series: series})
   630  		params.Application = factory.MakeApplication(c, &ApplicationParams{
   631  			Constraints: params.Constraints,
   632  			Charm:       ch,
   633  		})
   634  	}
   635  	if params.Password == "" {
   636  		var err error
   637  		params.Password, err = utils.RandomPassword()
   638  		c.Assert(err, jc.ErrorIsNil)
   639  	}
   640  	unit, err := params.Application.AddUnit(state.AddUnitParams{})
   641  	c.Assert(err, jc.ErrorIsNil)
   642  
   643  	if params.Machine != nil {
   644  		err = unit.AssignToMachine(params.Machine)
   645  		c.Assert(err, jc.ErrorIsNil)
   646  	}
   647  
   648  	if model.Type() == state.ModelTypeIAAS {
   649  		agentTools := version.Binary{
   650  			Number:  jujuversion.Current,
   651  			Arch:    arch.HostArch(),
   652  			Release: params.Application.CharmOrigin().Platform.OS,
   653  		}
   654  		err = unit.SetAgentVersion(agentTools)
   655  		c.Assert(err, jc.ErrorIsNil)
   656  	}
   657  
   658  	if params.SetCharmURL {
   659  		applicationCharmURL, _ := params.Application.CharmURL()
   660  		err = unit.SetCharmURL(*applicationCharmURL)
   661  		c.Assert(err, jc.ErrorIsNil)
   662  	}
   663  	err = unit.SetPassword(params.Password)
   664  	c.Assert(err, jc.ErrorIsNil)
   665  
   666  	if params.Status != nil {
   667  		now := time.Now()
   668  		s := status.StatusInfo{
   669  			Status:  params.Status.Status,
   670  			Message: params.Status.Message,
   671  			Data:    params.Status.Data,
   672  			Since:   &now,
   673  		}
   674  		err = unit.SetStatus(s)
   675  		c.Assert(err, jc.ErrorIsNil)
   676  	}
   677  
   678  	return unit, params.Password
   679  }
   680  
   681  // MakeMetric makes a metric with specified params, filling in
   682  // sane defaults for missing values.
   683  // If params is not specified, defaults are used.
   684  func (factory *Factory) MakeMetric(c *gc.C, params *MetricParams) *state.MetricBatch {
   685  	now := time.Now().Round(time.Second).UTC()
   686  	if params == nil {
   687  		params = &MetricParams{}
   688  	}
   689  	if params.Unit == nil {
   690  		meteredCharm := factory.MakeCharm(c, &CharmParams{Name: "metered", URL: "ch:quantal/metered"})
   691  		meteredApplication := factory.MakeApplication(c, &ApplicationParams{Charm: meteredCharm})
   692  		params.Unit = factory.MakeUnit(c, &UnitParams{Application: meteredApplication, SetCharmURL: true})
   693  	}
   694  	if params.Time == nil {
   695  		params.Time = &now
   696  	}
   697  	if params.Metrics == nil {
   698  		params.Metrics = []state.Metric{{
   699  			Key:    "pings",
   700  			Value:  strconv.Itoa(uniqueInteger()),
   701  			Time:   *params.Time,
   702  			Labels: map[string]string{"foo": "bar"},
   703  		}}
   704  	}
   705  
   706  	chURL := params.Unit.CharmURL()
   707  	c.Assert(chURL, gc.NotNil)
   708  
   709  	metric, err := factory.st.AddMetrics(
   710  		state.BatchParam{
   711  			UUID:     utils.MustNewUUID().String(),
   712  			Created:  *params.Time,
   713  			CharmURL: *chURL,
   714  			Metrics:  params.Metrics,
   715  			Unit:     params.Unit.UnitTag(),
   716  		})
   717  	c.Assert(err, jc.ErrorIsNil)
   718  	if params.Sent {
   719  		t := now
   720  		if params.DeleteTime != nil {
   721  			t = *params.DeleteTime
   722  		}
   723  		err := metric.SetSent(t)
   724  		c.Assert(err, jc.ErrorIsNil)
   725  	}
   726  	return metric
   727  }
   728  
   729  // MakeRelation create a relation with specified params, filling in sane
   730  // defaults for missing values.
   731  // If params is not specified, defaults are used.
   732  func (factory *Factory) MakeRelation(c *gc.C, params *RelationParams) *state.Relation {
   733  	if params == nil {
   734  		params = &RelationParams{}
   735  	}
   736  	if len(params.Endpoints) == 0 {
   737  		s1 := factory.MakeApplication(c, &ApplicationParams{
   738  			Charm: factory.MakeCharm(c, &CharmParams{
   739  				Name: "mysql",
   740  			}),
   741  		})
   742  		e1, err := s1.Endpoint("server")
   743  		c.Assert(err, jc.ErrorIsNil)
   744  
   745  		s2 := factory.MakeApplication(c, &ApplicationParams{
   746  			Charm: factory.MakeCharm(c, &CharmParams{
   747  				Name: "wordpress",
   748  			}),
   749  		})
   750  		e2, err := s2.Endpoint("db")
   751  		c.Assert(err, jc.ErrorIsNil)
   752  
   753  		params.Endpoints = []state.Endpoint{e1, e2}
   754  	}
   755  
   756  	relation, err := factory.st.AddRelation(params.Endpoints...)
   757  	c.Assert(err, jc.ErrorIsNil)
   758  
   759  	return relation
   760  }
   761  
   762  // MakeModel creates an model with specified params,
   763  // filling in sane defaults for missing values. If params is nil,
   764  // defaults are used for all values.
   765  //
   766  // By default the new model shares the same owner as the calling Factory's
   767  // model. TODO(ericclaudejones) MakeModel should return the model itself rather
   768  // than the state.
   769  func (factory *Factory) MakeModel(c *gc.C, params *ModelParams) *state.State {
   770  	if params == nil {
   771  		params = new(ModelParams)
   772  	}
   773  	if params.Type == state.ModelType("") {
   774  		params.Type = state.ModelTypeIAAS
   775  	}
   776  	if params.Name == "" {
   777  		params.Name = uniqueString("testmodel")
   778  	}
   779  	if params.CloudName == "" {
   780  		params.CloudName = "dummy"
   781  	}
   782  	if params.CloudRegion == "" && params.CloudName == "dummy" {
   783  		params.CloudRegion = "dummy-region"
   784  	}
   785  	if params.CloudRegion == "<none>" {
   786  		params.CloudRegion = ""
   787  	}
   788  	if params.Owner == nil {
   789  		origEnv, err := factory.st.Model()
   790  		c.Assert(err, jc.ErrorIsNil)
   791  		params.Owner = origEnv.Owner()
   792  	}
   793  	if params.StorageProviderRegistry == nil {
   794  		params.StorageProviderRegistry = provider.CommonStorageProviders()
   795  	}
   796  
   797  	// For IAAS models, it only makes sense to make a model with the same provider
   798  	// as the initial model, or things will break elsewhere.
   799  	// For CAAS models, the type is "kubernetes".
   800  	currentCfg := factory.currentCfg(c)
   801  	cfgType := currentCfg.Type()
   802  	if params.Type == state.ModelTypeCAAS {
   803  		cfgType = "kubernetes"
   804  	}
   805  
   806  	uuid, err := utils.NewUUID()
   807  	c.Assert(err, jc.ErrorIsNil)
   808  	cfg := testing.CustomModelConfig(c, testing.Attrs{
   809  		"name": params.Name,
   810  		"uuid": uuid.String(),
   811  		"type": cfgType,
   812  	}.Merge(params.ConfigAttrs))
   813  	controller := state.NewController(factory.pool)
   814  	_, st, err := controller.NewModel(state.ModelArgs{
   815  		Type:                    params.Type,
   816  		CloudName:               params.CloudName,
   817  		CloudRegion:             params.CloudRegion,
   818  		CloudCredential:         params.CloudCredential,
   819  		Config:                  cfg,
   820  		Owner:                   params.Owner.(names.UserTag),
   821  		StorageProviderRegistry: params.StorageProviderRegistry,
   822  		EnvironVersion:          params.EnvironVersion,
   823  	})
   824  	c.Assert(err, jc.ErrorIsNil)
   825  	err = factory.pool.StartWorkers(st)
   826  	c.Assert(err, jc.ErrorIsNil)
   827  	return st
   828  }
   829  
   830  // MakeCAASModel creates a CAAS model with specified params,
   831  // filling in sane defaults for missing values. If params is nil,
   832  // defaults are used for all values.
   833  func (factory *Factory) MakeCAASModel(c *gc.C, params *ModelParams) *state.State {
   834  	if params == nil {
   835  		params = &ModelParams{}
   836  	}
   837  	params.Type = state.ModelTypeCAAS
   838  	if params.Owner == nil {
   839  		origEnv, err := factory.st.Model()
   840  		c.Assert(err, jc.ErrorIsNil)
   841  		params.Owner = origEnv.Owner()
   842  	}
   843  	if params.CloudName == "" {
   844  		err := factory.st.AddCloud(cloud.Cloud{
   845  			Name:      "caascloud",
   846  			Type:      "kubernetes",
   847  			AuthTypes: []cloud.AuthType{cloud.UserPassAuthType},
   848  		}, params.Owner.Id())
   849  		c.Assert(err, jc.ErrorIsNil)
   850  		params.CloudName = "caascloud"
   851  	}
   852  	if params.CloudCredential.IsZero() {
   853  		if params.Owner == nil {
   854  			origEnv, err := factory.st.Model()
   855  			c.Assert(err, jc.ErrorIsNil)
   856  			params.Owner = origEnv.Owner()
   857  		}
   858  		cred := cloud.NewCredential(cloud.UserPassAuthType, nil)
   859  		tag := names.NewCloudCredentialTag(
   860  			fmt.Sprintf("%s/%s/dummy-credential", params.CloudName, params.Owner.Id()))
   861  		err := factory.st.UpdateCloudCredential(tag, cred)
   862  		c.Assert(err, jc.ErrorIsNil)
   863  		params.CloudCredential = tag
   864  	}
   865  	return factory.MakeModel(c, params)
   866  }
   867  
   868  // MakeSpace will create a new space with the specified params. If the space
   869  // name is not set, a unique space name is created.
   870  func (factory *Factory) MakeSpace(c *gc.C, params *SpaceParams) *state.Space {
   871  	if params == nil {
   872  		params = new(SpaceParams)
   873  	}
   874  	if params.Name == "" {
   875  		params.Name = uniqueString("space-")
   876  	}
   877  	space, err := factory.st.AddSpace(params.Name, params.ProviderID, params.SubnetIDs, params.IsPublic)
   878  	c.Assert(err, jc.ErrorIsNil)
   879  	return space
   880  }
   881  
   882  func (factory *Factory) currentCfg(c *gc.C) *config.Config {
   883  	model, err := factory.st.Model()
   884  	c.Assert(err, jc.ErrorIsNil)
   885  
   886  	currentCfg, err := model.ModelConfig()
   887  	c.Assert(err, jc.ErrorIsNil)
   888  
   889  	return currentCfg
   890  }